Home > Articles

This chapter is from the book

This chapter is from the book

Using Modules to Manipulate Files

Managing files is an important task for Linux administrators. Different types of manipulations are performed on files on a frequent basis. They include managing files, managing file contents, and moving files around. In this section you learn how to use Ansible modules to apply these different tasks.

File Module Manipulation Overview

Many modules are available to manage different aspects of files. Table 8-2 provides an overview of some of the most commonly used file modules.

Table 8-2 File Manipulation Module Overview

Module

Use

copy

Copies files to remote locations

fetch

Fetches files from remote locations

file

Manages files and file properties

acl

Works with file system ACLs

find

Finds files based on any property

lineinfile

Manages lines in text files

blockinfile

Manages blocks in text files

replace

Replaces strings in text files based on regex

synchronize

Performs rsync-based synchronization tasks

stat

Retrieves file or file system status

Most of these modules are discussed in the following sections. When using file-related modules, you might need a module that is not discussed here. If that is the case, the best approach is to use the ansible-doc command. When you use this command on any module, you always see related modules mentioned in the SEE ALSO section of the documentation.

Managing File Attributes

If you need to work with file attributes, the stat module and the file module come in handy. The stat module enables you to retrieve file status information. Because this module gets status information and is not used to change anything, you mainly use it to check specific file properties and perform an action if the properties are not set as expected. In Listing 8-1 you can see a playbook that uses the stat and debug modules to explore what exactly the stat module is doing. Listing 8-2 shows the output shown while running ansible-playbook listing81.yaml.

Listing 8-1 Exploring the stat Module

---
- name: stat module tests
  hosts: ansible1
  tasks:
  - stat:
      path: /etc/hosts
    register: st
  - name: show current values
    debug:
      msg: current value of the st variable is {{ st }}

Listing 8-2 Running ansible-playbook listing81.yaml

[ansible@control ~]$ ansible-playbook listing81.yaml

PLAY [stat module tests] *********************************************

TASK [Gathering Facts] ***********************************************
ok: [ansible1]

TASK [stat] **********************************************************
ok: [ansible1]

TASK [show current values] *******************************************
ok: [ansible1] => {
    "msg": "current value of the st variable is {'changed':
False, 'stat': {'exists': True, 'path': '/etc/hosts', 'mode':
'0644', 'isdir': False, 'ischr': False, 'isblk': False, 'isreg':
True, 'isfifo': False, 'islnk': False, 'issock': False, 'uid': 0,
'gid': 0, 'size': 158, 'inode': 16801440, 'dev': 64768, 'nlink':
1, 'atime': 1586230060.147566, 'mtime': 1536580263.0, 'ctime':
1584958718.8117938, 'wusr': True, 'rusr': True, 'xusr': False, 'wgrp':
False, 'rgrp': True, 'xgrp': False, 'woth': False, 'roth': True,
'xoth': False, 'isuid': False, 'isgid': False, 'blocks': 8, 'block_
size': 4096, 'device_type': 0, 'readable': True, 'writeable': True,
'executable': False, 'pw_name': 'root', 'gr_name': 'root', 'checksum':
'7335999eb54c15c67566186bdfc46f64e0d5a1aa', 'mimetype': 'text/plain',
'charset': 'us-ascii', 'version': '408552077', 'attributes': [],
'attr_flags': ''}, 'failed': False}"
}

PLAY RECAP ***********************************************************
ansible1                    : ok=3    changed=0    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0

As you can see from Listing 8-2, the stat module returns many file properties. It tests which permission mode is set, whether it is a link, which checksum is set on the file, and much more. For a complete list of output data, you can consult the documentation as provided while running ansible-doc stat.

Based on the output that is provided, a conditional test can be performed. The sample playbook in Listing 8-3 shows how this can be done and how the playbook can write a message if the expected permissions mode is not set.

Listing 8-3 Performing File State Tests with the stat Module

---
- name: stat module tests
  hosts: ansible1
  tasks:
  - command: touch /tmp/statfile
  - stat:
      path: /tmp/statfile
    register: st
  - name: show current values
    debug:
      msg: current value of the st variable is {{ st }}
  - fail:
      msg: "unexpected file mode, should be set to 0640"
    when: st.stat.mode != '0640'

As you can see in the playbook output in Listing 8-4, the playbook fails with the unexpected file mode message. Also notice the warning in the Listing 8-4 output: it tells you that there is a better solution to do what you wanted to do here. This happens on multiple occasions when you might have selected a module that is not the best solution for the task you want to perform. Remember: Using the command module will work in almost all cases, but often a better solution is available.

Listing 8-4 Running ansible-playbook listing83.yaml Result

[ansible@control ~]$ ansible-playbook listing83.yaml

PLAY [stat module tests] *********************************************

TASK [Gathering Facts] ***********************************************
ok: [ansible1]
TASK [command] *******************************************************
[WARNING]: Consider using the file module with state=touch rather
than running 'touch'. If you need to use command because file is
insufficient you can add 'warn: false' to this command task or set
'command_warnings=False' in ansible.cfg to get rid of this message.
changed: [ansible1]

TASK [stat] **********************************************************
ok: [ansible1]

TASK [show current values] *******************************************
ok: [ansible1] => {
    "msg": "current value of the st variable is {'changed':
False, 'stat': {'exists': True, 'path': '/tmp/statfile', 'mode':
'0644', 'isdir': False, 'ischr': False, 'isblk': False, 'isreg':
True, 'isfifo': False, 'islnk': False, 'issock': False, 'uid': 0,
'gid': 0, 'size': 0, 'inode': 51440456, 'dev': 64768, 'nlink': 1,
'atime': 1586253087.057596, 'mtime': 1586253087.057596, 'ctime':
1586253087.057596, 'wusr': True, 'rusr': True, 'xusr': False, 'wgrp':
False, 'rgrp': True, 'xgrp': False, 'woth': False, 'roth': True,
'xoth': False, 'isuid': False, 'isgid': False, 'blocks': 0, 'block_
size': 4096, 'device_type': 0, 'readable': True, 'writeable': True,
'executable': False, 'pw_name': 'root', 'gr_name': 'root', 'checksum':
'da39a3ee5e6b4b0d3255bfef95601890afd80709', 'mimetype': 'inode/x-
empty', 'charset': 'binary', 'version': '158303785', 'attributes': [],
'attr_flags': ''}, 'failed': False}"
}

TASK [fail] **********************************************************
fatal: [ansible1]: FAILED! => {"changed": false, "msg": "unexpected
file mode, should be set to 0640"}

PLAY RECAP ***********************************************************
ansible1                    : ok=4    changed=1    unreachable=0
failed=1    skipped=0    rescued=0    ignored=0

In the earlier examples in this section, you saw how you can use the stat module to show different types of file properties. Based on the output of the stat module, you may use the file module to set specific file properties. In Listing 8-5 you can see how the playbook from Listing 8-3 is rewritten to automatically set the desired permissions state.

Listing 8-5 Using the file Module to Correct File Properties Discovered with stat

---
- name: stat module tests
  hosts: ansible1
  tasks:
  - command: touch /tmp/statfile
  - stat:
      path: /tmp/statfile
    register: st
  - name: show current values
    debug:
      msg: current value of the st variable is {{ st }}
  - name: changing file permissions if that's needed
    file:
      path: /tmp/statfile
      mode: 0640
    when: st.stat.mode != '0640'

Managing File Contents

If you need to manage file contents, multiple modules can be useful. The find module enables you to find files, just like the Linux find command. The lineinfile module enables you to manipulate lines in files, and blockinfile enables you to manipulate complete blocks of text. Also don’t forget the copy module. We look at it in the next section, but you can also use it to copy a specified text to a file. For managing text operations on files, however, it is recommended that you use lineinfile or blockinfile instead because these give more options to specify where exactly the text should be written to.

Listing 8-6 shows an example where lineinfile is used to change a string, based on a regular expression.

Listing 8-6 Changing File Contents Using lineinfile

---
- name: configuring SSH
  hosts: all
  tasks:
  - name: disable root SSH login
    lineinfile:
      dest: /etc/ssh/sshd_config
      regexp: "^PermitRootLogin"
      line: "PermitRootLogin no"
    notify: restart sshd


  handlers:
  - name: restart sshd
    service:
      name: sshd
      state: restarted

As you can see in Listing 8-6, lineinfile uses the dest key to specify the filename. Next, a regular expression is used to search for lines that have text starting with PermitRootLogin. If this regular expression is found, it is changed into the line PermitRootLogin no.

You can use the lineinfile module to manipulate a single line in a file. In some cases you have to manage multiple lines in a file. In that case, you can use the blockinfile module. Listing 8-7 provides an example.

Listing 8-7 Using blockinfile to Manipulate Multiple Lines of Text

---
- name: modifying file
  hosts: all
  tasks:
  - name: ensure /tmp/hosts exists
    file:
      path: /tmp/hosts
      state: touch
  - name: add some lines to /tmp/hosts
    blockinfile:
      path: /tmp/hosts
      block: |
        192.168.4.110 host1.example.com
        192.168.4.120 host2.example.com
      state: present

Based on what you’ve learned so far, the use of blockinfile should be easy to understand. Just remember the use of the | after block:. This character is used to specify that the next couple of lines should be treated as lines, adding the newline character to the end of the line. Alternatively, you could use block: >, but that would add one long line to the destination file.

Notice that when blockinfile is used, the text specified in the block is copied with a start and end indicator. See Listing 8-8 for an example:

Listing 8-8 Resulting File Modification by blockinfile

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.
localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.
localdomain6
192.168.4.200.   control.example.com.      control
192.168.4.201    ansible1.example.com      ansible1
192.168.4.202    ansible2.example.com      ansible2

# BEGIN ANSIBLE MANAGED BLOCK
192.168.4.110 host1.example.com
192.168.4.120 host2.example.com
# END ANSIBLE MANAGED BLOCK

Creating and Removing Files

In an earlier example in this chapter you saw how the command module was used to create a new file by using the Linux touch command. While running this playbook, you saw a warning that you shouldn’t do it this way, but you should use the file module instead, and that is totally right.

You can use the file module to perform some pretty common tasks:

key_topic_icon.jpg
  • squ.jpg Create new files or directories

  • squ.jpg Create links

  • squ.jpg Remove files

  • squ.jpg Set permissions and ownership

Listing 8-9 shows a sample playbook where the file module is used to create a new directory and in that directory create an empty file, after which the same file module is used again to remove the directory recursively. This approach is not very useful, but at least it shows you some of the most common uses of the file module.

Listing 8-9 Creating and Removing Files with the file Module

---
- name: using the file module
  hosts: ansible1
  tasks:
  - name: create directory
    file:
      path: /newdir
      owner: ansible
      group: ansible
      mode: 770
      state: directory
  - name: create file in that directory
    file:
      path: /newdir/newfile
      state: touch
  - name: show the new file
    stat:
      path: /newdir/newfile
    register: result
  - debug:
      msg: |
           This shows that newfile was created
           "{{ result }}"
  - name: removing everything again
    file:
      path: /newdir
      state: absent

In Listing 8-9, you can see that the last task is configured to remove a directory. Just specifying the path to the directory and state: absent recursively removes the directory. You don’t need to specify any other options here, and the recurse key also is not required.

Moving Files Around

Three modules are particularly useful for moving files around. The copy module copies a file from the Ansible control host to a managed machine. The fetch module enables you to do the opposite, and the synchronize module performs Linux rsync-like tasks, ensuring that a file from the control host is synchronized to a file with that name on the managed host. The main difference between copy and synchronize is that the copy module always creates a new file, whereas the synchronize module updates a current existing file. In Listing 8-10 you can see how these modules are used.

Listing 8-10 Moving a File Around with Ansible

---
- name: file copy modules
  hosts: all
  tasks:
  - name: copy file demo
    copy:
      src: /etc/hosts
      dest: /tmp/
  - name: add some lines to /tmp/hosts
    blockinfile:
      path: /tmp/hosts
      block: |
        192.168.4.110 host1.example.com
        192.168.4.120 host2.example.com
      state: present
  - name: verify file checksum
    stat:
      path: /tmp/hosts
      checksum_algorithm: md5
    register: result
  - debug:
      msg: "The checksum of /tmp/hosts is {{ result.stat.checksum }}"
  - name: fetch a file
    fetch:
      src: /tmp/hosts
      dest: /tmp/

After running the playbook in Listing 8-10, you might expect to find the file /tmp/hosts on the Ansible control machine. This, however, is not the case, and the reason is easy to understand. Ansible playbooks typically are used on multiple hosts, so if a file is fetched from a managed host, it must be stored in a unique location. To guarantee the uniqueness, Ansible creates a subdirectory for each managed host in the dest directory and puts the file that fetch has copied from the remote host in that subdirectory. So the result of the playbook in Listing 8-10 is stored as /tmp/ansible1/hosts and /tmp/ansible2/hosts. You practice working with files in Exercise 8-1.

Pearson IT Certification Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from Pearson IT Certification and its family of brands. I can unsubscribe at any time.