# test code for the template module # (c) 2014, Michael DeHaan # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . - set_fact: output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}" - name: show python interpreter debug: msg: "{{ ansible_python['executable'] }}" - name: show jinja2 version debug: msg: "{{ lookup('pipe', '{{ ansible_python[\"executable\"] }} -c \"import jinja2; print(jinja2.__version__)\"') }}" - name: get default group shell: id -gn register: group - name: fill in a basic template template: src=foo.j2 dest={{output_dir}}/foo.templated mode=0644 register: template_result - assert: that: - "'changed' in template_result" - "'dest' in template_result" - "'group' in template_result" - "'gid' in template_result" - "'md5sum' in template_result" - "'checksum' in template_result" - "'owner' in template_result" - "'size' in template_result" - "'src' in template_result" - "'state' in template_result" - "'uid' in template_result" - name: verify that the file was marked as changed assert: that: - "template_result.changed == true" # Basic template with non-ascii names - name: Check that non-ascii source and dest work template: src: 'café.j2' dest: '{{ output_dir }}/café.txt' register: template_results - name: Check that the resulting file exists stat: path: '{{ output_dir }}/café.txt' register: stat_results - name: Check that template created the right file assert: that: - 'template_results is changed' - 'stat_results.stat["exists"]' # test for import with context on jinja-2.9 See https://github.com/ansible/ansible/issues/20494 - name: fill in a template using import with context ala issue 20494 template: src=import_with_context.j2 dest={{output_dir}}/import_with_context.templated mode=0644 register: template_result - name: copy known good import_with_context.expected into place copy: src=import_with_context.expected dest={{output_dir}}/import_with_context.expected - name: compare templated file to known good import_with_context shell: diff -uw {{output_dir}}/import_with_context.templated {{output_dir}}/import_with_context.expected register: diff_result - name: verify templated import_with_context matches known good assert: that: - 'diff_result.stdout == ""' - "diff_result.rc == 0" # test for nested include https://github.com/ansible/ansible/issues/34886 - name: test if parent variables are defined in nested include template: src=for_loop.j2 dest={{output_dir}}/for_loop.templated mode=0644 - name: save templated output shell: "cat {{output_dir}}/for_loop.templated" register: for_loop_out - debug: var=for_loop_out - name: verify variables got templated assert: that: - '"foo" in for_loop_out.stdout' - '"bar" in for_loop_out.stdout' - '"bam" in for_loop_out.stdout' # test for 'import as' on jinja-2.9 See https://github.com/ansible/ansible/issues/20494 - name: fill in a template using import as ala fails2 case in issue 20494 template: src=import_as.j2 dest={{output_dir}}/import_as.templated mode=0644 register: import_as_template_result - name: copy known good import_as.expected into place copy: src=import_as.expected dest={{output_dir}}/import_as.expected - name: compare templated file to known good import_as shell: diff -uw {{output_dir}}/import_as.templated {{output_dir}}/import_as.expected register: import_as_diff_result - name: verify templated import_as matches known good assert: that: - 'import_as_diff_result.stdout == ""' - "import_as_diff_result.rc == 0" # test for 'import as with context' on jinja-2.9 See https://github.com/ansible/ansible/issues/20494 - name: fill in a template using import as with context ala fails2 case in issue 20494 template: src=import_as_with_context.j2 dest={{output_dir}}/import_as_with_context.templated mode=0644 register: import_as_with_context_template_result - name: copy known good import_as_with_context.expected into place copy: src=import_as_with_context.expected dest={{output_dir}}/import_as_with_context.expected - name: compare templated file to known good import_as_with_context shell: diff -uw {{output_dir}}/import_as_with_context.templated {{output_dir}}/import_as_with_context.expected register: import_as_with_context_diff_result - name: verify templated import_as_with_context matches known good assert: that: - 'import_as_with_context_diff_result.stdout == ""' - "import_as_with_context_diff_result.rc == 0" # VERIFY comment_start_string and comment_end_string - name: Render a template with "comment_start_string" set to [# template: src: custom_comment_string.j2 dest: "{{output_dir}}/custom_comment_string.templated" comment_start_string: "[#" comment_end_string: "#]" register: custom_comment_string_result - name: Get checksum of known good custom_comment_string.expected stat: path: "{{role_path}}/files/custom_comment_string.expected" register: custom_comment_string_good - name: Verify templated custom_comment_string matches known good using checksum assert: that: - "custom_comment_string_result.checksum == custom_comment_string_good.stat.checksum" # VERIFY trim_blocks - name: Render a template with "trim_blocks" set to False template: src: trim_blocks.j2 dest: "{{output_dir}}/trim_blocks_false.templated" trim_blocks: False register: trim_blocks_false_result - name: Get checksum of known good trim_blocks_false.expected stat: path: "{{role_path}}/files/trim_blocks_false.expected" register: trim_blocks_false_good - name: Verify templated trim_blocks_false matches known good using checksum assert: that: - "trim_blocks_false_result.checksum == trim_blocks_false_good.stat.checksum" - name: Render a template with "trim_blocks" set to True template: src: trim_blocks.j2 dest: "{{output_dir}}/trim_blocks_true.templated" trim_blocks: True register: trim_blocks_true_result - name: Get checksum of known good trim_blocks_true.expected stat: path: "{{role_path}}/files/trim_blocks_true.expected" register: trim_blocks_true_good - name: Verify templated trim_blocks_true matches known good using checksum assert: that: - "trim_blocks_true_result.checksum == trim_blocks_true_good.stat.checksum" # VERIFY lstrip_blocks - name: Render a template with "lstrip_blocks" set to False template: src: lstrip_blocks.j2 dest: "{{output_dir}}/lstrip_blocks_false.templated" lstrip_blocks: False register: lstrip_blocks_false_result - name: Get checksum of known good lstrip_blocks_false.expected stat: path: "{{role_path}}/files/lstrip_blocks_false.expected" register: lstrip_blocks_false_good - name: Verify templated lstrip_blocks_false matches known good using checksum assert: that: - "lstrip_blocks_false_result.checksum == lstrip_blocks_false_good.stat.checksum" - name: Render a template with "lstrip_blocks" set to True template: src: lstrip_blocks.j2 dest: "{{output_dir}}/lstrip_blocks_true.templated" lstrip_blocks: True register: lstrip_blocks_true_result ignore_errors: True - name: Get checksum of known good lstrip_blocks_true.expected stat: path: "{{role_path}}/files/lstrip_blocks_true.expected" register: lstrip_blocks_true_good - name: Verify templated lstrip_blocks_true matches known good using checksum assert: that: - "lstrip_blocks_true_result.checksum == lstrip_blocks_true_good.stat.checksum" # VERIFY CONTENTS - name: check what python version ansible is running on command: "{{ ansible_python.executable }} -c 'import distutils.sysconfig ; print(distutils.sysconfig.get_python_version())'" register: pyver delegate_to: localhost - name: copy known good into place copy: src=foo.txt dest={{output_dir}}/foo.txt - name: compare templated file to known good shell: diff -uw {{output_dir}}/foo.templated {{output_dir}}/foo.txt register: diff_result - name: verify templated file matches known good assert: that: - 'diff_result.stdout == ""' - "diff_result.rc == 0" # VERIFY MODE - name: set file mode file: path={{output_dir}}/foo.templated mode=0644 register: file_result - name: ensure file mode did not change assert: that: - "file_result.changed != True" # VERIFY dest as a directory does not break file attributes # Note: expanduser is needed to go down the particular codepath that was broken before - name: setup directory for test file: state=directory dest={{output_dir | expanduser}}/template-dir mode=0755 owner=nobody group={{ group.stdout }} - name: set file mode when the destination is a directory template: src=foo.j2 dest={{output_dir | expanduser}}/template-dir/ mode=0600 owner=root group={{ group.stdout }} - name: set file mode when the destination is a directory template: src=foo.j2 dest={{output_dir | expanduser}}/template-dir/ mode=0600 owner=root group={{ group.stdout }} register: file_result - name: check that the file has the correct attributes stat: path={{output_dir | expanduser}}/template-dir/foo.j2 register: file_attrs - assert: that: - "file_attrs.stat.uid == 0" - "file_attrs.stat.pw_name == 'root'" - "file_attrs.stat.mode == '0600'" - name: check that the containing directory did not change attributes stat: path={{output_dir | expanduser}}/template-dir/ register: dir_attrs - assert: that: - "dir_attrs.stat.uid != 0" - "dir_attrs.stat.pw_name == 'nobody'" - "dir_attrs.stat.mode == '0755'" - name: Check that template to a directory where the directory does not end with a / is allowed template: src=foo.j2 dest={{output_dir | expanduser}}/template-dir mode=0600 owner=root group={{ group.stdout }} - name: make a symlink to the templated file file: path: '{{ output_dir }}/foo.symlink' src: '{{ output_dir }}/foo.templated' state: link - name: check that templating the symlink results in the file being templated template: src: foo.j2 dest: '{{output_dir}}/foo.symlink' mode: 0600 follow: True register: template_result - assert: that: - "template_result.changed == True" - name: check that the file has the correct attributes stat: path={{output_dir | expanduser}}/template-dir/foo.j2 register: file_attrs - assert: that: - "file_attrs.stat.mode == '0600'" - name: check that templating the symlink again makes no changes template: src: foo.j2 dest: '{{output_dir}}/foo.symlink' mode: 0600 follow: True register: template_result - assert: that: - "template_result.changed == False" # Test strange filenames - name: Create a temp dir for filename tests file: state: directory dest: '{{ output_dir }}/filename-tests' - name: create a file with an unusual filename template: src: foo.j2 dest: "{{ output_dir }}/filename-tests/foo t'e~m\\plated" register: template_result - assert: that: - "template_result.changed == True" - name: check that the unusual filename was created command: "ls {{ output_dir }}/filename-tests/" register: unusual_results - assert: that: - "\"foo t'e~m\\plated\" in unusual_results.stdout_lines" - "{{unusual_results.stdout_lines| length}} == 1" - name: check that the unusual filename can be checked for changes template: src: foo.j2 dest: "{{ output_dir }}/filename-tests/foo t'e~m\\plated" register: template_result - assert: that: - "template_result.changed == False" # check_mode - name: fill in a basic template in check mode template: src=short.j2 dest={{output_dir}}/short.templated register: template_result check_mode: True - name: check file exists stat: path={{output_dir}}/short.templated register: templated - name: verify that the file was marked as changed in check mode but was not created assert: that: - "not templated.stat.exists" - "template_result is changed" - name: fill in a basic template template: src=short.j2 dest={{output_dir}}/short.templated - name: fill in a basic template in check mode template: src=short.j2 dest={{output_dir}}/short.templated register: template_result check_mode: True - name: verify that the file was marked as not changes in check mode assert: that: - "template_result is not changed" - "'templated_var_loaded' in lookup('file', output_dir + '/short.templated')" - name: change var for the template set_fact: templated_var: "changed" - name: fill in a basic template with changed var in check mode template: src=short.j2 dest={{output_dir}}/short.templated register: template_result check_mode: True - name: verify that the file was marked as changed in check mode but the content was not changed assert: that: - "'templated_var_loaded' in lookup('file', output_dir + '/short.templated')" - "template_result is changed" # Create a template using a child template, to ensure that variables # are passed properly from the parent to subtemplate context (issue #20063) - name: test parent and subtemplate creation of context template: src=parent.j2 dest={{output_dir}}/parent_and_subtemplate.templated register: template_result - stat: path={{output_dir}}/parent_and_subtemplate.templated - name: verify that the parent and subtemplate creation worked assert: that: - "template_result is changed" # # template module can overwrite a file that's been hard linked # https://github.com/ansible/ansible/issues/10834 # - name: ensure test dir is absent file: path: '{{ output_dir | expanduser }}/hlink_dir' state: absent - name: create test dir file: path: '{{ output_dir | expanduser }}/hlink_dir' state: directory - name: template out test file to system 1 template: src: foo.j2 dest: '{{ output_dir | expanduser }}/hlink_dir/test_file' - name: make hard link file: src: '{{ output_dir | expanduser }}/hlink_dir/test_file' dest: '{{ output_dir | expanduser }}/hlink_dir/test_file_hlink' state: hard - name: template out test file to system 2 template: src: foo.j2 dest: '{{ output_dir | expanduser }}/hlink_dir/test_file' register: hlink_result - name: check that the files are still hardlinked stat: path: '{{ output_dir | expanduser }}/hlink_dir/test_file' register: orig_file - name: check that the files are still hardlinked stat: path: '{{ output_dir | expanduser }}/hlink_dir/test_file_hlink' register: hlink_file # We've done nothing at this point to update the content of the file so it should still be hardlinked - assert: that: - "hlink_result.changed == False" - "orig_file.stat.inode == hlink_file.stat.inode" - name: change var for the template set_fact: templated_var: "templated_var_loaded" # UNIX TEMPLATE - name: fill in a basic template (Unix) template: src: foo2.j2 dest: '{{ output_dir }}/foo.unix.templated' register: template_result - name: verify that the file was marked as changed (Unix) assert: that: - 'template_result is changed' - name: fill in a basic template again (Unix) template: src: foo2.j2 dest: '{{ output_dir }}/foo.unix.templated' register: template_result2 - name: verify that the template was not changed (Unix) assert: that: - 'template_result2 is not changed' # VERIFY UNIX CONTENTS - name: copy known good into place (Unix) copy: src: foo.unix.txt dest: '{{ output_dir }}/foo.unix.txt' - name: Dump templated file (Unix) command: hexdump -C {{ output_dir }}/foo.unix.templated - name: Dump expected file (Unix) command: hexdump -C {{ output_dir }}/foo.unix.txt - name: compare templated file to known good (Unix) command: diff -u {{ output_dir }}/foo.unix.templated {{ output_dir }}/foo.unix.txt register: diff_result - name: verify templated file matches known good (Unix) assert: that: - 'diff_result.stdout == ""' - "diff_result.rc == 0" # DOS TEMPLATE - name: fill in a basic template (DOS) template: src: foo2.j2 dest: '{{ output_dir }}/foo.dos.templated' newline_sequence: '\r\n' register: template_result - name: verify that the file was marked as changed (DOS) assert: that: - 'template_result is changed' - name: fill in a basic template again (DOS) template: src: foo2.j2 dest: '{{ output_dir }}/foo.dos.templated' newline_sequence: '\r\n' register: template_result2 - name: verify that the template was not changed (DOS) assert: that: - 'template_result2 is not changed' # VERIFY DOS CONTENTS - name: copy known good into place (DOS) copy: src: foo.dos.txt dest: '{{ output_dir }}/foo.dos.txt' - name: Dump templated file (DOS) command: hexdump -C {{ output_dir }}/foo.dos.templated - name: Dump expected file (DOS) command: hexdump -C {{ output_dir }}/foo.dos.txt - name: compare templated file to known good (DOS) command: diff -u {{ output_dir }}/foo.dos.templated {{ output_dir }}/foo.dos.txt register: diff_result - name: verify templated file matches known good (DOS) assert: that: - 'diff_result.stdout == ""' - "diff_result.rc == 0" # VERIFY DOS CONTENTS - name: copy known good into place (Unix) copy: src: foo.unix.txt dest: '{{ output_dir }}/foo.unix.txt' - name: Dump templated file (Unix) command: hexdump -C {{ output_dir }}/foo.unix.templated - name: Dump expected file (Unix) command: hexdump -C {{ output_dir }}/foo.unix.txt - name: compare templated file to known good (Unix) command: diff -u {{ output_dir }}/foo.unix.templated {{ output_dir }}/foo.unix.txt register: diff_result - name: verify templated file matches known good (Unix) assert: that: - 'diff_result.stdout == ""' - "diff_result.rc == 0" # Check that mode=preserve works with template - name: Create a template which has strange permissions copy: content: !unsafe '{{ ansible_managed }}\n' dest: '{{ output_dir }}/foo-template.j2' mode: 0547 delegate_to: localhost - name: Use template with mode=preserve template: src: '{{ output_dir }}/foo-template.j2' dest: '{{ output_dir }}/foo-templated.txt' mode: 'preserve' register: template_results - name: Get permissions from the templated file stat: path: '{{ output_dir }}/foo-templated.txt' register: stat_results - name: Check that the resulting file has the correct permissions assert: that: - 'template_results is changed' - 'template_results.mode == "0547"' - 'stat_results.stat["mode"] == "0547"' # Test output_encoding - name: Prepare the list of encodings we want to check, including empty string for defaults set_fact: template_encoding_1252_encodings: ['', 'utf-8', 'windows-1252'] - name: Copy known good encoding_1252_*.expected into place copy: src: 'encoding_1252_{{ item | default("utf-8", true) }}.expected' dest: '{{ output_dir }}/encoding_1252_{{ item }}.expected' loop: '{{ template_encoding_1252_encodings }}' - name: Generate the encoding_1252_* files from templates using various encoding combinations template: src: 'encoding_1252.j2' dest: '{{ output_dir }}/encoding_1252_{{ item }}.txt' output_encoding: '{{ item }}' loop: '{{ template_encoding_1252_encodings }}' - name: Compare the encoding_1252_* templated files to known good command: diff -u {{ output_dir }}/encoding_1252_{{ item }}.expected {{ output_dir }}/encoding_1252_{{ item }}.txt register: encoding_1252_diff_result loop: '{{ template_encoding_1252_encodings }}' - name: Check that nested undefined values return Undefined vars: dict_var: bar: {} list_var: - foo: {} assert: that: - dict_var is defined - dict_var.bar is defined - dict_var.bar.baz is not defined - dict_var.bar.baz | default('DEFAULT') == 'DEFAULT' - dict_var.bar.baz.abc is not defined - dict_var.bar.baz.abc | default('DEFAULT') == 'DEFAULT' - dict_var.baz is not defined - dict_var.baz.abc is not defined - dict_var.baz.abc | default('DEFAULT') == 'DEFAULT' - list_var.0 is defined - list_var.1 is not defined - list_var.0.foo is defined - list_var.0.foo.bar is not defined - list_var.0.foo.bar | default('DEFAULT') == 'DEFAULT' - list_var.1.foo is not defined - list_var.1.foo | default('DEFAULT') == 'DEFAULT' - dict_var is defined - dict_var['bar'] is defined - dict_var['bar']['baz'] is not defined - dict_var['bar']['baz'] | default('DEFAULT') == 'DEFAULT' - dict_var['bar']['baz']['abc'] is not defined - dict_var['bar']['baz']['abc'] | default('DEFAULT') == 'DEFAULT' - dict_var['baz'] is not defined - dict_var['baz']['abc'] is not defined - dict_var['baz']['abc'] | default('DEFAULT') == 'DEFAULT' - list_var[0] is defined - list_var[1] is not defined - list_var[0]['foo'] is defined - list_var[0]['foo']['bar'] is not defined - list_var[0]['foo']['bar'] | default('DEFAULT') == 'DEFAULT' - list_var[1]['foo'] is not defined - list_var[1]['foo'] | default('DEFAULT') == 'DEFAULT' - dict_var['bar'].baz is not defined - dict_var['bar'].baz | default('DEFAULT') == 'DEFAULT' - template: src: template_destpath_test.j2 dest: "{{ output_dir }}/template_destpath.templated" - copy: content: "{{ output_dir}}/template_destpath.templated\n" dest: "{{ output_dir }}/template_destpath.expected" - name: compare templated file to known good template_destpath shell: diff -uw {{output_dir}}/template_destpath.templated {{output_dir}}/template_destpath.expected register: diff_result - name: verify templated template_destpath matches known good assert: that: - 'diff_result.stdout == ""' - "diff_result.rc == 0" - debug: msg: "{{ 'x' in y }}" ignore_errors: yes register: error - name: check that proper error message is emitted when in operator is used assert: that: "\"'y' is undefined\" in error.msg" - template: src: template_import_macro_globals.j2 dest: "{{ output_dir }}/template_import_macro_globals.templated" - command: "cat {{ output_dir }}/template_import_macro_globals.templated" register: out - assert: that: - out.stdout == "bar=lookedup_bar" # aliases file requires root for template tests so this should be safe - import_tasks: backup_test.yml - name: test STRING_TYPE_FILTERS copy: content: "{{ a_dict | to_nice_json(indent=(indent_value|int))}}\n" dest: "{{ output_dir }}/string_type_filters.templated" vars: a_dict: foo: bar foobar: 1 indent_value: 2 - name: copy known good string_type_filters.expected into place copy: src: string_type_filters.expected dest: "{{ output_dir }}/string_type_filters.expected" - command: "diff {{ output_dir }}/string_type_filters.templated {{ output_dir}}/string_type_filters.expected" register: out - assert: that: - out.rc == 0