"Destination not writable" is returned when attempting to create, copy, download or move a file to a directory that the user lacks the write permission. There are a number of different modules that are used to create, copy, download or move a file to a directory, such as:
- the copy module
- the file module
- the get_url module
- the unarchive module
As an example, let's say the file module is being used to create foo.txt in the /etc directory.
- name: "create foo.txt in the /etc directory"
file:
path: "/etc/foo.txt"
state: "touch"
Typically, the /etc directory has the following ownership and permissions. Notice only root has the "w" (write) permission to this directory.
drwxr-xr-x. 124 root root 8192 Oct 15 05:59 /etc
Using native Linux commands, if a user other than root attempts to write to the /etc directory, "permission denied" would be returned.
[john.doe]$ touch /etc/foo.txt
touch: cannot touch ‘/etc/foo.txt’: Permission denied
Likewise, if John Doe (or any user other than root) were to invoke an Ansible playbook that attempted to create, copy, download or move a file to the /etc directory, "Destination not writable" would be returned.
TASKS [create foo.txt in the /etc directory]
fatal: [server1.example.com]: FAILED! => {"changed": false, "checksum": "34949034fz73467b77cdc923aa747b", "msg": "Destination /etc not writable"}
Furthermore, the file module could be used to confirm that only root can write to the /etc directory.
- name: "get the stats of the '/etc' directory"
stat:
path: "/etc"
register: out
- name: "output the 'out' variable"
debug:
msg: "{{ out }}"
Which should return something like this. Notice here the "writeable" is "false".
TASK [output the 'out' variable]
ok: [server1.example.com] => {
"msg": {
"changed": false,
"failed": false,
"stat": {
"atime": 1602763108.245787,
"attr_flags": "",
"attributes": [],
"block_size": 4096,
"blocks": 24,
"charset": "binary",
"ctime": 1602762950.0358446,
"dev": 64768,
"device_type": 0,
"executable": true,
"exists": true,
"gid": 0,
"gr_name": "root",
"inode": 69,
"isblk": false,
"ischr": false,
"isdir": true,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": false,
"issock": false,
"isuid": false,
"mimetype": "inode/directory",
"mode": "0755",
"mtime": 1602762950.0358446,
"nlink": 124,
"path": "/etc",
"pw_name": "root",
"readable": true,
"rgrp": true,
"roth": true,
"rusr": true,
"size": 8192,
"uid": 0,
"version": "1862525523",
"wgrp": false,
"woth": false,
"writeable": false,
"wusr": true,
"xgrp": true,
"xoth": true,
"xusr": true
}
}
}
Be aware that a sort of "false positive" situation can occur. Let's say /tmp/foo.txt already exists, and foo.txt is owned by John Doe.
~]$ ls -l /tmp/foo.txt
-rw-r--r--. 1 john.doe admins 1524 Oct 16 05:23 foo.txt
If Jane Doe attempts to create, copy, download or move foo.txt to /tmp/foo.txt, Jane may also get "Destination /etc not writable". For this reason, before you create, copy, download or move a file, it's a good practice to first determine if the file already exists.
- name: "store the statistics of /tmp/foo.txt in the 'foo' variable"
stat:
path: "/tmp/foo.txt"
register: foo
The fail module can now be used to cease execution of the playbook if /opt/example.txt does not exist.
- name: "fail when /tmp/foo.txt exists"
fail:
msg: "/tmp/foo.txt already exists"
when: "foo.stat.exists == true"
Assuming the target file does not already exist, there are a few solutions to "Destination not writeable":
- create, copy, download or move a file to a directory that the is writeable, such as the /tmp directory
- Invoke the playbook as root
- Use "become" to become root - of course, this assumes that you have the root password
Did you find this article helpful?
If so, consider buying me a coffee over at