diff options
Diffstat (limited to 'test/integration/targets/file/tasks')
-rw-r--r-- | test/integration/targets/file/tasks/diff_peek.yml | 10 | ||||
-rw-r--r-- | test/integration/targets/file/tasks/directory_as_dest.yml | 345 | ||||
-rw-r--r-- | test/integration/targets/file/tasks/initialize.yml | 15 | ||||
-rw-r--r-- | test/integration/targets/file/tasks/link_rewrite.yml | 47 | ||||
-rw-r--r-- | test/integration/targets/file/tasks/main.yml | 960 | ||||
-rw-r--r-- | test/integration/targets/file/tasks/modification_time.yml | 70 | ||||
-rw-r--r-- | test/integration/targets/file/tasks/selinux_tests.yml | 33 | ||||
-rw-r--r-- | test/integration/targets/file/tasks/state_link.yml | 501 | ||||
-rw-r--r-- | test/integration/targets/file/tasks/unicode_path.yml | 10 |
9 files changed, 1991 insertions, 0 deletions
diff --git a/test/integration/targets/file/tasks/diff_peek.yml b/test/integration/targets/file/tasks/diff_peek.yml new file mode 100644 index 0000000..802a99a --- /dev/null +++ b/test/integration/targets/file/tasks/diff_peek.yml @@ -0,0 +1,10 @@ +- name: Run task with _diff_peek + file: + path: "{{ output_file }}" + _diff_peek: yes + register: diff_peek_result + +- name: Ensure warning was not issued when using _diff_peek parameter + assert: + that: + - diff_peek_result['warnings'] is not defined diff --git a/test/integration/targets/file/tasks/directory_as_dest.yml b/test/integration/targets/file/tasks/directory_as_dest.yml new file mode 100644 index 0000000..161a12a --- /dev/null +++ b/test/integration/targets/file/tasks/directory_as_dest.yml @@ -0,0 +1,345 @@ +# File module tests for overwriting directories +- name: Initialize the test output dir + import_tasks: initialize.yml + +# We need to make this more consistent: +# https://github.com/ansible/proposals/issues/111 +# +# This series of tests document the current inconsistencies. We should not +# break these by accident but if we approve a proposal we can break these on +# purpose. + +# +# Setup +# + +- name: create a test sub-directory + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: directory + +- name: create a file for linking to + copy: + dest: '{{remote_tmp_dir_test}}/file_to_link' + content: 'Hello World' + +# +# Error condtion: specify a directory with state={link,file}, force=False +# + +# file raises an error +- name: Try to create a file with directory as dest + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: file + force: False + ignore_errors: True + register: file1_result + +- name: Get stat info to show the directory has not been changed to a file + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file1_dir_stat + +- name: verify that the directory was not overwritten + assert: + that: + - 'file1_result is failed' + - 'file1_dir_stat["stat"].isdir' + +# link raises an error +- name: Try to create a symlink with directory as dest + file: + src: '{{ remote_tmp_dir_test }}/file_to_link' + dest: '{{remote_tmp_dir_test}}/sub1' + state: link + force: False + ignore_errors: True + register: file2_result + +- name: Get stat info to show the directory has not been changed to a file + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file2_dir_stat + +- name: verify that the directory was not overwritten + assert: + that: + - 'file2_result is failed' + - 'file2_dir_stat["stat"].isdir' + +# +# Error condition: file and link with non-empty directory +# + +- copy: + content: 'test' + dest: '{{ remote_tmp_dir_test }}/sub1/passwd' + +# file raises an error +- name: Try to create a file with directory as dest + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: file + force: True + ignore_errors: True + register: file3_result + +- name: Get stat info to show the directory has not been changed to a file + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file3_dir_stat + +- name: verify that the directory was not overwritten + assert: + that: + - 'file3_result is failed' + - 'file3_dir_stat["stat"].isdir' + +# link raises an error +- name: Try to create a symlink with directory as dest + file: + src: '{{ remote_tmp_dir_test }}/file_to_link' + dest: '{{remote_tmp_dir_test}}/sub1' + state: link + force: True + ignore_errors: True + register: file4_result + +- name: Get stat info to show the directory has not been changed to a file + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file4_dir_stat + +- name: verify that the directory was not overwritten + assert: + that: + - 'file4_result is failed' + - 'file4_dir_stat["stat"].isdir' + +# Cleanup the file that made it non-empty +- name: Cleanup the file that made the directory nonempty + file: + state: 'absent' + dest: '{{ remote_tmp_dir_test }}/sub1/passwd' + +# +# Error condition: file cannot even overwrite an empty directory with force=True +# + +# file raises an error +- name: Try to create a file with directory as dest + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: file + force: True + ignore_errors: True + register: file5_result + +- name: Get stat info to show the directory has not been changed to a file + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file5_dir_stat + +- name: verify that the directory was not overwritten + assert: + that: + - 'file5_result is failed' + - 'file5_dir_stat["stat"].isdir' + +# +# Directory overwriting - link with force=True will overwrite an empty directory +# + +# link can overwrite an empty directory with force=True +- name: Try to create a symlink with directory as dest + file: + src: '{{ remote_tmp_dir_test }}/file_to_link' + dest: '{{remote_tmp_dir_test}}/sub1' + state: link + force: True + register: file6_result + +- name: Get stat info to show the directory has been overwritten + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file6_dir_stat + +- name: verify that the directory was overwritten + assert: + that: + - 'file6_result is changed' + - 'not file6_dir_stat["stat"].isdir' + - 'file6_dir_stat["stat"].islnk' + +# +# Cleanup from last set of tests +# + +- name: Cleanup the test subdirectory + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: 'absent' + +- name: Re-create the test sub-directory + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: 'directory' + +# +# Hard links have the proposed 111 behaviour already: Place the new file inside the directory +# + +- name: Try to create a hardlink with directory as dest + file: + src: '{{ remote_tmp_dir_test }}/file_to_link' + dest: '{{ remote_tmp_dir_test }}/sub1' + state: hard + force: False + ignore_errors: True + register: file7_result + +- name: Get stat info to show the directory has not been changed to a file + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file7_dir_stat + +- name: Get stat info to show the link has been created + stat: + path: '{{ remote_tmp_dir_test }}/sub1/file_to_link' + follow: False + register: file7_link_stat + +- debug: + var: file7_link_stat + +- name: verify that the directory was not overwritten + assert: + that: + - 'file7_result is changed' + - 'file7_dir_stat["stat"].isdir' + - 'file7_link_stat["stat"].isfile' + - 'file7_link_stat["stat"].isfile' + ignore_errors: True + +# +# Touch is a bit different than everything else. +# If we need to set timestamps we should probably add atime, mtime, and ctime parameters +# But I think touch was written because state=file didn't create a file if it +# didn't already exist. We should look at changing that behaviour. +# + +- name: Get initial stat info to compare with later + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file8_initial_dir_stat + +- name: Pause to ensure stat times are not the exact same + pause: + seconds: 1 + +- name: Use touch with directory as dest + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: touch + force: False + register: file8_result + +- name: Get stat info to show the directory has not been changed to a file + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file8_dir_stat + +- name: verify that the directory has been updated + assert: + that: + - 'file8_result is changed' + - 'file8_dir_stat["stat"].isdir' + - 'file8_dir_stat["stat"]["mtime"] != file8_initial_dir_stat["stat"]["mtime"]' + +- name: Get initial stat info to compare with later + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file11_initial_dir_stat + +- name: Use touch with directory as dest and keep mtime and atime + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: touch + force: False + modification_time: preserve + access_time: preserve + register: file11_result + +- name: Get stat info to show the directory has not been changed + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file11_dir_stat + +- name: verify that the directory has not been updated + assert: + that: + - 'file11_result is not changed' + - 'file11_dir_stat["stat"].isdir' + - 'file11_dir_stat["stat"]["mtime"] == file11_initial_dir_stat["stat"]["mtime"]' + - 'file11_dir_stat["stat"]["atime"] == file11_initial_dir_stat["stat"]["atime"]' + +# +# State=directory realizes that the directory already exists and does nothing +# +- name: Get initial stat info to compare with later + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file9_initial_dir_stat + +- name: Use directory with directory as dest + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: directory + force: False + register: file9_result + +- name: Get stat info to show the directory has not been changed + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file9_dir_stat + +- name: verify that the directory has been updated + assert: + that: + - 'file9_result is not changed' + - 'file9_dir_stat["stat"].isdir' + - 'file9_dir_stat["stat"]["mtime"] == file9_initial_dir_stat["stat"]["mtime"]' + +- name: Use directory with directory as dest and force=True + file: + dest: '{{remote_tmp_dir_test}}/sub1' + state: directory + force: True + register: file10_result + +- name: Get stat info to show the directory has not been changed + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file10_dir_stat + +- name: verify that the directory has been updated + assert: + that: + - 'file10_result is not changed' + - 'file10_dir_stat["stat"].isdir' + - 'file10_dir_stat["stat"]["mtime"] == file9_initial_dir_stat["stat"]["mtime"]' diff --git a/test/integration/targets/file/tasks/initialize.yml b/test/integration/targets/file/tasks/initialize.yml new file mode 100644 index 0000000..ad9f664 --- /dev/null +++ b/test/integration/targets/file/tasks/initialize.yml @@ -0,0 +1,15 @@ +# +# Cleanup the output dir and recreate it for the tests to operate on +# +- name: Cleanup the output directory + file: + dest: '{{ remote_tmp_dir_test }}' + state: 'absent' + +- name: Recreate the toplevel output dir + file: + dest: '{{ remote_tmp_dir_test }}' + state: 'directory' + +- name: prep with a basic file to operate on + copy: src=foo.txt dest={{output_file}} diff --git a/test/integration/targets/file/tasks/link_rewrite.yml b/test/integration/targets/file/tasks/link_rewrite.yml new file mode 100644 index 0000000..b0e1af3 --- /dev/null +++ b/test/integration/targets/file/tasks/link_rewrite.yml @@ -0,0 +1,47 @@ +- name: create temporary build directory + tempfile: + state: directory + suffix: ansible_test_leave_links_alone_during_touch + register: tempdir + +- name: create file + copy: + mode: 0600 + content: "chicken" + dest: "{{ tempdir.path }}/somefile" + +- name: Create relative link + file: + src: somefile + dest: "{{ tempdir.path }}/somelink" + state: link + +- stat: + path: "{{ tempdir.path }}/somelink" + register: link + +- stat: + path: "{{ tempdir.path }}/somefile" + register: file + +- assert: + that: + - "file.stat.mode == '0600'" + - "link.stat.lnk_target == 'somefile'" + +- file: + path: "{{ tempdir.path }}/somelink" + mode: 0644 + +- stat: + path: "{{ tempdir.path }}/somelink" + register: link + +- stat: + path: "{{ tempdir.path }}/somefile" + register: file + +- assert: + that: + - "file.stat.mode == '0644'" + - "link.stat.lnk_target == 'somefile'" diff --git a/test/integration/targets/file/tasks/main.yml b/test/integration/targets/file/tasks/main.yml new file mode 100644 index 0000000..17b0fae --- /dev/null +++ b/test/integration/targets/file/tasks/main.yml @@ -0,0 +1,960 @@ +# Test code for the file module. +# (c) 2014, Richard Isaacson <richard.c.isaacson@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: + remote_tmp_dir_test: '{{ remote_tmp_dir }}/file' + +- set_fact: + output_file: '{{remote_tmp_dir_test}}/foo.txt' + +# same as expanduser & expandvars called on managed host +- command: 'echo {{ output_file }}' + register: echo + +- set_fact: + remote_file_expanded: '{{ echo.stdout }}' + +# Import the test tasks +- name: Run tests for state=link + import_tasks: state_link.yml + +- name: Run tests for directory as dest + import_tasks: directory_as_dest.yml + +- name: Run tests for unicode + import_tasks: unicode_path.yml + environment: + LC_ALL: C + LANG: C + +- name: decide to include or not include selinux tests + include_tasks: selinux_tests.yml + when: selinux_installed is defined and selinux_installed.stdout != "" and selinux_enabled.stdout != "Disabled" + +- name: Initialize the test output dir + import_tasks: initialize.yml + +- name: Test _diff_peek + import_tasks: diff_peek.yml + +- name: Test modification time + import_tasks: modification_time.yml + +# These tests need to be organized by state parameter into separate files later + +- name: verify that we are checking a file and it is present + file: path={{output_file}} state=file + register: file_result + +- name: verify that the file was marked as changed + assert: + that: + - "file_result.changed == false" + - "file_result.state == 'file'" + +- name: Make sure file does not exist + file: + path: /tmp/ghost + state: absent + +- name: Target a file that does not exist + file: + path: /tmp/ghost + ignore_errors: yes + register: ghost_file_result + +- name: Validate ghost file results + assert: + that: + - ghost_file_result is failed + - ghost_file_result is not changed + - ghost_file_result.state == 'absent' + - "'cannot continue' in ghost_file_result.msg" + +- name: verify that we are checking an absent file + file: path={{remote_tmp_dir_test}}/bar.txt state=absent + register: file2_result + +- name: verify that the file was marked as changed + assert: + that: + - "file2_result.changed == false" + - "file2_result.state == 'absent'" + +- name: verify we can touch a file + file: + path: "{{remote_tmp_dir_test}}/baz.txt" + state: touch + mode: '0644' + register: file3_result + +- name: verify that the file was marked as changed + assert: + that: + - "file3_result.changed == true" + - "file3_result.state == 'file'" + - "file3_result.mode == '0644'" + +- name: change file mode + file: path={{remote_tmp_dir_test}}/baz.txt mode=0600 + register: file4_result + +- name: verify that the file was marked as changed + assert: + that: + - "file4_result.changed == true" + - "file4_result.mode == '0600'" + +- name: define file to verify chattr/lsattr with + set_fact: + attributes_file: "{{ remote_tmp_dir_test }}/attributes.txt" + attributes_supported: no + +- name: create file to verify chattr/lsattr with + command: touch "{{ attributes_file }}" + +- name: add "A" attribute to file + command: chattr +A "{{ attributes_file }}" + ignore_errors: yes + +- name: get attributes from file + command: lsattr -d "{{ attributes_file }}" + register: attribute_A_set + ignore_errors: yes + +- name: remove "A" attribute from file + command: chattr -A "{{ attributes_file }}" + ignore_errors: yes + +- name: get attributes from file + command: lsattr -d "{{ attributes_file }}" + register: attribute_A_unset + ignore_errors: yes + +- name: determine if chattr/lsattr is supported + set_fact: + attributes_supported: yes + when: + - attribute_A_set is success + - attribute_A_set.stdout_lines + - "'A' in attribute_A_set.stdout_lines[0].split()[0]" + - attribute_A_unset is success + - attribute_A_unset.stdout_lines + - "'A' not in attribute_A_unset.stdout_lines[0].split()[0]" + +- name: explicitly set file attribute "A" + file: path={{remote_tmp_dir_test}}/baz.txt attributes=A + register: file_attributes_result + ignore_errors: True + when: attributes_supported + +- name: add file attribute "A" + file: path={{remote_tmp_dir_test}}/baz.txt attributes=+A + register: file_attributes_result_2 + when: file_attributes_result is changed + +- name: verify that the file was not marked as changed + assert: + that: + - "file_attributes_result_2 is not changed" + when: file_attributes_result is changed + +- name: remove file attribute "A" + file: path={{remote_tmp_dir_test}}/baz.txt attributes=-A + register: file_attributes_result_3 + ignore_errors: True + +- name: explicitly remove file attributes + file: path={{remote_tmp_dir_test}}/baz.txt attributes="" + register: file_attributes_result_4 + when: file_attributes_result_3 is changed + +- name: verify that the file was not marked as changed + assert: + that: + - "file_attributes_result_4 is not changed" + when: file_attributes_result_4 is changed + +- name: create user + user: + name: test1 + uid: 1234 + notify: remove users + +- name: create group + group: + name: test1 + gid: 1234 + notify: remove groups + +- name: change ownership and group + file: path={{remote_tmp_dir_test}}/baz.txt owner=1234 group=1234 + +- name: Get stat info to check atime later + stat: path={{remote_tmp_dir_test}}/baz.txt + register: file_attributes_result_5_before + +- name: updates access time + file: path={{remote_tmp_dir_test}}/baz.txt access_time=now + register: file_attributes_result_5 + +- name: Get stat info to check atime later + stat: path={{remote_tmp_dir_test}}/baz.txt + register: file_attributes_result_5_after + +- name: verify that the file was marked as changed and atime changed + assert: + that: + - "file_attributes_result_5 is changed" + - "file_attributes_result_5_after['stat']['atime'] != file_attributes_result_5_before['stat']['atime']" + +- name: setup a tmp-like directory for ownership test + file: path=/tmp/worldwritable mode=1777 state=directory + +- name: Ask to create a file without enough perms to change ownership + file: path=/tmp/worldwritable/baz.txt state=touch owner=root + become: yes + become_user: nobody + register: chown_result + ignore_errors: True + +- name: Ask whether the new file exists + stat: path=/tmp/worldwritable/baz.txt + register: file_exists_result + +- name: Verify that the file doesn't exist on failure + assert: + that: + - "chown_result.failed == True" + - "file_exists_result.stat.exists == False" + +- name: clean up + file: path=/tmp/worldwritable state=absent + +- name: create hard link to file + file: src={{output_file}} dest={{remote_tmp_dir_test}}/hard.txt state=hard + register: file6_result + +- name: verify that the file was marked as changed + assert: + that: + - "file6_result.changed == true" + +- name: touch a hard link + file: + dest: '{{ remote_tmp_dir_test }}/hard.txt' + state: 'touch' + register: file6_touch_result + +- name: verify that the hard link was touched + assert: + that: + - "file6_touch_result.changed == true" + +- name: stat1 + stat: path={{output_file}} + register: hlstat1 + +- name: stat2 + stat: path={{remote_tmp_dir_test}}/hard.txt + register: hlstat2 + +- name: verify that hard link is still the same after timestamp updated + assert: + that: + - "hlstat1.stat.inode == hlstat2.stat.inode" + +- name: create hard link to file 2 + file: src={{output_file}} dest={{remote_tmp_dir_test}}/hard.txt state=hard + register: hlink_result + +- name: verify that hard link creation is idempotent + assert: + that: + - "hlink_result.changed == False" + +- name: Change mode on a hard link + file: dest={{remote_tmp_dir_test}}/hard.txt mode=0701 + register: file6_mode_change + +- name: verify that the hard link was touched + assert: + that: + - "file6_touch_result.changed == true" + +- name: stat1 + stat: path={{output_file}} + register: hlstat1 + +- name: stat2 + stat: path={{remote_tmp_dir_test}}/hard.txt + register: hlstat2 + +- name: verify that hard link is still the same after timestamp updated + assert: + that: + - "hlstat1.stat.inode == hlstat2.stat.inode" + - "hlstat1.stat.mode == '0701'" + +- name: create a directory + file: path={{remote_tmp_dir_test}}/foobar state=directory + register: file7_result + +- name: verify that the file was marked as changed + assert: + that: + - "file7_result.changed == true" + - "file7_result.state == 'directory'" + +- name: determine if selinux is installed + shell: which getenforce || exit 0 + register: selinux_installed + +- name: determine if selinux is enabled + shell: getenforce + register: selinux_enabled + when: selinux_installed.stdout != "" + ignore_errors: true + +- name: remove directory foobar + file: path={{remote_tmp_dir_test}}/foobar state=absent + +- name: remove file foo.txt + file: path={{remote_tmp_dir_test}}/foo.txt state=absent + +- name: remove file bar.txt + file: path={{remote_tmp_dir_test}}/foo.txt state=absent + +- name: remove file baz.txt + file: path={{remote_tmp_dir_test}}/foo.txt state=absent + +- name: copy directory structure over + copy: src=foobar dest={{remote_tmp_dir_test}} + +- name: check what would be removed if folder state was absent and diff is enabled + file: + path: "{{ item }}" + state: absent + check_mode: yes + diff: yes + with_items: + - "{{ remote_tmp_dir_test }}" + - "{{ remote_tmp_dir_test }}/foobar/fileA" + register: folder_absent_result + +- name: 'assert that the "absent" state lists expected files and folders for only directories' + assert: + that: + - folder_absent_result.results[0].diff.before.path_content is defined + - folder_absent_result.results[1].diff.before.path_content is not defined + - test_folder in folder_absent_result.results[0].diff.before.path_content.directories + - test_file in folder_absent_result.results[0].diff.before.path_content.files + vars: + test_folder: "{{ folder_absent_result.results[0].path }}/foobar" + test_file: "{{ folder_absent_result.results[0].path }}/foobar/fileA" + +- name: Change ownership of a directory with recurse=no(default) + file: path={{remote_tmp_dir_test}}/foobar owner=1234 + +- name: verify that the permission of the directory was set + file: path={{remote_tmp_dir_test}}/foobar state=directory + register: file8_result + +- name: assert that the directory has changed to have owner 1234 + assert: + that: + - "file8_result.uid == 1234" + +- name: verify that the permission of a file under the directory was not set + file: path={{remote_tmp_dir_test}}/foobar/fileA state=file + register: file9_result + +- name: assert the file owner has not changed to 1234 + assert: + that: + - "file9_result.uid != 1234" + +- name: create user + user: + name: test2 + uid: 1235 + +- name: change the ownership of a directory with recurse=yes + file: path={{remote_tmp_dir_test}}/foobar owner=1235 recurse=yes + +- name: verify that the permission of the directory was set + file: path={{remote_tmp_dir_test}}/foobar state=directory + register: file10_result + +- name: assert that the directory has changed to have owner 1235 + assert: + that: + - "file10_result.uid == 1235" + +- name: verify that the permission of a file under the directory was not set + file: path={{remote_tmp_dir_test}}/foobar/fileA state=file + register: file11_result + +- name: assert that the file has changed to have owner 1235 + assert: + that: + - "file11_result.uid == 1235" + +- name: remove directory foobar + file: path={{remote_tmp_dir_test}}/foobar state=absent + register: file14_result + +- name: verify that the directory was removed + assert: + that: + - 'file14_result.changed == true' + - 'file14_result.state == "absent"' + +- name: create a test sub-directory + file: dest={{remote_tmp_dir_test}}/sub1 state=directory + register: file15_result + +- name: verify that the new directory was created + assert: + that: + - 'file15_result.changed == true' + - 'file15_result.state == "directory"' + +- name: create test files in the sub-directory + file: dest={{remote_tmp_dir_test}}/sub1/{{item}} state=touch + with_items: + - file1 + - file2 + - file3 + register: file16_result + +- name: verify the files were created + assert: + that: + - 'item.changed == true' + - 'item.state == "file"' + with_items: "{{file16_result.results}}" + +- name: test file creation with symbolic mode + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=u=rwx,g=rwx,o=rwx + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0777' + +- name: modify symbolic mode for all + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=a=r + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0444' + +- name: modify symbolic mode for owner + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=u+w + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0644' + +- name: modify symbolic mode for group + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=g+w + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0664' + +- name: modify symbolic mode for world + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=o+w + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0666' + +- name: modify symbolic mode for owner + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=u+x + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0766' + +- name: modify symbolic mode for group + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=g+x + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0776' + +- name: modify symbolic mode for world + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=o+x + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0777' + +- name: remove symbolic mode for world + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=o-wx + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0774' + +- name: remove symbolic mode for group + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=g-wx + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0744' + +- name: remove symbolic mode for owner + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=u-wx + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0444' + +- name: set sticky bit with symbolic mode + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=o+t + register: result + +- name: assert file mode + assert: + that: + - result.mode == '01444' + +- name: remove sticky bit with symbolic mode + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=o-t + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0444' + +- name: add setgid with symbolic mode + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=g+s + register: result + +- name: assert file mode + assert: + that: + - result.mode == '02444' + +- name: remove setgid with symbolic mode + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=g-s + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0444' + +- name: add setuid with symbolic mode + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=u+s + register: result + +- name: assert file mode + assert: + that: + - result.mode == '04444' + +- name: remove setuid with symbolic mode + file: dest={{remote_tmp_dir_test}}/test_symbolic state=touch mode=u-s + register: result + +- name: assert file mode + assert: + that: + - result.mode == '0444' + +# https://github.com/ansible/ansible/issues/67307 +# Test the module fails in check_mode when directory and owner/group do not exist +# I don't use state=touch here intentionally to fail and catch warnings +- name: owner does not exist in check_mode + file: + path: '/tmp/nonexistent' + owner: nonexistent + check_mode: yes + register: owner_no_exist + ignore_errors: yes + +- name: create owner + user: + name: nonexistent + notify: remove users + +# I don't use state=touch here intentionally to fail and catch warnings +- name: owner exist in check_mode + file: + path: '/tmp/nonexistent' + owner: nonexistent + check_mode: yes + register: owner_exists + ignore_errors: yes + +# I don't use state=touch here intentionally to fail and catch warnings +- name: owner does not exist in check_mode, using uid + file: + path: '/tmp/nonexistent' + owner: '111111' + check_mode: yes + ignore_errors: yes + register: owner_uid_no_exist + +- name: create owner using uid + user: + name: test_uid + uid: 111111 + notify: remove users + +# I don't use state=touch here intentionally to fail and catch warnings +- name: owner exists in check_mode, using uid + file: + path: '/tmp/nonexistent' + owner: '111111' + state: touch + check_mode: yes + ignore_errors: yes + register: owner_uid_exists + +# I don't use state=touch here intentionally to fail and catch warnings +- name: group does not exist in check_mode + file: + path: '/tmp/nonexistent' + group: nonexistent1 + check_mode: yes + register: group_no_exist + ignore_errors: yes + +- name: create group + group: + name: nonexistent1 + notify: remove groups + +# I don't use state=touch here intentionally to fail and catch warnings +- name: group exists in check_mode + file: + path: '/tmp/nonexistent' + group: nonexistent1 + check_mode: yes + register: group_exists + ignore_errors: yes + +# I don't use state=touch here intentionally to fail and catch warnings +- name: group does not exist in check_mode, using gid + file: + path: '/tmp/nonexistent' + group: '111112' + check_mode: yes + register: group_gid_no_exist + ignore_errors: yes + +- name: create group with gid + group: + name: test_gid + gid: 111112 + notify: remove groups + +# I don't use state=touch here intentionally to fail and catch warnings +- name: group exists in check_mode, using gid + file: + path: '/tmp/nonexistent' + group: '111112' + check_mode: yes + register: group_gid_exists + ignore_errors: yes + +- assert: + that: + - owner_no_exist.warnings[0] is search('failed to look up user') + - owner_uid_no_exist.warnings[0] is search('failed to look up user with uid') + - group_no_exist.warnings[0] is search('failed to look up group') + - group_gid_no_exist.warnings[0] is search('failed to look up group with gid') + - owner_exists.warnings is not defined + - owner_uid_exists.warnings is not defined + - group_exists.warnings is not defined + - group_gid_exists.warnings is not defined + +# ensures touching a file returns changed when needed +# issue: https://github.com/ansible/ansible/issues/79360 +- name: touch a file returns changed in check mode if file does not exist + file: + path: '/tmp/touch_check_mode_test' + state: touch + check_mode: yes + register: touch_result_in_check_mode_not_existing + +- name: touch the file + file: + path: '/tmp/touch_check_mode_test' + mode: "0660" + state: touch + +- name: touch an existing file returns changed in check mode + file: + path: '/tmp/touch_check_mode_test' + state: touch + check_mode: yes + register: touch_result_in_check_mode_change_all_attr + +- name: touch an existing file returns changed in check mode when preserving access time + file: + path: '/tmp/touch_check_mode_test' + state: touch + access_time: "preserve" + check_mode: yes + register: touch_result_in_check_mode_preserve_access_time + +- name: touch an existing file returns changed in check mode when only mode changes + file: + path: '/tmp/touch_check_mode_test' + state: touch + access_time: "preserve" + modification_time: "preserve" + mode: "0640" + check_mode: yes + register: touch_result_in_check_mode_change_only_mode + +- name: touch an existing file returns ok if all attributes are preserved + file: + path: '/tmp/touch_check_mode_test' + state: touch + access_time: "preserve" + modification_time: "preserve" + check_mode: yes + register: touch_result_in_check_mode_all_attrs_preserved + +- name: touch an existing file fails in check mode when user does not exist + file: + path: '/tmp/touch_check_mode_test' + state: touch + owner: not-existing-user + check_mode: yes + ignore_errors: true + register: touch_result_in_check_mode_fails_not_existing_user + +- name: touch an existing file fails in check mode when group does not exist + file: + path: '/tmp/touch_check_mode_test' + state: touch + group: not-existing-group + check_mode: yes + ignore_errors: true + register: touch_result_in_check_mode_fails_not_existing_group + +- assert: + that: + - touch_result_in_check_mode_not_existing.changed + - touch_result_in_check_mode_preserve_access_time.changed + - touch_result_in_check_mode_change_only_mode.changed + - not touch_result_in_check_mode_all_attrs_preserved.changed + - touch_result_in_check_mode_fails_not_existing_user.warnings[0] is search('failed to look up user') + - touch_result_in_check_mode_fails_not_existing_group.warnings[0] is search('failed to look up group') + +# https://github.com/ansible/ansible/issues/50943 +# Need to use /tmp as nobody can't access remote_tmp_dir_test at all +- name: create file as root with all write permissions + file: dest=/tmp/write_utime state=touch mode=0666 owner={{ansible_user_id}} + +- name: Pause to ensure stat times are not the exact same + pause: + seconds: 1 + +- block: + - name: get previous time + stat: path=/tmp/write_utime + register: previous_time + + - name: pause for 1 second to ensure the next touch is newer + pause: seconds=1 + + - name: touch file as nobody + file: dest=/tmp/write_utime state=touch + become: True + become_user: nobody + register: result + + - name: get new time + stat: path=/tmp/write_utime + register: current_time + + always: + - name: remove test utime file + file: path=/tmp/write_utime state=absent + +- name: assert touch file as nobody + assert: + that: + - result is changed + - current_time.stat.atime > previous_time.stat.atime + - current_time.stat.mtime > previous_time.stat.mtime + +# Follow + recursive tests +- name: create a toplevel directory + file: path={{remote_tmp_dir_test}}/test_follow_rec state=directory mode=0755 + +- name: create a file outside of the toplevel + file: path={{remote_tmp_dir_test}}/test_follow_rec_target_file state=touch mode=0700 + +- name: create a directory outside of the toplevel + file: path={{remote_tmp_dir_test}}/test_follow_rec_target_dir state=directory mode=0700 + +- name: create a file inside of the link target directory + file: path={{remote_tmp_dir_test}}/test_follow_rec_target_dir/foo state=touch mode=0700 + +- name: create a symlink to the file + file: path={{remote_tmp_dir_test}}/test_follow_rec/test_link state=link src="../test_follow_rec_target_file" + +- name: create a symlink to the directory + file: path={{remote_tmp_dir_test}}/test_follow_rec/test_link_dir state=link src="../test_follow_rec_target_dir" + +- name: create a symlink to a nonexistent file + file: path={{remote_tmp_dir_test}}/test_follow_rec/nonexistent state=link src=does_not_exist force=True + +- name: try to change permissions without following symlinks + file: path={{remote_tmp_dir_test}}/test_follow_rec follow=False mode="a-x" recurse=True + +- name: stat the link file target + stat: path={{remote_tmp_dir_test}}/test_follow_rec_target_file + register: file_result + +- name: stat the link dir target + stat: path={{remote_tmp_dir_test}}/test_follow_rec_target_dir + register: dir_result + +- name: stat the file inside the link dir target + stat: path={{remote_tmp_dir_test}}/test_follow_rec_target_dir/foo + register: file_in_dir_result + +- name: assert that the link targets were unmodified + assert: + that: + - file_result.stat.mode == '0700' + - dir_result.stat.mode == '0700' + - file_in_dir_result.stat.mode == '0700' + +- name: try to change permissions with following symlinks + file: path={{remote_tmp_dir_test}}/test_follow_rec follow=True mode="a-x" recurse=True + +- name: stat the link file target + stat: path={{remote_tmp_dir_test}}/test_follow_rec_target_file + register: file_result + +- name: stat the link dir target + stat: path={{remote_tmp_dir_test}}/test_follow_rec_target_dir + register: dir_result + +- name: stat the file inside the link dir target + stat: path={{remote_tmp_dir_test}}/test_follow_rec_target_dir/foo + register: file_in_dir_result + +- name: assert that the link targets were modified + assert: + that: + - file_result.stat.mode == '0600' + - dir_result.stat.mode == '0600' + - file_in_dir_result.stat.mode == '0600' + +# https://github.com/ansible/ansible/issues/55971 +- name: Test missing src and path + file: + state: hard + register: file_error1 + ignore_errors: yes + +- assert: + that: + - "file_error1 is failed" + - "file_error1.msg == 'missing required arguments: path'" + +- name: Test missing src + file: + dest: "{{ remote_tmp_dir_test }}/hard.txt" + state: hard + register: file_error2 + ignore_errors: yes + +- assert: + that: + - "file_error2 is failed" + - "file_error2.msg == 'src is required for creating new hardlinks'" + +- name: Test non-existing src + file: + src: non-existing-file-that-does-not-exist.txt + dest: "{{ remote_tmp_dir_test }}/hard.txt" + state: hard + register: file_error3 + ignore_errors: yes + +- assert: + that: + - "file_error3 is failed" + - "file_error3.msg == 'src does not exist'" + - "file_error3.dest == '{{ remote_tmp_dir_test }}/hard.txt' | expanduser" + - "file_error3.src == 'non-existing-file-that-does-not-exist.txt'" + +- block: + - name: Create a testing file + file: + dest: original_file.txt + state: touch + + - name: Test relative path with state=hard + file: + src: original_file.txt + dest: hard_link_file.txt + state: hard + register: hard_link_relpath + + - name: Just check if it was successful, we don't care about the actual hard link in this test + assert: + that: + - "hard_link_relpath is success" + + always: + - name: Clean up + file: + path: "{{ item }}" + state: absent + loop: + - original_file.txt + - hard_link_file.txt + +# END #55971 diff --git a/test/integration/targets/file/tasks/modification_time.yml b/test/integration/targets/file/tasks/modification_time.yml new file mode 100644 index 0000000..daec036 --- /dev/null +++ b/test/integration/targets/file/tasks/modification_time.yml @@ -0,0 +1,70 @@ +# file module tests for dealing with modification_time + +- name: Initialize the test output dir + import_tasks: initialize.yml + +- name: Setup the modification time for the tests + set_fact: + modification_timestamp: "202202081414.00" + +- name: Get stat info for the file + stat: + path: "{{ output_file }}" + register: initial_file_stat + +- name: Set a modification time in check_mode + ansible.builtin.file: + path: "{{ output_file }}" + modification_time: "{{ modification_timestamp }}" + modification_time_format: "%Y%m%d%H%M.%S" + check_mode: true + register: file_change_check_mode + +- name: Re-stat the file + stat: + path: "{{ output_file }}" + register: check_mode_stat + +- name: Confirm check_mode did not change the file + assert: + that: + - initial_file_stat.stat.mtime == check_mode_stat.stat.mtime + # Ensure the changed flag was set + - file_change_check_mode.changed + # Ensure the diff is present + # Note: file diff always contains the path + - file_change_check_mode.diff.after | length > 1 + +- name: Set a modification time for real + ansible.builtin.file: + path: "{{ output_file }}" + modification_time: "{{ modification_timestamp }}" + modification_time_format: "%Y%m%d%H%M.%S" + register: file_change_no_check_mode + +- name: Stat of the file after the change + stat: + path: "{{ output_file }}" + register: change_stat + +- name: Confirm the modification time changed + assert: + that: + - initial_file_stat.stat.mtime != change_stat.stat.mtime + - file_change_no_check_mode.changed + # Note: file diff always contains the path + - file_change_no_check_mode.diff.after | length > 1 + +- name: Set a modification time a second time to confirm no changes or diffs + ansible.builtin.file: + path: "{{ output_file }}" + modification_time: "{{ modification_timestamp }}" + modification_time_format: "%Y%m%d%H%M.%S" + register: file_change_no_check_mode_second + +- name: Confirm no changes made registered + assert: + that: + - not file_change_no_check_mode_second.changed + # Note: file diff always contains the path + - file_change_no_check_mode_second.diff.after | length == 1 diff --git a/test/integration/targets/file/tasks/selinux_tests.yml b/test/integration/targets/file/tasks/selinux_tests.yml new file mode 100644 index 0000000..eda54f1 --- /dev/null +++ b/test/integration/targets/file/tasks/selinux_tests.yml @@ -0,0 +1,33 @@ +# Test code for the file module - selinux subtasks. +# (c) 2014, Richard Isaacson <richard.c.isaacson@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/>. + +- name: Initialize the test output dir + import_tasks: initialize.yml + +- name: touch a file for testing + file: path={{remote_tmp_dir_test}}/foo-se.txt state=touch + register: file_se_result + +- name: verify that the file was marked as changed + assert: + that: + - "file_se_result.changed == true" + - "file_se_result.secontext == 'unconfined_u:object_r:admin_home_t:s0'" + +- name: remove the file used for testing + file: path={{remote_tmp_dir_test}}/foo-se.txt state=absent diff --git a/test/integration/targets/file/tasks/state_link.yml b/test/integration/targets/file/tasks/state_link.yml new file mode 100644 index 0000000..673fe6f --- /dev/null +++ b/test/integration/targets/file/tasks/state_link.yml @@ -0,0 +1,501 @@ +# file module tests for dealing with symlinks (state=link) + +- name: Initialize the test output dir + import_tasks: initialize.yml + +# +# Basic absolute symlink to a file +# +- name: create soft link to file + file: src={{output_file}} dest={{remote_tmp_dir_test}}/soft.txt state=link + register: file1_result + +- name: Get stat info for the link + stat: + path: '{{ remote_tmp_dir_test }}/soft.txt' + follow: False + register: file1_link_stat + +- name: verify that the symlink was created correctly + assert: + that: + - 'file1_result is changed' + - 'file1_link_stat["stat"].islnk' + - 'file1_link_stat["stat"].lnk_target | expanduser == output_file | expanduser' + +# +# Change an absolute soft link into a relative soft link +# +- name: change soft link to relative + file: src={{output_file|basename}} dest={{remote_tmp_dir_test}}/soft.txt state=link + register: file2_result + +- name: Get stat info for the link + stat: + path: '{{ remote_tmp_dir_test }}/soft.txt' + follow: False + register: file2_link_stat + +- name: verify that the file was marked as changed + assert: + that: + - "file2_result is changed" + - "file2_result.diff.before.src == remote_file_expanded" + - "file2_result.diff.after.src == remote_file_expanded|basename" + - "file2_link_stat['stat'].islnk" + - "file2_link_stat['stat'].lnk_target == remote_file_expanded | basename" + +# +# Check that creating the soft link a second time was idempotent +# +- name: soft link idempotency check + file: src={{output_file|basename}} dest={{remote_tmp_dir_test}}/soft.txt state=link + register: file3_result + +- name: Get stat info for the link + stat: + path: '{{ remote_tmp_dir_test }}/soft.txt' + follow: False + register: file3_link_stat + +- name: verify that the file was not marked as changed + assert: + that: + - "not file3_result is changed" + - "file3_link_stat['stat'].islnk" + - "file3_link_stat['stat'].lnk_target == remote_file_expanded | basename" + +# +# Test symlink to nonexistent files +# +- name: fail to create soft link to non existent file + file: + src: '/nonexistent' + dest: '{{remote_tmp_dir_test}}/soft2.txt' + state: 'link' + force: False + register: file4_result + ignore_errors: true + +- name: verify that link was not created + assert: + that: + - "file4_result is failed" + +- name: force creation soft link to non existent + file: + src: '/nonexistent' + dest: '{{ remote_tmp_dir_test}}/soft2.txt' + state: 'link' + force: True + register: file5_result + +- name: Get stat info for the link + stat: + path: '{{ remote_tmp_dir_test }}/soft2.txt' + follow: False + register: file5_link_stat + +- name: verify that link was created + assert: + that: + - "file5_result is changed" + - "file5_link_stat['stat'].islnk" + - "file5_link_stat['stat'].lnk_target == '/nonexistent'" + +- name: Prove idempotence of force creation soft link to non existent + file: + src: '/nonexistent' + dest: '{{ remote_tmp_dir_test }}/soft2.txt' + state: 'link' + force: True + register: file6a_result + +- name: verify that the link to nonexistent is idempotent + assert: + that: + - "file6a_result.changed == false" + +# In order for a symlink in a sticky world writable directory to be followed, it must +# either be owned by the follower, +# or the directory and symlink must have the same owner. +- name: symlink in sticky directory + block: + - name: Create remote unprivileged remote user + user: + name: '{{ remote_unprivileged_user }}' + register: user + notify: remove users + + - name: Create a local temporary directory + tempfile: + state: directory + register: tempdir + + - name: Set sticky bit + file: + path: '{{ tempdir.path }}' + mode: o=rwXt + + - name: 'Check mode: force creation soft link in sticky directory owned by another user (mode is used)' + file: + src: '{{ user.home }}/nonexistent' + dest: '{{ tempdir.path }}/soft3.txt' + mode: 0640 + state: 'link' + owner: '{{ remote_unprivileged_user }}' + force: true + follow: false + check_mode: true + register: missing_dst_no_follow_enable_force_use_mode1 + + - name: force creation soft link in sticky directory owned by another user (mode is used) + file: + src: '{{ user.home }}/nonexistent' + dest: '{{ tempdir.path }}/soft3.txt' + mode: 0640 + state: 'link' + owner: '{{ remote_unprivileged_user }}' + force: true + follow: false + register: missing_dst_no_follow_enable_force_use_mode2 + + - name: Get stat info for the link + stat: + path: '{{ tempdir.path }}/soft3.txt' + follow: false + register: soft3_result + + - name: 'Idempotence: force creation soft link in sticky directory owned by another user (mode is used)' + file: + src: '{{ user.home }}/nonexistent' + dest: '{{ tempdir.path }}/soft3.txt' + mode: 0640 + state: 'link' + owner: '{{ remote_unprivileged_user }}' + force: yes + follow: false + register: missing_dst_no_follow_enable_force_use_mode3 + always: + - name: Delete remote unprivileged remote user + user: + name: '{{ remote_unprivileged_user }}' + state: absent + force: yes + remove: yes + + - name: Delete unprivileged user home and tempdir + file: + path: "{{ item }}" + state: absent + loop: + - '{{ tempdir.path }}' + - '{{ user.home }}' + +- name: verify that link was created + assert: + that: + - "missing_dst_no_follow_enable_force_use_mode1 is changed" + - "missing_dst_no_follow_enable_force_use_mode2 is changed" + - "missing_dst_no_follow_enable_force_use_mode3 is not changed" + - "soft3_result['stat'].islnk" + - "soft3_result['stat'].lnk_target == '{{ user.home }}/nonexistent'" + +# +# Test creating a link to a directory https://github.com/ansible/ansible/issues/1369 +# +- name: create soft link to directory using absolute path + file: + src: '/' + dest: '{{ remote_tmp_dir_test }}/root' + state: 'link' + register: file6_result + +- name: Get stat info for the link + stat: + path: '{{ remote_tmp_dir_test }}/root' + follow: False + register: file6_link_stat + +- name: Get stat info for the pointed to file + stat: + path: '{{ remote_tmp_dir_test }}/root' + follow: True + register: file6_links_dest_stat + +- name: Get stat info for the file we intend to point to + stat: + path: '/' + follow: False + register: file6_dest_stat + +- name: verify that the link was created correctly + assert: + that: + # file command reports it created something + - "file6_result.changed == true" + # file command created a link + - 'file6_link_stat["stat"]["islnk"]' + # Link points to the right path + - 'file6_link_stat["stat"]["lnk_target"] == "/"' + # The link target and the file we intended to link to have the same inode + - 'file6_links_dest_stat["stat"]["inode"] == file6_dest_stat["stat"]["inode"]' + +# +# Test creating a relative link +# + +# Relative link to file +- name: create a test sub-directory to link to + file: + dest: '{{ remote_tmp_dir_test }}/sub1' + state: 'directory' + +- name: create a file to link to in the test sub-directory + file: + dest: '{{ remote_tmp_dir_test }}/sub1/file1' + state: 'touch' + +- name: create another test sub-directory to place links within + file: + dest: '{{remote_tmp_dir_test}}/sub2' + state: 'directory' + +- name: create soft link to relative file + file: + src: '../sub1/file1' + dest: '{{ remote_tmp_dir_test }}/sub2/link1' + state: 'link' + register: file7_result + +- name: Get stat info for the link + stat: + path: '{{ remote_tmp_dir_test }}/sub2/link1' + follow: False + register: file7_link_stat + +- name: Get stat info for the pointed to file + stat: + path: '{{ remote_tmp_dir_test }}/sub2/link1' + follow: True + register: file7_links_dest_stat + +- name: Get stat info for the file we intend to point to + stat: + path: '{{ remote_tmp_dir_test }}/sub1/file1' + follow: False + register: file7_dest_stat + +- name: verify that the link was created correctly + assert: + that: + # file command reports it created something + - "file7_result.changed == true" + # file command created a link + - 'file7_link_stat["stat"]["islnk"]' + # Link points to the right path + - 'file7_link_stat["stat"]["lnk_target"] == "../sub1/file1"' + # The link target and the file we intended to link to have the same inode + - 'file7_links_dest_stat["stat"]["inode"] == file7_dest_stat["stat"]["inode"]' + +# Relative link to directory +- name: create soft link to relative directory + file: + src: sub1 + dest: '{{ remote_tmp_dir_test }}/sub1-link' + state: 'link' + register: file8_result + +- name: Get stat info for the link + stat: + path: '{{ remote_tmp_dir_test }}/sub1-link' + follow: False + register: file8_link_stat + +- name: Get stat info for the pointed to file + stat: + path: '{{ remote_tmp_dir_test }}/sub1-link' + follow: True + register: file8_links_dest_stat + +- name: Get stat info for the file we intend to point to + stat: + path: '{{ remote_tmp_dir_test }}/sub1' + follow: False + register: file8_dest_stat + +- name: verify that the link was created correctly + assert: + that: + # file command reports it created something + - "file8_result.changed == true" + # file command created a link + - 'file8_link_stat["stat"]["islnk"]' + # Link points to the right path + - 'file8_link_stat["stat"]["lnk_target"] == "sub1"' + # The link target and the file we intended to link to have the same inode + - 'file8_links_dest_stat["stat"]["inode"] == file8_dest_stat["stat"]["inode"]' + +# test the file module using follow=yes, so that the target of a +# symlink is modified, rather than the link itself + +- name: create a test file + copy: + dest: '{{remote_tmp_dir_test}}/test_follow' + content: 'this is a test file\n' + mode: 0666 + +- name: create a symlink to the test file + file: + path: '{{remote_tmp_dir_test}}/test_follow_link' + src: './test_follow' + state: 'link' + +- name: modify the permissions on the link using follow=yes + file: + path: '{{remote_tmp_dir_test}}/test_follow_link' + mode: 0644 + follow: yes + register: file9_result + +- name: stat the link target + stat: + path: '{{remote_tmp_dir_test}}/test_follow' + register: file9_stat + +- name: assert that the chmod worked + assert: + that: + - 'file9_result is changed' + - 'file9_stat["stat"]["mode"] == "0644"' + +# +# Test modifying the permissions of a link itself +# +- name: attempt to modify the permissions of the link itself + file: + path: '{{remote_tmp_dir_test}}/test_follow_link' + state: 'link' + mode: 0600 + follow: False + register: file10_result + +# Whether the link itself changed is platform dependent! (BSD vs Linux?) +# Just check that the underlying file was not changed +- name: stat the link target + stat: + path: '{{remote_tmp_dir_test}}/test_follow' + register: file10_target_stat + +- name: assert that the link target was unmodified + assert: + that: + - 'file10_target_stat["stat"]["mode"] == "0644"' + + +# https://github.com/ansible/ansible/issues/56928 +- block: + + - name: Create a testing file + file: + path: "{{ remote_tmp_dir_test }}/test_follow1" + state: touch + + - name: Create a symlink and change mode of the original file, since follow == yes by default + file: + src: "{{ remote_tmp_dir_test }}/test_follow1" + dest: "{{ remote_tmp_dir_test }}/test_follow1_link" + state: link + mode: 0700 + + - name: stat the original file + stat: + path: "{{ remote_tmp_dir_test }}/test_follow1" + register: stat_out + + - name: Check if the mode of the original file was set + assert: + that: + - 'stat_out.stat.mode == "0700"' + + always: + - name: Clean up + file: + path: "{{ item }}" + state: absent + loop: + - "{{ remote_tmp_dir_test }}/test_follow1" + - "{{ remote_tmp_dir_test }}/test_follow1_link" + +# END #56928 + + +# Test failure with src and no state parameter +- name: Specify src without state + file: + src: "{{ output_file }}" + dest: "{{ remote_tmp_dir_test }}/link.txt" + ignore_errors: yes + register: src_state + +- name: Ensure src without state failed + assert: + that: + - src_state is failed + - "'src option requires state to be' in src_state.msg" + +- name: Create without src + file: + state: link + dest: "{{ output_dir }}/link.txt" + ignore_errors: yes + register: create_without_src + +- name: Ensure create without src failed + assert: + that: + - create_without_src is failed + - "'src is required' in create_without_src.msg" + +# Test creating a symlink when the destination exists and is a file +- name: create a test file + copy: + dest: '{{ remote_tmp_dir_test }}/file.txt' + content: 'this is a test file\n' + mode: 0666 + +- name: Create a symlink with dest already a file + file: + src: '{{ output_file }}' + dest: '{{ remote_tmp_dir_test }}/file.txt' + state: link + ignore_errors: true + register: dest_is_existing_file_fail + +- name: Stat to make sure the symlink was not created + stat: + path: '{{ remote_tmp_dir_test }}/file.txt' + follow: false + register: dest_is_existing_file_fail_stat + +- name: Forcefully a symlink with dest already a file + file: + src: '{{ output_file }}' + dest: '{{ remote_tmp_dir_test }}/file.txt' + state: link + force: true + register: dest_is_existing_file_force + +- name: Stat to make sure the symlink was created + stat: + path: '{{ remote_tmp_dir_test }}/file.txt' + follow: false + register: dest_is_existing_file_force_stat + +- assert: + that: + - dest_is_existing_file_fail is failed + - not dest_is_existing_file_fail_stat.stat.islnk + - dest_is_existing_file_force is changed + - dest_is_existing_file_force_stat.stat.exists + - dest_is_existing_file_force_stat.stat.islnk diff --git a/test/integration/targets/file/tasks/unicode_path.yml b/test/integration/targets/file/tasks/unicode_path.yml new file mode 100644 index 0000000..a4902a9 --- /dev/null +++ b/test/integration/targets/file/tasks/unicode_path.yml @@ -0,0 +1,10 @@ +- name: create local file with unicode filename and content + lineinfile: + dest: "{{ remote_tmp_dir_test }}/语/汉语.txt" + create: true + line: 汉语 + +- name: remove local file with unicode filename and content + file: + path: "{{ remote_tmp_dir_test }}/语/汉语.txt" + state: absent |