From fdb572d0ba09397c13d864adf549699160e804c2 Mon Sep 17 00:00:00 2001 From: Dmitriy Rabotyagov Date: Fri, 28 Apr 2023 20:25:57 +0200 Subject: [PATCH] Add support for start/end_string arguments In case template contains some Jinja content that we don't want to resolve, there're not much options due to the bug, that ignores {% raw %} tags. The other way to work it around is to override tags that Jinja will use to detect the content it needs to render. This way content that should be renderred can be placed in the same template with the one that needs to stay intact. Change-Id: I4d28f5ab03858f5555f5e9e555da1dddc0f2b016 Related-Bug: #1649381 --- plugins/action/config_template.py | 44 +++++++++++++------ ...t_end_string_support-420acb44b5feeb5c.yaml | 10 +++++ tests/files/test_jinja_variables.yml.expected | 4 ++ tests/templates/test_jinja_variables.yml | 5 +++ tests/test-yaml.yml | 25 +++++++++++ tests/test.yml | 8 ++++ 6 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/start_end_string_support-420acb44b5feeb5c.yaml create mode 100644 tests/files/test_jinja_variables.yml.expected create mode 100644 tests/templates/test_jinja_variables.yml diff --git a/plugins/action/config_template.py b/plugins/action/config_template.py index 51fa85f..44e7a63 100644 --- a/plugins/action/config_template.py +++ b/plugins/action/config_template.py @@ -737,6 +737,10 @@ class ActionModule(ActionBase): remote_src = self._task.args.get('remote_src', False) yml_multilines = self._task.args.get('yml_multilines', False) + block_end_string = self._task.args.get('block_end_string', '%}') + block_start_string = self._task.args.get('block_start_string', '{%') + variable_end_string = self._task.args.get('variable_end_string', '}}') + variable_start_string = self._task.args.get('variable_start_string', '{{') return True, dict( source=source, @@ -748,7 +752,11 @@ class ActionModule(ActionBase): ignore_none_type=ignore_none_type, default_section=default_section, yml_multilines=yml_multilines, - remote_src=remote_src + remote_src=remote_src, + block_end_string=block_end_string, + block_start_string=block_start_string, + variable_end_string=variable_end_string, + variable_start_string=variable_start_string ) def resultant_ini_as_dict(self, resultant_dict, return_dict=None): @@ -766,14 +774,22 @@ class ActionModule(ActionBase): return return_dict - def _check_templar(self, data): + def _check_templar(self, data, extra): if boolean(self._task.args.get('render_template', True)): - return self._templar.template( - data, - preserve_trailing_newlines=True, - escape_backslashes=False, - convert_data=False - ) + templar = self._templar + with templar.set_temporary_context( + variable_start_string=extra['variable_start_string'], + variable_end_string=extra['variable_end_string'], + block_start_string=extra['block_start_string'], + block_end_string=extra['block_end_string'], + searchpath=extra['searchpath'] + ): + return templar.template( + data, + preserve_trailing_newlines=True, + escape_backslashes=False, + convert_data=False + ) else: return data @@ -862,14 +878,12 @@ class ActionModule(ActionBase): template_data_slurpee['content'] ).decode('utf-8') - self._templar.environment.loader.searchpath = _vars['searchpath'] - if LooseVersion(__ansible_version__) < LooseVersion("2.9"): self._templar.set_available_variables(temp_vars) else: self._templar.available_variables = temp_vars - resultant = self._check_templar(data=template_data) + resultant = self._check_templar(data=template_data, extra=_vars) if LooseVersion(__ansible_version__) < LooseVersion("2.9"): # Access to protected method is unavoidable in Ansible @@ -898,7 +912,7 @@ class ActionModule(ActionBase): if 'content' in slurpee: dest_data = base64.b64decode( slurpee['content']).decode('utf-8') - resultant_dest = self._check_templar(data=dest_data) + resultant_dest = self._check_templar(data=dest_data, extra=_vars) type_merger = getattr(self, CONFIG_TYPES.get(_vars['config_type'])) _, config_new = type_merger( @@ -941,7 +955,7 @@ class ActionModule(ActionBase): # Re-template the resultant object as it may have new data within it # as provided by an override variable. - resultant = self._check_templar(data=resultant) + resultant = self._check_templar(data=resultant, extra=_vars) # run the copy module new_module_args = self._task.args.copy() @@ -976,6 +990,10 @@ class ActionModule(ActionBase): new_module_args.pop('ignore_none_type', None) new_module_args.pop('default_section', None) new_module_args.pop('yml_multilines', None) + new_module_args.pop('block_end_string', None) + new_module_args.pop('block_start_string', None) + new_module_args.pop('variable_end_string', None) + new_module_args.pop('variable_start_string', None) # While this is in the copy module we dont want to use it. new_module_args.pop('remote_src', None) diff --git a/releasenotes/notes/start_end_string_support-420acb44b5feeb5c.yaml b/releasenotes/notes/start_end_string_support-420acb44b5feeb5c.yaml new file mode 100644 index 0000000..f88b1a0 --- /dev/null +++ b/releasenotes/notes/start_end_string_support-420acb44b5feeb5c.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added support for arguments that can be used to avoid templating Jinja + content: + + * variable_start_string + * variable_end_string + * block_start_string + * block_end_string diff --git a/tests/files/test_jinja_variables.yml.expected b/tests/files/test_jinja_variables.yml.expected new file mode 100644 index 0000000..6ee9167 --- /dev/null +++ b/tests/files/test_jinja_variables.yml.expected @@ -0,0 +1,4 @@ +section1: + alfa: '{{ alfa_hotel }}' + baz: baz + tango: tango diff --git a/tests/templates/test_jinja_variables.yml b/tests/templates/test_jinja_variables.yml new file mode 100644 index 0000000..d97d0d8 --- /dev/null +++ b/tests/templates/test_jinja_variables.yml @@ -0,0 +1,5 @@ +--- + +section1: + alfa: "{{ alfa_hotel }}" + tango: "tango" diff --git a/tests/test-yaml.yml b/tests/test-yaml.yml index bceee14..d4a1609 100644 --- a/tests/test-yaml.yml +++ b/tests/test-yaml.yml @@ -173,3 +173,28 @@ assert: that: - "(test_list_only_replace_file_expected.content | b64decode) == (test_list_only_replace_file.content | b64decode)" + +- name: Test template with jinja vars in it + config_template: + src: "{{ playbook_dir }}/templates/test_jinja_variables.yml" + dest: "/tmp/test_jinja_variables.yml" + config_overrides: "{{ test_jinja_variables }}" + config_type: yaml + variable_end_string: "%]" + variable_start_string: "[%" + +- name: Read test_jinja_variables.yml + slurp: + src: /tmp/test_jinja_variables.yml + register: test_jinja_variables + +- debug: + msg: "Jinja strings - {{ test_jinja_variables.content | b64decode }}" + +- debug: + msg: "Jinja strings Expected - {{ test_jinja_variables_expected.content | b64decode }}" + +- name: Compare files + assert: + that: + - "(test_jinja_variables_expected.content | b64decode) == (test_jinja_variables.content | b64decode)" diff --git a/tests/test.yml b/tests/test.yml index d36d5d5..e960292 100644 --- a/tests/test.yml +++ b/tests/test.yml @@ -73,6 +73,11 @@ src: "{{ playbook_dir }}/files/test_list_only_replace.yml.expected" register: test_list_only_replace_file_expected + - name: Read expected test_jinja_variables.yml + slurp: + src: "{{ playbook_dir }}/files/test_jinja_variables.yml.expected" + register: test_jinja_variables_expected + - import_tasks: test-common-tasks.yml handlers: @@ -168,6 +173,9 @@ baz: "hotel" section3: alfa: "bravo" + test_jinja_variables: + section1: + baz: "baz" test_enhanced_comments_ini_overrides: DEFAULT: default_availability_zone: zone1