From 7ee793baa47745290a143f3d841e433bea67591b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ryan=20Pers=C3=A9e?= Date: Tue, 21 Jun 2022 17:09:06 +0200 Subject: [PATCH 1/4] Added basic support for Ansible filters --- README.md | 5 +++++ jinja2cli/cli.py | 19 +++++++++++++++++-- setup.py | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 13579e6..dbceaaa 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,11 @@ Options: template ``` +## Optional Ansible support +If `Ansible Core` is present, you can use Ansible filters within your templates. + +`$ pip install jinja2-cli[ansible]` + ## Optional YAML support If `PyYAML` is present, you can use YAML as an input data source. diff --git a/jinja2cli/cli.py b/jinja2cli/cli.py index fa57ad9..5ab46bf 100644 --- a/jinja2cli/cli.py +++ b/jinja2cli/cli.py @@ -220,7 +220,13 @@ def _parse_env(data): } -def render(template_path, data, extensions, strict=False): +def _load_ansible_filters(): + from ansible.plugins.filter.core import FilterModule + + return FilterModule().filters() + + +def render(template_path, data, extensions, strict=False, ansible=False): from jinja2 import ( __version__ as jinja_version, Environment, @@ -246,6 +252,8 @@ def render(template_path, data, extensions, strict=False): ) if strict: env.undefined = StrictUndefined + if ansible: + env.filters.update(_load_ansible_filters()) # Add environ global env.globals["environ"] = lambda key: force_text(os.environ.get(key)) @@ -336,7 +344,7 @@ def cli(opts, args): out = codecs.getwriter("utf8")(out) - out.write(render(template_path, data, extensions, opts.strict)) + out.write(render(template_path, data, extensions, opts.strict, opts.ansible)) out.flush() return 0 @@ -422,6 +430,13 @@ def main(): dest="strict", action="store_true", ) + parser.add_option( + "-a", + "--ansible", + help="Enable support for Ansible filters", + dest="ansible", + action="store_true", + ) parser.add_option( "-o", "--outfile", diff --git a/setup.py b/setup.py index ccade5d..c4bd972 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ license="BSD", install_requires=install_requires, extras_require={ + "ansible": install_requires + ["ansible-core"], "yaml": install_requires + ["pyyaml"], "toml": install_requires + ["toml"], "xml": install_requires + ["xmltodict"], From 78a7fa82d18623561bd6f9d07e272216bd6a5e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ryan=20Pers=C3=A9e?= Date: Tue, 21 Jun 2022 17:24:04 +0200 Subject: [PATCH 2/4] Updated usage options in README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dbceaaa..9a4a88d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Usage: jinja2 [options] Options: --version show program's version number and exit -h, --help show this help message and exit - --format=FORMAT format of input variables: auto, ini, json, + --format=FORMAT format of input variables: auto, env, ini, json, querystring, yaml, yml -e EXTENSIONS, --extension=EXTENSIONS extra jinja2 extensions to load @@ -26,6 +26,9 @@ Options: Use only this section from the configuration --strict Disallow undefined variables to be used within the template + -a, --ansible Enable support for Ansible filters + -o FILE, --outfile=FILE + File to use for output. Default is stdout. ``` ## Optional Ansible support From ecab194cab14864dccf041cdf5a6d4e245edfd3e Mon Sep 17 00:00:00 2001 From: rpersee Date: Wed, 22 Jun 2022 08:50:38 +0200 Subject: [PATCH 3/4] Import error message and more filters for Ansible Added import error message when `Ansible Core` is not installed (and `--ansible` is used). Importing all filters from `ansible.plugins.filter`. --- jinja2cli/cli.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/jinja2cli/cli.py b/jinja2cli/cli.py index 5ab46bf..7aa2579 100644 --- a/jinja2cli/cli.py +++ b/jinja2cli/cli.py @@ -221,9 +221,21 @@ def _parse_env(data): def _load_ansible_filters(): - from ansible.plugins.filter.core import FilterModule + from pkgutil import iter_modules + from jinja2.utils import import_string - return FilterModule().filters() + try: + import ansible.plugins.filter + except ImportError: + print("This feature requires the `ansible-core` package.") + raise + + filters = dict() + for module in iter_modules(ansible.plugins.filter.__path__): + filter_module = import_string(f"ansible.plugins.filter.{module.name}") + filters.update(filter_module.FilterModule().filters()) + + return filters def render(template_path, data, extensions, strict=False, ansible=False): From 67e2305ea7a926ddc9bdb0b59a4142e40d8c37b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ryan=20Pers=C3=A9e?= Date: Wed, 22 Jun 2022 14:05:45 +0200 Subject: [PATCH 4/4] Added support for Ansible Collections --- jinja2cli/cli.py | 51 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/jinja2cli/cli.py b/jinja2cli/cli.py index 7aa2579..d161c46 100644 --- a/jinja2cli/cli.py +++ b/jinja2cli/cli.py @@ -220,25 +220,55 @@ def _parse_env(data): } -def _load_ansible_filters(): +def _ansible_filter_loader(base_module, module_path=None): from pkgutil import iter_modules from jinja2.utils import import_string + if module_path is not None: + try: + parent_module = import_string(f"{base_module.__name__}.{module_path}.plugins.filter") + except ImportError: + print(f"Could not find collection '{module_path}', did you mean 'ansible.{module_path}'?") + raise + else: + parent_module = import_string(f"{base_module.__name__}.plugins.filter") + + for module in iter_modules(parent_module.__path__): + filter_module = import_string(f"{parent_module.__name__}.{module.name}") + yield filter_module.FilterModule().filters() + + +def _load_ansible_filters(collections=None): try: - import ansible.plugins.filter + import ansible except ImportError: print("This feature requires the `ansible-core` package.") raise filters = dict() - for module in iter_modules(ansible.plugins.filter.__path__): - filter_module = import_string(f"ansible.plugins.filter.{module.name}") - filters.update(filter_module.FilterModule().filters()) + for filter in _ansible_filter_loader(ansible): + filters.update(filter) + + if collections is None: + return filters + + collections_paths = os.environ.get("ANSIBLE_COLLECTIONS") or \ + f"{os.path.expanduser('~/.ansible/collections')}:/usr/share/ansible/collections" + sys.path.extend(collections_paths.split(':')) + try: + import ansible_collections + except ImportError: + print("Could not find the `ansible_collections` module, please check the ANSIBLE_COLLECTIONS environment variable.") + raise + + for collection in collections: + for filter in _ansible_filter_loader(ansible_collections, collection): + filters.update(filter) return filters -def render(template_path, data, extensions, strict=False, ansible=False): +def render(template_path, data, extensions, strict=False, ansible=None): from jinja2 import ( __version__ as jinja_version, Environment, @@ -265,7 +295,9 @@ def render(template_path, data, extensions, strict=False, ansible=False): if strict: env.undefined = StrictUndefined if ansible: - env.filters.update(_load_ansible_filters()) + collections = set(ansible) + collections.discard('core') # only used because optparse doesn't support 0+ values + env.filters.update(_load_ansible_filters(collections)) # Add environ global env.globals["environ"] = lambda key: force_text(os.environ.get(key)) @@ -445,9 +477,10 @@ def main(): parser.add_option( "-a", "--ansible", - help="Enable support for Ansible filters", + help="Enable support for Ansible filters (--ansible core or --ansible )", dest="ansible", - action="store_true", + action="append", + default=[], ) parser.add_option( "-o",