ansible/ansible — pull request #6285 by rhaido (2014-03-04 22:54:55+00:00)
Title: Correct checking for symlink or hard link source existence

Dear Ansible Development Team,

This patch was created to fix the bug introduced in commit **684921c1c1cd05126815affb367a83dfd0256e18**. The problem was reported by **wferi** [here](https://github.com/ansible/ansible/pull/3119#issuecomment-36658883).

The aforementioned commit (**684921c1c1cd05126815affb367a83dfd0256e18**) blindly assignes all the source file existence checks only to the case *state == 'hard'*, forgetting about the case *state == 'link'*, where we also need to check the source existence if force is False.

The following new behaviour is defined:
- if state == hard and src is not absolute - failure (for symlinks that's not necessary, because ln -sf xxx /tmp/yyy is the valid operation and very useful to have in ansible)
- if src doesn't exist and state is hard or not force (i.e. state == 'link' and force flag is False) - failure

I tested it with standard ansible tests:
```
-bash-4.1$ make tests
PYTHONPATH=./lib ANSIBLE_LIBRARY=./library nosetests -d -w test/units -v
test_configfile_and_env_both_set (TestConstants.TestConstants) ... ok
test_configfile_not_set_env_not_set (TestConstants.TestConstants) ... ok
test_configfile_not_set_env_set (TestConstants.TestConstants) ... ok
test_configfile_set_env_not_set (TestConstants.TestConstants) ... ok
test_bool_no (TestFilters.TestFilters) ... ok
test_bool_none (TestFilters.TestFilters) ... ok
test_bool_true (TestFilters.TestFilters) ... ok
test_bool_yes (TestFilters.TestFilters) ... ok
test_fileglob (TestFilters.TestFilters) ... ok
test_match_case_insensitive (TestFilters.TestFilters) ... ok
test_match_case_sensitive (TestFilters.TestFilters) ... ok
test_match_no_match (TestFilters.TestFilters) ... ok
test_quotes (TestFilters.TestFilters) ... ok
test_regex (TestFilters.TestFilters) ... ok
test_search_case_insensitive (TestFilters.TestFilters) ... ok
test_search_case_sensitive (TestFilters.TestFilters) ... ok
test_allows_equals_sign_in_var (TestInventory.TestInventory) ... ok
test_alpha_end_before_beg (TestInventory.TestInventory) ... ok
test_combined_range (TestInventory.TestInventory) ... ok
test_complex_enumeration (TestInventory.TestInventory) ... ok
test_complex_exclude (TestInventory.TestInventory) ... ok
test_complex_group_names (TestInventory.TestInventory) ... ok
test_complex_intersect (TestInventory.TestInventory) ... ok
test_complex_vars (TestInventory.TestInventory) ... ok
test_empty (TestInventory.TestInventory) ... ok
test_get_hosts (TestInventory.TestInventory) ... ok
test_hosts_list (TestInventory.TestInventory) ... ok
test_incorrect_format (TestInventory.TestInventory) ... ok
test_invalid_range (TestInventory.TestInventory) ... ok
test_large_range (TestInventory.TestInventory) ... ok
test_leading_range (TestInventory.TestInventory) ... ok
test_missing_end (TestInventory.TestInventory) ... ok
test_no_src (TestInventory.TestInventory) ... ok
test_regex_exclude (TestInventory.TestInventory) ... ok
test_script (TestInventory.TestInventory) ... ok
test_script_all (TestInventory.TestInventory) ... ok
test_script_combined (TestInventory.TestInventory) ... ok
test_script_multiple_groups (TestInventory.TestInventory) ... ok
test_script_norse (TestInventory.TestInventory) ... ok
test_script_restrict (TestInventory.TestInventory) ... ok
test_script_vars (TestInventory.TestInventory) ... ok
test_simple (TestInventory.TestInventory) ... ok
test_simple_all (TestInventory.TestInventory) ... ok
test_simple_combined (TestInventory.TestInventory) ... ok
test_simple_norse (TestInventory.TestInventory) ... ok
test_simple_port (TestInventory.TestInventory) ... ok
test_simple_restrict (TestInventory.TestInventory) ... ok
test_simple_string_fqdn (TestInventory.TestInventory) ... ok
test_simple_string_fqdn_port (TestInventory.TestInventory) ... ok
test_simple_string_fqdn_vars (TestInventory.TestInventory) ... ok
test_simple_string_ipv4 (TestInventory.TestInventory) ... ok
test_simple_string_ipv4_port (TestInventory.TestInventory) ... ok
test_simple_string_ipv4_vars (TestInventory.TestInventory) ... ok
test_simple_string_ipv6 (TestInventory.TestInventory) ... ok
test_simple_string_ipv6_port (TestInventory.TestInventory) ... ok
test_simple_string_ipv6_vars (TestInventory.TestInventory) ... ok
test_simple_ungrouped (TestInventory.TestInventory) ... ok
test_simple_vars (TestInventory.TestInventory) ... ok
test_subet_range_empty_group (TestInventory.TestInventory) ... ok
test_subset (TestInventory.TestInventory) ... ok
test_subset_filename (TestInventory.TestInventory) ... ok
test_subset_range (TestInventory.TestInventory) ... ok
testinvalid_entry (TestInventory.TestInventory) ... ok
verify the synchronize action plugin sets ... ok
verify the synchronize action plugin sets ... ok
Verify the action plugin accomodates the common ... ok
test_check_conditional_jinja2_expression (TestUtils.TestUtils) ... ok
test_check_conditional_jinja2_expression_in_variable (TestUtils.TestUtils) ... ok
test_check_conditional_jinja2_literals (TestUtils.TestUtils) ... ok
test_check_conditional_jinja2_unicode (TestUtils.TestUtils) ... ok
test_check_conditional_jinja2_variable_literals (TestUtils.TestUtils) ... ok
test_parse_kv_basic (TestUtils.TestUtils) ... ok
test_add_header (TestVault.TestVaultLib) ... ok
test_cipher_not_set (TestVault.TestVaultLib) ... ok
test_decrypt_decrypted (TestVault.TestVaultLib) ... ok
test_encrypt_encrypted (TestVault.TestVaultLib) ... ok
test_encyrpt_decrypt (TestVault.TestVaultLib) ... ok
test_is_encrypted (TestVault.TestVaultLib) ... ok
test_methods_exist (TestVault.TestVaultLib) ... ok
test_remove_header (TestVault.TestVaultLib) ... ok

----------------------------------------------------------------------
Ran 80 tests in 0.809s

OK
```
And also with my own tests:
```
-bash-4.1$ ansible 127.0.0.1 --connection=local -m file -a "state=hard path=/tmp/xxx src=yyy"
127.0.0.1 | FAILED >> {
"failed": true,
"msg": "absolute paths are required for hard links"
}
-bash-4.1$ ansible 127.0.0.1 --connection=local -m file -a "state=hard path=/tmp/xxx src=/tmp/yyy"
127.0.0.1 | FAILED >> {
"failed": true,
"msg": "src file does not exist",
"path": "/tmp/xxx",
"src": "/tmp/yyy",
"state": "absent"
}
-bash-4.1$ ansible 127.0.0.1 --connection=local -m file -a "state=link path=/tmp/xxx src=/tmp/yyy"
127.0.0.1 | FAILED >> {
"failed": true,
"msg": "src file does not exist",
"path": "/tmp/xxx",
"src": "/tmp/yyy",
"state": "absent"
}
-bash-4.1$ ansible 127.0.0.1 --connection=local -m file -a "state=link path=/tmp/xxx src=yyy"
127.0.0.1 | FAILED >> {
"failed": true,
"msg": "src file does not exist",
"path": "/tmp/xxx",
"src": "yyy",
"state": "absent"
}

-bash-4.1$ ansible 127.0.0.1 --connection=local -m file -a "state=hard path=/tmp/xxx src=/tmp/yyy force=yes"
127.0.0.1 | FAILED >> {
"failed": true,
"msg": "src file does not exist",
"path": "/tmp/xxx",
"src": "/tmp/yyy",
"state": "absent"
}
-bash-4.1$ ansible 127.0.0.1 --connection=local -m file -a "state=link path=/tmp/xxx src=yyy force=yes"
127.0.0.1 | success >> {
"changed": true,
"dest": "/tmp/xxx",
"src": "yyy",
"state": "absent"
}
```