Repository: ansible/ansible

Issue:
copy module creates target directories - completely undocumented
##### ISSUE TYPE

 - Documentation Report

(Since the module says it must maintain backwards compatibility.  Behaviour like this will certainly have been depended on by someone).

##### COMPONENT NAME
copy module

##### ANSIBLE VERSION
```
ansible-playbook 2.4.0 (devel 301cbc1f5b) last updated 2017/06/11 11:15:37 (GMT +100)
  config file = /home/alan-sysop/ansible/ansible.cfg
  configured module search path = [u'/home/alan-sysop/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/alan-sysop/src/ansible/lib/ansible
  executable location = /home/alan-sysop/src/ansible/bin/ansible-playbook
  python version = 2.7.13 (default, May 10 2017, 20:04:28) [GCC 6.3.1 20161221 (Red Hat 6.3.1-1)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
Fedora Linux -> Debian Linux, over ssh.

##### SUMMARY
Using copy module to create a single file, also creates a containing directory. It is not documented that this happens, nor what attributes ansible will choose to apply to the directory.

(I was hoping for the opposite behaviour, so that my play would implicitly assert that `/etc/smokeping/config.d/` had already been created, and fail if it had not.  Aka defensive programming).

##### STEPS TO REPRODUCE
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: configure smokeping targets
  copy:
    src: smokeping/config.d/Targets
    dest: /etc/smokeping/config.d/
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
Given that Ansible creates /etc/smokeping/config.d/ if it does not exist, Ansible should document that this will happen and explain what attributes it applies to the directory.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
TASK [smokeping : configure smokeping targets] *********************************
task path: /home/alan-sysop/ansible/roles/smokeping/tasks/main-tasks.yml:31
Using module file /home/alan-sysop/src/ansible/lib/ansible/modules/files/stat.py
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd jessie '/bin/sh -c '"'"'echo ~ && sleep 0'"'"''
<jessie> (0, '/home/alan-sysop\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd jessie '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451 `" && echo ansible-tmp-1497177121.08-152845988052451="` echo /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451 `" ) && sleep 0'"'"''
<jessie> (0, 'ansible-tmp-1497177121.08-152845988052451=/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> PUT /tmp/tmp7V3FAy TO /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451/stat.py
<jessie> SSH: EXEC sftp -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd '[jessie]'
<jessie> (0, 'sftp> put /tmp/tmp7V3FAy /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451/stat.py\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug2: Remote version: 3\r\ndebug2: Server supports extension "posix-rename@openssh.com" revision 1\r\ndebug2: Server supports extension "statvfs@openssh.com" revision 2\r\ndebug2: Server supports extension "fstatvfs@openssh.com" revision 2\r\ndebug2: Server supports extension "hardlink@openssh.com" revision 1\r\ndebug2: Server supports extension "fsync@openssh.com" revision 1\r\ndebug3: Sent message fd 5 T:16 I:1\r\ndebug3: SSH_FXP_REALPATH . -> /home/alan-sysop size 0\r\ndebug3: Looking up /tmp/tmp7V3FAy\r\ndebug3: Sent message fd 5 T:17 I:2\r\ndebug3: Received stat reply T:101 I:2\r\ndebug1: Couldn\'t stat remote file: No such file or directory\r\ndebug3: Sent message SSH2_FXP_OPEN I:3 P:/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451/stat.py\r\ndebug3: Sent message SSH2_FXP_WRITE I:4 O:0 S:32768\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 4 32768 bytes at 0\r\ndebug3: Sent message SSH2_FXP_WRITE I:5 O:32768 S:32768\r\ndebug3: Sent message SSH2_FXP_WRITE I:6 O:65536 S:2687\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 5 32768 bytes at 32768\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 6 2687 bytes at 65536\r\ndebug3: Sent message SSH2_FXP_CLOSE I:4\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd jessie '/bin/sh -c '"'"'chmod u+x /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451/ /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451/stat.py && sleep 0'"'"''
<jessie> (0, '', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd -tt jessie '/bin/sh -c '"'"'sudo -H -S  -p "[sudo via ansible, key=znyzoyyaipbthtdtkyolzhjbwpbtfmsw] password: " -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-znyzoyyaipbthtdtkyolzhjbwpbtfmsw; /usr/bin/python /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451/stat.py; rm -rf "/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.08-152845988052451/" > /dev/null 2>&1'"'"'"'"'"'"'"'"' && sleep 0'"'"''
<jessie> (0, '\r\n\r\n{"invocation": {"module_args": {"checksum_algorithm": "sha1", "get_checksum": true, "path": "/etc/smokeping/config.d/Targets", "checksum_algo": "sha1", "follow": false, "get_md5": false, "get_mime": true, "get_attributes": true}}, "stat": {"exists": false}, "changed": false}\r\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\nShared connection to jessie closed.\r\n')
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd jessie '/bin/sh -c '"'"'echo ~ && sleep 0'"'"''
<jessie> (0, '/home/alan-sysop\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd jessie '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877 `" && echo ansible-tmp-1497177121.28-8280454705877="` echo /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877 `" ) && sleep 0'"'"''
<jessie> (0, 'ansible-tmp-1497177121.28-8280454705877=/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> PUT /home/alan-sysop/ansible/roles/smokeping/files/smokeping/config.d/Targets TO /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/source
<jessie> SSH: EXEC sftp -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd '[jessie]'
<jessie> (0, 'sftp> put /home/alan-sysop/ansible/roles/smokeping/files/smokeping/config.d/Targets /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/source\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug2: Remote version: 3\r\ndebug2: Server supports extension "posix-rename@openssh.com" revision 1\r\ndebug2: Server supports extension "statvfs@openssh.com" revision 2\r\ndebug2: Server supports extension "fstatvfs@openssh.com" revision 2\r\ndebug2: Server supports extension "hardlink@openssh.com" revision 1\r\ndebug2: Server supports extension "fsync@openssh.com" revision 1\r\ndebug3: Sent message fd 5 T:16 I:1\r\ndebug3: SSH_FXP_REALPATH . -> /home/alan-sysop size 0\r\ndebug3: Looking up /home/alan-sysop/ansible/roles/smokeping/files/smokeping/config.d/Targets\r\ndebug3: Sent message fd 5 T:17 I:2\r\ndebug3: Received stat reply T:101 I:2\r\ndebug1: Couldn\'t stat remote file: No such file or directory\r\ndebug3: Sent message SSH2_FXP_OPEN I:3 P:/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/source\r\ndebug3: Sent message SSH2_FXP_WRITE I:4 O:0 S:485\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 4 485 bytes at 0\r\ndebug3: Sent message SSH2_FXP_CLOSE I:4\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd jessie '/bin/sh -c '"'"'chmod u+x /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/ /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/source && sleep 0'"'"''
<jessie> (0, '', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
Using module_utils file /home/alan-sysop/src/ansible/lib/ansible/module_utils/_text.py
Using module_utils file /home/alan-sysop/src/ansible/lib/ansible/module_utils/basic.py
Using module_utils file /home/alan-sysop/src/ansible/lib/ansible/module_utils/pycompat24.py
Using module_utils file /home/alan-sysop/src/ansible/lib/ansible/module_utils/six/__init__.py
Using module_utils file /home/alan-sysop/src/ansible/lib/ansible/module_utils/six/_six.py
Using module file /home/alan-sysop/src/ansible/lib/ansible/modules/files/copy.py
<jessie> PUT /tmp/tmpYjWsRR TO /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/copy.py
<jessie> SSH: EXEC sftp -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd '[jessie]'
<jessie> (0, 'sftp> put /tmp/tmpYjWsRR /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/copy.py\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug2: Remote version: 3\r\ndebug2: Server supports extension "posix-rename@openssh.com" revision 1\r\ndebug2: Server supports extension "statvfs@openssh.com" revision 2\r\ndebug2: Server supports extension "fstatvfs@openssh.com" revision 2\r\ndebug2: Server supports extension "hardlink@openssh.com" revision 1\r\ndebug2: Server supports extension "fsync@openssh.com" revision 1\r\ndebug3: Sent message fd 5 T:16 I:1\r\ndebug3: SSH_FXP_REALPATH . -> /home/alan-sysop size 0\r\ndebug3: Looking up /tmp/tmpYjWsRR\r\ndebug3: Sent message fd 5 T:17 I:2\r\ndebug3: Received stat reply T:101 I:2\r\ndebug1: Couldn\'t stat remote file: No such file or directory\r\ndebug3: Sent message SSH2_FXP_OPEN I:3 P:/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/copy.py\r\ndebug3: Sent message SSH2_FXP_WRITE I:4 O:0 S:32768\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 4 32768 bytes at 0\r\ndebug3: Sent message SSH2_FXP_WRITE I:5 O:32768 S:32768\r\ndebug3: Sent message SSH2_FXP_WRITE I:6 O:65536 S:2721\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 5 32768 bytes at 32768\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 6 2721 bytes at 65536\r\ndebug3: Sent message SSH2_FXP_CLOSE I:4\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd jessie '/bin/sh -c '"'"'chmod u+x /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/ /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/copy.py && sleep 0'"'"''
<jessie> (0, '', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<jessie> ESTABLISH SSH CONNECTION FOR USER: None
<jessie> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/alan-sysop/.ansible/cp/351d9a42bd -tt jessie '/bin/sh -c '"'"'sudo -H -S  -p "[sudo via ansible, key=ttdmqcglaerkibogyobybznynkhhkyxg] password: " -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-ttdmqcglaerkibogyobybznynkhhkyxg; /usr/bin/python /home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/copy.py; rm -rf "/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/" > /dev/null 2>&1'"'"'"'"'"'"'"'"' && sleep 0'"'"''
<jessie> (0, '\r\n\r\n{"src": "/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/source", "changed": true, "group": "root", "uid": 0, "dest": "/etc/smokeping/config.d/Targets", "checksum": "f3d017f8c410c343bcb013bb7e0f18be8e2de297", "md5sum": "6a9df90b7d25b7962e29a6b1ae4ac945", "owner": "root", "state": "file", "gid": 0, "mode": "0644", "invocation": {"module_args": {"src": "/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/source", "directory_mode": null, "force": true, "remote_src": null, "unsafe_writes": null, "selevel": null, "seuser": null, "setype": null, "group": null, "content": null, "dest": "/etc/smokeping/config.d/Targets", "serole": null, "original_basename": "Targets", "delimiter": null, "mode": null, "regexp": null, "owner": null, "follow": false, "validate": null, "attributes": null, "backup": false}}, "size": 485}\r\n', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug3: /etc/ssh/ssh_config line 56: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0\r\ndebug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 2: include /etc/crypto-policies/back-ends/openssh.config matched no files\r\ndebug1: /etc/ssh/ssh_config.d/05-redhat.conf line 8: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 3707\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\nShared connection to jessie closed.\r\n')
NOTIFIED HANDLER Restart smokeping
NOTIFIED HANDLER Restart apache2
changed: [jessie] => {
    "changed": true, 
    "checksum": "f3d017f8c410c343bcb013bb7e0f18be8e2de297", 
    "dest": "/etc/smokeping/config.d/Targets", 
    "failed": false, 
    "gid": 0, 
    "group": "root", 
    "invocation": {
        "module_args": {
            "attributes": null, 
            "backup": false, 
            "content": null, 
            "delimiter": null, 
            "dest": "/etc/smokeping/config.d/Targets", 
            "directory_mode": null, 
            "follow": false, 
            "force": true, 
            "group": null, 
            "mode": null, 
            "original_basename": "Targets", 
            "owner": null, 
            "regexp": null, 
            "remote_src": null, 
            "selevel": null, 
            "serole": null, 
            "setype": null, 
            "seuser": null, 
            "src": "/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/source", 
            "unsafe_writes": null, 
            "validate": null
        }
    }, 
    "md5sum": "6a9df90b7d25b7962e29a6b1ae4ac945", 
    "mode": "0644", 
    "owner": "root", 
    "size": 485, 
    "src": "/home/alan-sysop/.ansible/tmp/ansible-tmp-1497177121.28-8280454705877/source", 
    "state": "file", 
    "uid": 0
}

```



Discussion:
```
- name: configure smokeping targets
  copy:
    src: smokeping/config.d/Targets
    dest: /etc/smokeping/config.d/Targets
```
This task will fail when `̀/etc/smokeping/config.d/` doesn't exist.
@pilou no it doesn't.  Please show your test & output.

```
$ ansible --version
ansible 2.4.0 (devel 91e995d691) last updated 2017/06/14 08:27:05 (GMT +100)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/alan-sysop/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/alan-sysop/src/ansible/lib/ansible
  executable location = /home/alan-sysop/src/ansible/bin/ansible
  python version = 2.7.13 (default, May 10 2017, 20:04:28) [GCC 6.3.1 20161221 (Red Hat 6.3.1-1)]

$ cat test.yml
- hosts: all
  tasks:
   - name: copy Targets to /etc/smokeping/config.d/
     copy:
       src: Targets
       dest: /etc/smokeping/config.d/

# WARNING: delete /etc/smokeping/config.d/
#
$ sudo rm -rv /etc/smokeping/config.d/
removed '/etc/smokeping/config.d/Targets'
removed directory '/etc/smokeping/config.d/'

$ ls -l /etc/smokeping/config.d
ls: cannot access '/etc/smokeping/config.d': No such file or directory

# WARNING: overwrite /etc/smokeping/config.d/Target
#
$ ANSIBLE_CONFIG=/dev/null ansible-playbook -b -K test.yml -c local -i localhost, -v
Using /dev/null as config file
SUDO password: 

PLAY [all] *******************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************
ok: [localhost]

TASK [copy Targets to /etc/smokeping/config.d/] ******************************************************************************************************
changed: [localhost] => {"changed": true, "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709", "dest": "/etc/smokeping/config.d/Targets", "failed": false, "gid": 0, "group": "root", "md5sum": "d41d8cd98f00b204e9800998ecf8427e", "mode": "0644", "owner": "root", "secontext": "system_u:object_r:etc_t:s0", "size": 0, "src": "/home/alan-sysop/.ansible/tmp/ansible-tmp-1497426560.06-159072103176468/source", "state": "file", "uid": 0}

PLAY RECAP *******************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0   

$ ls -l /etc/smokeping/config.d
total 0
-rw-r--r--. 1 root root 0 Jun 14 08:49 Targets
@sourcejedi you aren't using the same value for the `dest` parameter.
Oh!  Sorry.  I misread you as quoting my original post, and saying it didn't work as I described.  In fact you were showing me modified code I could use, which would provide a failure when my target directory did not exist.  Interesting :).

---

I see ansible is more magic than I noticed :).  It can create _multiple_ directories, if needed for the dest path (`os.makedirs()`).

    # Special handling for recursive copy - create intermediate dirs
    if original_basename and dest.endswith(os.sep):

    https://github.com/ansible/ansible/blob/8d8cfb5/lib/ansible/modules/files/copy.py#L301

IIUC, all the directories created by the copy module, are created by this code.  So there's no need for concern about what attributes the directories are created with.  So long as the documentation mentions that parent directories can be created automatically.

The obscure part is how parent directory creation depends on both 1) which way you specify the `dest` parameter, and 2) whether `src` is a directory.

Even documented, the exact rule seems obscure enough to me, that I don't want to rely on it.  (I have enough trouble with `rsync`'s behaviour on trailing slashes).  If I have to add an explanatory comment, I may as well use an explicit task to specify which behaviour I need.

I suspect that makes it awkward to document.  Unless one is able to say "version X may create parent directories automatically in some situations; in version Y this will cause a warning, and in a future version the parent directories will be required to exist."

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
@@ -32,13 +32,14 @@
         is copied. This behavior is similar to Rsync.
   content:
     description:
-      - When used instead of 'src', sets the contents of a file directly to the specified value.
+      - When used instead of I(src), sets the contents of a file directly to the specified value.
         For anything advanced or with formatting also look at the template module.
     version_added: "1.1"
   dest:
     description:
-      - Remote absolute path where the file should be copied to. If C(src) is a directory,
-        this must be a directory too.
+      - 'Remote absolute path where the file should be copied to. If I(src) is a directory, this must be a directory too.
+        If I(dest) is a nonexistent path and if either I(dest) ends with "/" or I(src) is a directory, I(dest) is created.
+        If I(src) and I(dest) are files, the parent directory of I(dest) isn''t created: the task fails if it doesn''t already exist.'
     required: yes
   backup:
     description:
@@ -64,9 +65,9 @@
     version_added: "1.5"
   remote_src:
     description:
-      - If C(no), it will search for src at originating/master machine.
-      - If C(yes), it will go to the remote/target machine for the src. Default is False.
-      - Currently C(remote_src) does not support recursive copying.
+      - If C(no), it will search for I(src) at originating/master machine.
+      - If C(yes) it will go to the remote/target machine for the I(src). Default is C(no).
+      - Currently I(remote_src) does not support recursive copying.
     type: bool
     default: 'no'
     version_added: "2.0"
@@ -90,8 +91,8 @@
     - "Ansible Core Team"
     - "Michael DeHaan"
 notes:
-   - The "copy" module recursively copy facility does not scale to lots (>hundreds) of files.
-     For alternative, see synchronize module, which is a wrapper around rsync.
+   - The M(copy) module recursively copy facility does not scale to lots (>hundreds) of files.
+     For alternative, see M(synchronize) module, which is a wrapper around C(rsync).
    - For Windows targets, use the M(win_copy) module instead.
 '''
 


Test patch:
diff --git a/test/integration/targets/copy/tasks/dest_in_non_existent_directories.yml b/test/integration/targets/copy/tasks/dest_in_non_existent_directories.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/copy/tasks/dest_in_non_existent_directories.yml
@@ -0,0 +1,29 @@
+# src is a file, dest is a non-existent directory (2 levels of directories):
+# checks that dest is created
+- name: Ensure that dest top directory doesn't exist
+  file:
+    path: '{{ remote_dir }}/{{ item.dest.split("/")[0] }}'
+    state: absent
+
+- name: Copy file, dest is a nonexistent target directory
+  copy:
+    src: '{{ item.src }}'
+    dest: '{{ remote_dir }}/{{ item.dest }}'
+  register: copy_result
+
+- name: assert copy worked
+  assert:
+    that:
+      - 'copy_result|success'
+      - 'copy_result|changed'
+
+- name: stat copied file
+  stat:
+    path: '{{ remote_dir }}/{{ item.check }}'
+  register: stat_file_in_dir_result
+
+- name: assert that file exists
+  assert:
+    that:
+    - stat_file_in_dir_result.stat.exists
+    - stat_file_in_dir_result.stat.isreg
diff --git a/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir.yml b/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir.yml
@@ -0,0 +1,26 @@
+- name: Ensure that dest top directory doesn't exist
+  file:
+    path: '{{ remote_dir }}/{{ dest.split("/")[0] }}'
+    state: absent
+
+- name: Copy file, dest is a file in non-existing target directory
+  copy:
+    src: foo.txt
+    dest: '{{ remote_dir }}/{{ dest }}'
+  register: copy_result
+  ignore_errors: True
+
+- name: Assert copy failed
+  assert:
+    that:
+      - 'copy_result|failed'
+
+- name: Stat dest path
+  stat:
+    path: '{{ remote_dir }}/{{ dest.split("/")[0] }}'
+  register: stat_file_in_dir_result
+
+- name: assert that dest doesn't exist
+  assert:
+    that:
+      - 'not stat_file_in_dir_result.stat.exists'
diff --git a/test/integration/targets/copy/tasks/tests.yml b/test/integration/targets/copy/tasks/tests.yml
--- a/test/integration/targets/copy/tasks/tests.yml
+++ b/test/integration/targets/copy/tasks/tests.yml
@@ -926,3 +926,21 @@
     - stat_file_in_dir_result.stat.isreg
     - stat_circular_symlink_result.stat.exists
     - stat_circular_symlink_result.stat.islnk
+
+# src is a file, dest is a non-existent directory (2 levels of directories):
+# checks that dest is created
+- include: dest_in_non_existent_directories.yml
+  with_items:
+    - { src: 'foo.txt', dest: 'new_sub_dir1/sub_dir2/', check: 'new_sub_dir1/sub_dir2/foo.txt' }
+    - { src: 'subdir', dest: 'new_sub_dir1/sub_dir2/', check: 'new_sub_dir1/sub_dir2/subdir/bar.txt' }
+    - { src: 'subdir/', dest: 'new_sub_dir1/sub_dir2/', check: 'new_sub_dir1/sub_dir2/bar.txt' }
+    - { src: 'subdir', dest: 'new_sub_dir1/sub_dir2', check: 'new_sub_dir1/sub_dir2/subdir/bar.txt' }
+    - { src: 'subdir/', dest: 'new_sub_dir1/sub_dir2', check: 'new_sub_dir1/sub_dir2/bar.txt' }
+
+# src is a file, dest is file in a non-existent directory: checks that a failure occurs
+- include: src_file_dest_file_in_non_existent_dir.yml
+  with_items:
+    - 'new_sub_dir1/sub_dir2/foo.txt'
+    - 'new_sub_dir1/foo.txt'
+  loop_control:
+    loop_var: 'dest'