- "Do I Know This Already?" Quiz
- Using Modules to Manipulate Files
- Managing SELinux Properties
- Using Jinja2 Templates
- Summary
- Exam Preparation Tasks
- Review All Key Topics
- Memory Tables
- Define Key Terms
- Review Questions
- Exercise Answers
- Lab 8-1: Generate an /etc/hosts File
- Lab 8-2: Manage a vsftpd Service
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:
Create new files or directories
Create links
Remove files
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.
