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.

The file modules

Almost everything you need ships with ansible-core, in the ansible.builtin collection:

ModuleUse it to
copyPut a file from the control node (or a few lines of inline text) onto managed hosts, with attributes.
fileSet owner, group, mode, SELinux context and timestamps; create or remove files, directories and links.
lineinfileMake sure one particular line is present, replacing a matching line if there is one.
blockinfileInsert, update or remove several lines as one marked block.
fetchCopy a file from managed hosts back to the control node: copy in reverse.
statRead facts about a file, like the Linux stat command. Changes nothing.

The ansible.posix collection adds two more:

ModuleUse it to
synchronizeSynchronise files and directories with rsync.
patchApply 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 file

The 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_file

setype 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_file

Copying 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/file

By 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: present

Several 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: present

blockinfile 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 BLOCK

Use 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: absent

Checking 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

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.