Chapter 6: Deploying Files to Managed Hosts
Modifying and copying files to hosts
Most configuration work comes down to files: put one in place, fix its owner and permissions, change a line, or remove it. Ansible has a small set of modules for exactly these jobs. Each one describes the state a file should be in, so running it twice is safe.
- Choose the right module to create, copy, edit, fetch, check or remove a file.
- Set ownership, permissions and SELinux type as part of the same task.
- Edit a file in place with
lineinfileandblockinfile, and know what makes them idempotent. - Read a file's status with
statand use it in later tasks.
The file modules
Almost everything you need ships with ansible-core, in the ansible.builtin collection:
| Module | Use it to |
|---|---|
copy | Put a file from the control node (or a few lines of inline text) onto managed hosts, with attributes. |
file | Set owner, group, mode, SELinux context and timestamps; create or remove files, directories and links. |
lineinfile | Make sure one particular line is present, replacing a matching line if there is one. |
blockinfile | Insert, update or remove several lines as one marked block. |
fetch | Copy a file from managed hosts back to the control node: copy in reverse. |
stat | Read facts about a file, like the Linux stat command. Changes nothing. |
The ansible.posix collection adds two more:
| Module | Use it to |
|---|---|
synchronize | Synchronise files and directories with rsync. |
patch | Apply a patch file with GNU patch. |
Pick a job to see the module and a task that does it:
ansible.posix is in /usr/share/ansible/collections on workstation (section 1.6), so synchronize and patch resolve without an execution environment. synchronize also needs the rsync package on both ends: the lab profile installs it on every VM.
Creating files and setting attributes
ansible.builtin.file with state: touch behaves like the touch command: it creates an empty file if there is none, and the same task sets who owns it and what they may do with it.
- name: Touch a file and set permissions
ansible.builtin.file:
path: /path/to/file
owner: user1
group: group1
mode: '0640'
state: touch[user@host ~]$ ls -l file
-rw-r-----. user1 group1 0 Nov 25 08:00 fileThe attribute arguments owner, group, mode and the SELinux ones are shared by file, copy, template and several other modules. Learn them once.
Getting the mode right
Click the bits to build a mode. Both the octal and the symbolic form are accepted:
SELinux type
A file created in the wrong place keeps the SELinux type of where it was made. Here, a file meant for a Samba share still has the type of a home directory:
[user@host ~]$ ls -Z samba_file
-rw-r--r--. owner group unconfined_u:object_r:user_home_t:s0 samba_filesetype fixes that, in the way chcon would:
- name: SELinux type is set to samba_share_t
ansible.builtin.file:
path: /path/to/samba_file
setype: samba_share_t[user@host ~]$ ls -Z samba_file
-rw-r--r--. owner group unconfined_u:object_r:samba_share_t:s0 samba_fileCopying files to managed hosts
ansible.builtin.copy looks for src on the control node, relative to the playbook, and also in a files/ directory next to it.
- name: Copy a file to managed hosts
ansible.builtin.copy:
src: file
dest: /path/to/fileBy default force: true applies: if the remote file exists but its content differs, it is overwritten. With force: false the file is copied only when it is missing, which suits files that people are expected to edit afterwards.
Fetching files from managed hosts
ansible.builtin.fetch goes the other way. A typical use is collecting a log, or an SSH public key from a reference machine before handing it out to others:
- name: Retrieve SSH key from reference host
ansible.builtin.fetch:
src: "/home/{{ user }}/.ssh/id_rsa.pub"
dest: "files/keys/{{ user }}.pub"Several hosts may each have a file with the same name, so by default fetch builds a separate path for each host. Switch flat to see the difference:
Editing a file in place
When a file already exists and you only care about part of it, change just that part.
One line: ansible.builtin.lineinfile.
- name: Add a line of text to a file
ansible.builtin.lineinfile:
path: /path/to/file
line: 'Add this line to the file'
state: presentSeveral lines: ansible.builtin.blockinfile.
- name: Add additional lines to a file
ansible.builtin.blockinfile:
path: /path/to/file
block: |
First line in the additional block of text
Second line in the additional block of text
state: presentblockinfile surrounds its text with marker comments. They are how a later run recognises the block instead of adding a second copy:
# BEGIN ANSIBLE MANAGED BLOCK
First line in the additional block of text
Second line in the additional block of text
# END ANSIBLE MANAGED BLOCKUse the marker argument when the file needs a different comment character, or when one file holds more than one managed block.
Run each module against a small file, change the text, and run it twice:
Removing files
state: absent removes a file, link or directory (with its contents).
- name: Make sure a file does not exist on managed hosts
ansible.builtin.file:
dest: /path/to/file
state: absentChecking a file with stat
ansible.builtin.stat gathers facts about one file: whether it exists, its type, owner, mode, size and checksum. Register the result and use it in later tasks.
- name: Verify the checksum of a file
ansible.builtin.stat:
path: /path/to/file
checksum_algorithm: md5
register: result
- ansible.builtin.debug:
msg: "The checksum of the file is {{ result.stat.checksum }}"TASK [debug] *******************************************************************
ok: [hostname] => {
"msg": "The checksum of the file is 5f76590425303022e933c43a7f2092a3"
}Everything sits under the stat key of the registered variable. Browse a sample result; the panel shows how to reference the value you select:
stat.exists is the one you will use most, in conditions such as when: not result.stat.exists.
Synchronising directories
ansible.posix.synchronize wraps rsync, which is much faster than copy for large trees. By default the source is on the control node and the destination on the managed host.
- name: Synchronize local file to remote files
ansible.posix.synchronize:
src: file
dest: /path/to/file- Which module copies `/var/log/secure` from each managed host to the control node?
- A task sets `mode: 644` on a file. What goes wrong?
- You run a `blockinfile` task a second time with the same block. What happens?
- A file has `setype: samba_share_t` set by `ansible.builtin.file`. Someone runs `restorecon` on it. What is its type now?
- With `force: false`, when does `copy` write the file?
Takeaway
Choose the right module to create, copy, edit, fetch, check or remove a file. Use the chapter lab to check this on a real host.