diff options
Diffstat (limited to 'test/integration/targets/template/tasks/main.yml')
-rw-r--r-- | test/integration/targets/template/tasks/main.yml | 811 |
1 files changed, 811 insertions, 0 deletions
diff --git a/test/integration/targets/template/tasks/main.yml b/test/integration/targets/template/tasks/main.yml new file mode 100644 index 0000000..c0d2e11 --- /dev/null +++ b/test/integration/targets/template/tasks/main.yml @@ -0,0 +1,811 @@ +# test code for the template module +# (c) 2014, Michael DeHaan <michael.dehaan@gmail.com> + +# 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 <http://www.gnu.org/licenses/>. + +- 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: 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 + +- template: + src: empty_template.j2 + dest: "{{ output_dir }}/empty_template.templated" + +- assert: + that: + - test + vars: + test: "{{ lookup('file', '{{ output_dir }}/empty_template.templated')|length == 0 }}" + +- name: test jinja2 override without colon throws proper error + block: + - template: + src: override_separator.j2 + dest: "{{ output_dir }}/override_separator.templated" + - assert: + that: + - False + rescue: + - assert: + that: + - "'failed to parse jinja2 override' in ansible_failed_result.msg" + +- name: test jinja2 override with colon in value + template: + src: override_colon_value.j2 + dest: "{{ output_dir }}/override_colon_value.templated" + ignore_errors: yes + register: override_colon_value_task + +- copy: + src: override_colon_value.expected + dest: "{{output_dir}}/override_colon_value.expected" + +- command: "diff {{ output_dir }}/override_colon_value.templated {{ output_dir}}/override_colon_value.expected" + register: override_colon_value_diff + +- assert: + that: + - override_colon_value_task is success + - override_colon_value_diff.rc == 0 + +- assert: + that: + - data_not_converted | type_debug == 'NativeJinjaUnsafeText' + - data_converted | type_debug == 'dict' + vars: + data_not_converted: "{{ lookup('template', 'json_macro.j2', convert_data=False) }}" + data_converted: "{{ lookup('template', 'json_macro.j2') }}" + +- name: Test convert_data is correctly set to True for nested vars evaluation + debug: + msg: "{{ lookup('template', 'indirect_dict.j2', convert_data=False) }}" + vars: + d: + foo: bar + v: "{{ d }}" |