Advertisement
Guest User

Untitled

a guest
Sep 18th, 2017
427
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.58 KB | None | 0 0
  1. # Proof-of-concept Jinja2 conditional expression evaluator.
  2.  
  3.  
  4. # Based on code from Ansible, under the following license agreement:
  5. #
  6. # (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
  7. #
  8. # This file is part of Ansible
  9. #
  10. # Ansible is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 3 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Ansible is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
  22.  
  23.  
  24. import ast
  25. import re
  26.  
  27. import jinja2
  28.  
  29.  
  30. class Jinja2ExpressionEvaluator:
  31. def __init__(self, variables):
  32. self.variables = variables
  33.  
  34. self.environment = jinja2.Environment()
  35.  
  36. # Remove the default global functions such as range(), dict(), lipsum()
  37. # etc. -- these don't make sense for conditional expressions.
  38. self.environment.globals = []
  39.  
  40. def template(self, data):
  41. '''Expand the given string using the Jinja2 templating engine'''
  42. template = self.environment.from_string(data)
  43. new_context = template.new_context(self.variables, shared=True)
  44. result = template.root_render_func(new_context)
  45. return jinja2.utils.concat(result)
  46.  
  47. DEFINED_REGEX = re.compile(r'([\w_]+)\s+(not\s+is|is|is\s+not)\s+(defined|undefined)')
  48. def extract_defined_undefined(self, conditional):
  49. '''Extract subexpressions like '$var is defined' from the expression.
  50.  
  51. We need to treat these specially because undefined variables trigger an
  52. exception from the rendering function.
  53.  
  54. '''
  55. results = []
  56.  
  57. cond = conditional
  58. m = self.DEFINED_REGEX.search(cond)
  59. while m:
  60. results.append(m.groups())
  61. cond = cond[m.end():]
  62. m = self.DEFINED_REGEX.search(cond)
  63.  
  64. return result
  65.  
  66. def evaluate_conditional(self, conditional):
  67. try:
  68. presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional
  69. val = self.template(presented).strip()
  70. if val == "True":
  71. return True
  72. elif val == "False":
  73. return False
  74. else:
  75. raise RuntimeError("unable to evaluate conditional: %s" % original)
  76. except jinja2.exceptions.UndefinedError as e:
  77. # The templating failed, meaning most likely a variable was undefined. If we happened
  78. # to be looking for an undefined variable, return True, otherwise fail
  79. try:
  80. # first we extract the variable name from the error message
  81. var_name = re.compile(r"'(hostvars\[.+\]|[\w_]+)' is undefined").search(str(e)).groups()[0]
  82. # next we extract all defined/undefined tests from the conditional string
  83. def_undef = self.extract_defined_undefined(conditional)
  84. # then we loop through these, comparing the error variable name against
  85. # each def/undef test we found above. If there is a match, we determine
  86. # whether the logic/state mean the variable should exist or not and return
  87. # the corresponding True/False
  88. for (du_var, logic, state) in def_undef:
  89. # when we compare the var names, normalize quotes because something
  90. # like hostvars['foo'] may be tested against hostvars["foo"]
  91. if var_name.replace("'", '"') == du_var.replace("'", '"'):
  92. # the should exist is a xor test between a negation in the logic portion
  93. # against the state (defined or undefined)
  94. should_exist = ('not' in logic) != (state == 'defined')
  95. if should_exist:
  96. return False
  97. else:
  98. return True
  99. # as nothing above matched the failed var name, re-raise here to
  100. # trigger the UndefinedVariable exception again below
  101. raise
  102. except Exception:
  103. raise jinja2.exceptions.UndefinedError("error while evaluating conditional (%s): %s" % (original, e))
  104.  
  105.  
  106. def evaluate_condition(text, variables):
  107. engine = Jinja2ExpressionEvaluator(variables=variables)
  108. return engine.evaluate_conditional(text)
  109.  
  110.  
  111. variables = {
  112. 'xyzzy': 3,
  113. 'plugh': 10,
  114. 'plover': 'string',
  115. 'y2': True,
  116. 'features': ['first', 'second', ['nested', 'thing']],
  117. }
  118.  
  119. tests = [
  120. # Simple boolean logic
  121. 'True and not False',
  122. # Variable evaluations
  123. 'plugh == 9',
  124. 'xyzzy > 2 and plugh > 9',
  125. 'plover == "string"',
  126. 'plover == "String"',
  127. # Type coercion
  128. 'y2',
  129. 'plover',
  130. 'y2 == True',
  131. 'y2 == "True"',
  132. '5 == "5"',
  133. '0',
  134. # The 'defined' function takes a special codepath that triggers when
  135. # jinja2 raises an UndefinedError exception.
  136. 'y2 is defined',
  137. 'y3 is defined',
  138. '(y3 is defined or plover == "false") or True',
  139. # Lists
  140. '"second" in features',
  141. '"third" in features',
  142. 'features[0] == "second"',
  143. ]
  144.  
  145. for condition in tests:
  146. result = evaluate_condition(condition, variables)
  147. print("%s: %s" % (condition, result))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement