Repository: ansible/ansible

Issue:
copy: directory_mode arg is not octal-cleansed like mode arg
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
Copy module
##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
$ ansible --version
ansible 2.3.0.0
  config file = /home/wed/repostories/smartholiday-conf/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]

```

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
N/A
##### SUMMARY
<!--- Explain the problem briefly -->

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Test
  copy:
    src: "src_dir"
    dest: "dest_dir"
    mode: 0640
    directory_mode: 0750

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
The directory should be copied with the joined modes/permissions.
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
It fails badly, stopping on first absent directory, copying it with root owner while another user and groups were required, and then exiting there. If ran several times, it passes present directories without correcting user and group though and copy the one next to the last present one. All directories after the first get the right user and group, but it is still copying them one by run and failing after. (This was containing several directories).
<!--- Paste verbatim command output between quotes below -->
```
failed: [host] (item={u'dest': u'dest_dir', u'src': u'src_dir'}) => {"checksum": "---", "details": "bad symbolic permission for mode: 488", "failed": true, "gid": 1001, "group": "group", "item": {"dest": "dest_dir", "src": "src_dir"}, "mode": "0755", "msg": "mode must be in octal or symbolic form", "owner": "owner", "path": "/path/to/dest_file", "size": 4096, "state": "directory", "uid": 1001}

```
##### ADDITIONAL NOTES

This issue have been recreated from https://github.com/ansible/ansible-modules-core/issues/5234
It is still here in 2.3, and workaround proposed by @edrozenberg in https://github.com/ansible/ansible-modules-core/issues/5234#issuecomment-259799568 works correctly (having `directory_mode` enquoted).

`mode` in the error message shows `0755` while the task had the `0640` value (and `directory_mode` had `0750`)

EDIT: I forgot to add that problem didn't show up with -CD (check/dry-run mode).


Discussion:
This is a known issue with YAML type inference- octal int literals are signified in YAML 1.1 by values beginning with `0` and containing only digits 0-9. YAML is turning the octal mode string into the integer 493, which gets stringified directly to chmod. There's no sane way to fix this internally- we can't just assume any integer value has been octal-interpreted, as `750` (sans leading 0) would still come in as an integer, but attempting to reverse octal conversion on that would be wrong. We can't really fix this internally without changing Ansible to use YAML1.2 (which requires the octal prefix `0o` to trigger this behavior), but that would likely break a lot of other things. It didn't fail in check mode because we don't actually send the mode value to chmod in check mode.

I see a couple of options:
- interpret any mode value that reaches the module as an int as "suspect" and fail with a warning about "this value should be quoted" (this will break anyone doing non-zero-prefixed modes today)
- add docs to every usage of mode in the product strongly recommending that they be quoted in YAML

Thoughts?

cc @bcoca @jimi-c @abadger 
Yeah, but in the current situation, it failed even with the value starting with a zero.

Plus what I don't get at all is that `directory_mode` breaks while `mode` don't. Aren't those supposed to read values the same way and so behave the same way? (if I quote `directory_mode` and don't quote `mode`, it works flawlessly).
Apparently there's a large wad of code that tries to detect and reverse this situation for mode, but it's not applied on directory_mode. I'm not certain said code is 100% correct in all situations, but we might be able to apply it to directory_mode and at least be consistent. The leading zero is what causes the octal->decimal conversion that breaks it.
Having consistency between mode and directory_mode would make sense, for whichever way the octal detection works. If it breaks, at least it would break in the same way for both mode and directory_mode.

Since mode seems to work ok at the moment, making directory_mode behave the same way would be good.

Resolving patch:
diff --git a/lib/ansible/modules/files/copy.py b/lib/ansible/modules/files/copy.py
--- a/lib/ansible/modules/files/copy.py
+++ b/lib/ansible/modules/files/copy.py
@@ -262,7 +262,7 @@ def main():
             backup            = dict(default=False, type='bool'),
             force             = dict(default=True, aliases=['thirsty'], type='bool'),
             validate          = dict(required=False, type='str'),
-            directory_mode    = dict(required=False),
+            directory_mode    = dict(required=False, type='raw'),
             remote_src        = dict(required=False, type='bool'),
         ),
         add_file_common_args=True,


Test patch: