Lines with characters and numbers can be used to describe the beauties of this world and can also be used to describe the configuration of your web server. It's a simple concept with infinite forms and uses. When you're handling files with lines in Ansible, there's one go-to module that covers most use cases. The lineinfile module is a simple yet powerful tool that you can use in a variety of ways to improve your playbooks. This article lists nine of the best ways it can be used.
Getting Started
Lineinfile is a task module, so we need a playbook to put it in. If you’re following along, you can use this playbook to get you started:
---
- name: Lineinfile Examples
hosts: localhost
tasks:
You can copy and paste this into a directory of your choosing. The name doesn’t matter, so feel free to go for something like main.yml. To run the playbook, you run "ansible-playbook main.yml". All of the examples below are able to run locally just fine, but you can use them for remote machines in the same way.
Placing a Line in a File
As the name of this module suggests, the lineinfile module is surprisingly good at placing a line in a file. The most basic example of this would be using only the two parameters, the line of content and the path of the file that this line needs to get.
- name: Place line
lineinfile:
line: Hello World
path: hello.txt
create: true
If you don’t provide the create parameter, this task will fail if the file does not exist. In most cases, you want to leave this out. This way, you can be alerted when you want to change a file that is expected to exist.
Remove a Line From a File
Handling lines in files also includes removing lines. With the lineinfile module, this is easy to do. To ensure a specific line is absent in the file, you can use the state parameter.
- name: Remove line
lineinfile:
line: Hello World
path: hello.txt
state: absent
By default, the state parameter is set to "present." That means that if you're adding or changing lines, then setting this parameter is not required.
Changing a Line
Adding or removing plain lines has a limited set of real-life use cases. In most situations, you need a bit more finesse. A very common use case is changing configuration files. To define which line you want to change, you can use the regexp parameter, which takes regular expressions. If you search for the key you want to set, you can prevent having the key defined twice. If you have the "PermitEmptyPasswords yes" line, and you only define "line: PermitEmptyPasswords no", you’ll end up with a completely new line at the end of the file. To actually change the existing line, using the regular expression "^PermitEmptyPasswords" makes sense. The ^ character makes sure the line you’re changing starts with that key. This way you don’t accidentally change comments instead, for example.
- name: Change SSH daemon configuration
lineinfile:
line: PermitEmptyPasswords no
regexp: ^PermitEmptyPasswords
path: /etc/ssh/sshd_config
Note that for changing the file "/etc/ssh/sshd_config", you need the right permissions. In most cases, you would need to add "become: true’" on the play or task level for this task to be successful.
Changing Multiple Lines
If you want to set multiple configuration items, using a loop makes this very straightforward:
- name: Change SSH daemon configuration
lineinfile:
line: "{{ item.line }}"
regexp: "{{ item.regexp }}"
path: /etc/ssh/sshd_config
loop:
- line: GatewayPorts no
regexp: ^GatewayPorts
- line: X11Forwarding yes
regexp: ^X11Forwarding
Adding a Line to a Specific Location
For some configuration files, it doesn’t really matter where you put a certain configuration item. In other cases, location is essential. The lineinfile module provides the tools to define the context in which a line needs to be present by using the insertbefore and insertafter parameters. You can set these parameters to EOF or BOF, respectively, to place the configuration item at the end or at the beginning of the file. The other way of using these parameters is by using a regular expression to which the reference line should adhere. Let’s say you have the following configuration file:
# ansible.cfg
[defaults]
<----------- Desired location of config line *
gathering = implicit
[colors]
highlight = white
<----------- EOF and default location of new line *
If you set a configuration item without specifying the location, it will by default place the item at the end of the file. In this case, that means you will find it under the colors category. To place a line in the defaults category, you would provide the insertafter parameter:
- name: Set the remote_port value in defaults category
lineinfile:
insertafter: \\[defaults\\]
line: remote_port = 22
path: ansible.cfg
For some configuration file formats, like the ini format, there are other modules that might provide better support, like the ini_file module.
Writing to Log Files
You can use the lineinfile module for more than configuration files. You can also use it for appending lines to log files, for example. Of course, you can save the complete Ansible logs too, but having granular and explicit logging can help a lot when debugging or auditing. This is how you could do that in your playbook:
- name: Write message to logging
lineinfile:
path: application.log
line: "{{ ansible_date_time.iso8601 }} - Application successfully installed."
create: true
It results in a log file that looks like this:
2022-01-21T14:03:23Z - Application successfully installed.
2022-01-21T14:09:31Z - Application successfully installed.
2022-01-21T14:10:23Z - Application successfully installed.
2022-01-21T14:10:51Z - Application successfully installed.
You can use variables to make log lines like this even more useful.
Backing Up Files You Change
Changing files can be, or feel, dangerous. The lineinfile module can help provide you peace of mind by backing up the file that you’re changing. To ensure a backup is made, you'll want to set the backup parameter to "true":
- name: Set the remote_port value in defaults category
lineinfile:
insertafter: \\[defaults\\]
line: remote_port = 22
path: ansible.cfg
backup: true
After the task has been executed, you’ll find a file with a name similar to "ansible.cfg.12578.2022-01-21@15:00:09~" in the same directory as the changed file. The backup only happens when the file is actually changed, so rerunning the task will not fill up the directory with backups.
Verifying Changes
Some applications or systems will not work correctly if the configuration file has invalid entries. For example, it can be risky to change entries in the sudoers file, as this can lead to a broken system. To prevent situations like this, the lineinfile module has the validate parameter. You can use it to provide a command to verify the file you’re changing:
- name: Enable passwordless sudo
lineinfile:
path: sudoers
line: 'myusername ALL=(ALL) NOPASSWD: ALL'
create: true
validate: /usr/sbin/visudo -cf %s
Since this is an example, this task is just changing the sudoers file in the playbook directory. To actually change the sudoers configuration, you’d need to change the path to "/etc/sudoers", or, preferably, to "/etc/sudoers.d/myusername".
Checking the Presence of a Line in a File
If you want to check if a certain line is present in a file, you can use the check_mode attribute. Note that this is not a parameter of the module, but of the task itself. This can be useful if you want to run some tasks based on whether a line is present in a file.
- name: Check for line
lineinfile:
line: Hello World
path: hello.txt
check_mode: true
register: line_check
- name: Print message
debug:
msg: The line "Hello World" was not found in hello.txt.
when: line_check.changed
Using the check_mode parameter ensures that the task does not actually change the file, but instead it reports what it would do. You can then check for the return key "changed" in order to see if it would have been changed. If it would, the line is not present in the file.
Wrapping Things Up
The possibilities of using lineinfile are virtually endless. By combining the simplicity of a plain text line with the power of regular expressions, the lineinfile module makes it possible to do both simple and complex manipulations of plain text files. It’s typically a module where you’ll find fresh ways of using it, even after years of experience with it. Hopefully, these nine examples will help you make your playbooks a little bit better.
To learn more about Ansible, including how to keep your playbooks secure, check out this post.
This post was written by Ruben Jongejan. Automation is Ruben’s thing. He likes to be on the cutting edge—the place where infrastructure, automation, and application development touch. That's why he spends most of his days in the cloud.