Ansible Pitfall: File Modes
tl;dr: To avoid unexpected behaviour, always quote your file modes in Ansible tasks and variables.
Ansible often exhibits unintuitive behaviour for people who are unfamiliar with the intricacies of YAML syntax and parsing, so today we’re going to explore some of those intricacies by setting a trap for the unwary:
- copy:
src: /dev/null
dest: /var/tmp/foo
mode: 0755
$ ls -lh /var/tmp/foo
-rwxr-xr-x 1 root root 0 Nov 27 17:58 /var/tmp/foo
This is a perfectly valid Ansible task that sets the mode to exactly what you would expect. A week later your coworker comes along and copies your code, but wants their file to be setuid:
- copy:
src: /dev/null
dest: /var/tmp/bar
mode: 4755
$ ls -lh /var/tmp/bar
--w--w--wt 1 root root 0 Nov 27 17:58 /var/tmp/bar
Ansible will run this without falling over, but the state of their file is somewhat surprising. What went wrong?
YAML scalars can often be safely expressed without any quotes (e.g.
src: /dev/null
is exactly the same as src: '/dev/null'
) but there
are multiple scalar types and quotes do sometimes matter. Unquoted
numeric values are parsed as ints, while quoted ones are strings:
- debug:
msg: 0755
- debug:
msg: '0755'
- debug:
msg: 4755
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": 493
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "0755"
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": 4755
}
“Where did 493 come from?” you ask. While 0755
looks like a normal
four-digit octal mode that you would pass to chmod
, in YAML it’s
actually a three-digit octal number with a format specifier so
parsing converts this to its decimal equivalent. Going back to our
example task with mode: 4755
we see that it’s parsed as decimal
4755, which is very different from the intended octal 4755.
“Fine,” you say, “I’ll just make a note in our consistently excellent
documentation (which everyone reads and follows) that modes should
always be notated in YAML as octal numbers, and advise my coworker to
use mode: 04755
. Problem solved!” And it is. For now. Then a week
later you decide to install a second executable, and modify the task
again:
- copy:
src: /dev/null
dest: /var/tmp/{{ item.0 }}
mode: "{{ item.1 }}"
loop:
- [ bar, 04755 ]
- [ baz, 04750 ]
$ ls -lh /var/tmp/ba[rz]
-r-xr-S--x 1 root root 0 Nov 27 17:58 /var/tmp/bar
-r-x-wsrw- 1 root root 0 Nov 27 18:03 /var/tmp/baz
Not only are you back to broken files, they’re not even the same
broken as previously! This exciting new behaviour is due to the
introduction of templating. Currently Jinja templating defaults to
producing strings, so your mode is translated from octal to decimal
then transformed into a string, and the various file modules assume
that this numeric string is octal. 04755
becomes "2541"
, which can
be parsed as an octal mode but its effect is very different from your
intent.
Which brings us back to the recommendation: always quote your file modes so that you don’t have to care about any of this. Quoted file modes behave consistently, survive both parsing and templating intact, and don’t provide exciting traps for your future self.
- copy:
src: /dev/null
dest: /var/tmp/foo
mode: '0755'
- copy:
src: /dev/null
dest: /var/tmp/{{ item.0 }}
mode: "{{ item.1 }}"
loop:
- [ bar, '4755' ]
- [ baz, '4750' ]