def load_filter_ansible(env): from ansible.plugins.loader import filter_loader, test_loader for path in [ str(x.resolve()) for x in Path( f"{sys.base_prefix}/lib/python3.8/site-packages/ansible_collections/" ).glob("**/plugins/filter") if "tests" not in str(x) ]: filter_loader.add_directory(path) for fp in filter_loader.all(): for filter_name, filter in fp.filters().items(): path_parts = fp._original_path.split("/") if path_parts[-5:-3] == ["site-packages", "ansible"]: env.filters[f"ansible.builtin.{filter_name}"] = filter env.filters[filter_name] = filter else: site_packages = path_parts.index("site-packages") if path_parts[site_packages + 2 : -3] == [ "community", "general", ] or path_parts[site_packages + 2 : -3] == ["ansible", "netcommon"]: env.filters[filter_name] = filter filter_name = ".".join( path_parts[site_packages + 2 : -3] + [filter_name] ) env.filters[filter_name] = filter for fp in test_loader.all(): env.tests.update(fp.tests())
def ansible_filters(filter_paths): # Load Ansible filters for path in filter_paths: filter_loader.add_directory(path) return { k: v for filter in filter_loader.all() for k, v in filter.filters().items() }
def check(template, out, err): env = jinja2.Environment(loader=AbsolutePathLoader()) for fp in filter_loader.all(): env.filters.update(fp.filters()) try: env.get_template(template) # NOTE(sambetts) Don't output OKs so we keep logs clean # out.write("%s: Syntax OK\n" % template) return 0 except jinja2.TemplateNotFound: err.write("%s: File not found\n" % template) return 2 except jinja2.exceptions.TemplateSyntaxError as e: err.write("%s: Syntax check failed: %s in %s at %d\n" % (template, e.message, e.filename, e.lineno)) return 1
def safe_eval(expr, locals=None, include_exceptions=False): ''' This is intended for allowing things like: with_items: a_list_variable Where Jinja2 would return a string but we do not want to allow it to call functions (outside of Jinja2, where the env is constrained). Based on: http://stackoverflow.com/questions/12523516/using-ast-and-whitelists-to-make-pythons-eval-safe ''' locals = {} if locals is None else locals # define certain JSON types # eg. JSON booleans are unknown to python eval() JSON_TYPES = { 'false': False, 'null': None, 'true': True, } # this is the whitelist of AST nodes we are going to # allow in the evaluation. Any node type other than # those listed here will raise an exception in our custom # visitor class defined below. SAFE_NODES = set( ( ast.Add, ast.BinOp, # ast.Call, ast.Compare, ast.Dict, ast.Div, ast.Expression, ast.List, ast.Load, ast.Mult, ast.Num, ast.Name, ast.Str, ast.Sub, ast.USub, ast.Tuple, ast.UnaryOp, ) ) # AST node types were expanded after 2.6 if sys.version_info[:2] >= (2, 7): SAFE_NODES.update( set( (ast.Set,) ) ) # And in Python 3.4 too if sys.version_info[:2] >= (3, 4): SAFE_NODES.update( set( (ast.NameConstant,) ) ) filter_list = [] for filter_ in filter_loader.all(): filter_list.extend(filter_.filters().keys()) test_list = [] for test in test_loader.all(): test_list.extend(test.tests().keys()) CALL_WHITELIST = C.DEFAULT_CALLABLE_WHITELIST + filter_list + test_list class CleansingNodeVisitor(ast.NodeVisitor): def generic_visit(self, node, inside_call=False): if type(node) not in SAFE_NODES: raise Exception("invalid expression (%s)" % expr) elif isinstance(node, ast.Call): inside_call = True elif isinstance(node, ast.Name) and inside_call: # Disallow calls to builtin functions that we have not vetted # as safe. Other functions are excluded by setting locals in # the call to eval() later on if hasattr(builtins, node.id) and node.id not in CALL_WHITELIST: raise Exception("invalid function: %s" % node.id) # iterate over all child nodes for child_node in ast.iter_child_nodes(node): self.generic_visit(child_node, inside_call) if not isinstance(expr, string_types): # already templated to a datastructure, perhaps? if include_exceptions: return (expr, None) return expr cnv = CleansingNodeVisitor() try: parsed_tree = ast.parse(expr, mode='eval') cnv.visit(parsed_tree) compiled = compile(parsed_tree, expr, 'eval') # Note: passing our own globals and locals here constrains what # callables (and other identifiers) are recognized. this is in # addition to the filtering of builtins done in CleansingNodeVisitor result = eval(compiled, JSON_TYPES, dict(locals)) if include_exceptions: return (result, None) else: return result except SyntaxError as e: # special handling for syntax errors, we just return # the expression string back as-is to support late evaluation if include_exceptions: return (expr, None) return expr except Exception as e: if include_exceptions: return (expr, e) return expr
def safe_eval(expr, locals=None, include_exceptions=False): ''' This is intended for allowing things like: with_items: a_list_variable Where Jinja2 would return a string but we do not want to allow it to call functions (outside of Jinja2, where the env is constrained). Based on: http://stackoverflow.com/questions/12523516/using-ast-and-whitelists-to-make-pythons-eval-safe ''' locals = {} if locals is None else locals # define certain JSON types # eg. JSON booleans are unknown to python eval() OUR_GLOBALS = { '__builtins__': {}, # avoid global builtins as per eval docs 'false': False, 'null': None, 'true': True, # also add back some builtins we do need 'True': True, 'False': False, 'None': None } # this is the whitelist of AST nodes we are going to # allow in the evaluation. Any node type other than # those listed here will raise an exception in our custom # visitor class defined below. SAFE_NODES = set(( ast.Add, ast.BinOp, # ast.Call, ast.Compare, ast.Dict, ast.Div, ast.Expression, ast.List, ast.Load, ast.Mult, ast.Num, ast.Name, ast.Str, ast.Sub, ast.USub, ast.Tuple, ast.UnaryOp, )) # AST node types were expanded after 2.6 if sys.version_info[:2] >= (2, 7): SAFE_NODES.update(set((ast.Set, ))) # And in Python 3.4 too if sys.version_info[:2] >= (3, 4): SAFE_NODES.update(set((ast.NameConstant, ))) # And in Python 3.6 too, although not encountered until Python 3.8, see https://bugs.python.org/issue32892 if sys.version_info[:2] >= (3, 6): SAFE_NODES.update(set((ast.Constant, ))) filter_list = [] for filter_ in filter_loader.all(): try: filter_list.extend(filter_.filters().keys()) except Exception: # This is handled and displayed in JinjaPluginIntercept._load_ansible_plugins continue test_list = [] for test in test_loader.all(): try: test_list.extend(test.tests().keys()) except Exception: # This is handled and displayed in JinjaPluginIntercept._load_ansible_plugins continue CALL_ENABLED = C.CALLABLE_ACCEPT_LIST + filter_list + test_list class CleansingNodeVisitor(ast.NodeVisitor): def generic_visit(self, node, inside_call=False): if type(node) not in SAFE_NODES: raise Exception("invalid expression (%s)" % expr) elif isinstance(node, ast.Call): inside_call = True elif isinstance(node, ast.Name) and inside_call: # Disallow calls to builtin functions that we have not vetted # as safe. Other functions are excluded by setting locals in # the call to eval() later on if hasattr(builtins, node.id) and node.id not in CALL_ENABLED: raise Exception("invalid function: %s" % node.id) # iterate over all child nodes for child_node in ast.iter_child_nodes(node): self.generic_visit(child_node, inside_call) if not isinstance(expr, string_types): # already templated to a datastructure, perhaps? if include_exceptions: return (expr, None) return expr cnv = CleansingNodeVisitor() try: parsed_tree = ast.parse(expr, mode='eval') cnv.visit(parsed_tree) compiled = compile(parsed_tree, '<expr %s>' % to_native(expr), 'eval') # Note: passing our own globals and locals here constrains what # callables (and other identifiers) are recognized. this is in # addition to the filtering of builtins done in CleansingNodeVisitor result = eval(compiled, OUR_GLOBALS, dict(locals)) if PY2: # On Python 2 u"{'key': 'value'}" is evaluated to {'key': 'value'}, # ensure it is converted to {u'key': u'value'}. result = container_to_text(result) if include_exceptions: return (result, None) else: return result except SyntaxError as e: # special handling for syntax errors, we just return # the expression string back as-is to support late evaluation if include_exceptions: return (expr, None) return expr except Exception as e: if include_exceptions: return (expr, e) return expr
def ansible_filters(filter_paths): # Load Ansible filters for path in filter_paths: filter_loader.add_directory(path) return { k:v for filter in filter_loader.all() for k,v in filter.filters().items() }
#!/usr/bin/python3 from ansible.plugins.loader import filter_loader from ansible.release import __version__ output = open('poly-ansible-jinja2-filters.el', 'w') version_comment = 'Automatically generated for Ansible ' + __version__ + '.' output.write(";; " + version_comment + "\n\n") output.write("(defvar poly-ansible-jinja2-filters\n") output.write(" (list") for plugin in filter_loader.all(): for filter_name in plugin.filters(): output.write('\n "' + filter_name + '"') output.write(")\n") output.write(" \"Additional Jinja2 filters defined by Ansible.\n" + version_comment + "\")\n\n") output.write("(provide 'poly-ansible-jinja2-filters)\n") output.close()