Introduction

I recently came across a situation wherein I needed to search for a string/word in a file and then append some piece of text after the matched string. I’m sure that there are many ways to accomplish this using Ansible but in this post, I’ll demonstrate what I used to get this done. I’ll also show you what didn’t work so you might get an idea about what not to do if you are trying to modify text in files using lineinfile module which I found to be awesome by the way. You may refer to the official documentation for lineinfile right here. This module ensures a particular line is in a file, or replace an existing line using a back-referenced regular expression. This is primarily useful when you want to change a single line in a file only.

For the purpose of this demonstration, I’ll be using a Centos 7 system with Ansible 2.8 installed on it. Let’s just verify that before we get started.

[ssuri@linuxnix ~]$ cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
[ssuri@linuxnix ~]$ ansible --version
ansible 2.8.2
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/home/ssuri/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/site-packages/ansible
executable location = /usr/bin/ansible
python version = 2.7.5 (default, Jun 20 2019, 20:27:34) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
[ssuri@linuxnix ~]$

Scenario

Given below is the file that I’d like to modify.

[ssuri@linuxnix ansible_playbooks]$ cat /etc/systemd/system/node_exporter.service
WorkingDirectory=/opt/prometheus/
ExecStart=/opt/prometheus/node_exporter --collector.systemd $exporterArg
[Install]

[ssuri@linuxnix ansible_playbooks]$

My requirement is to add this piece of text –collector.textfile.directory /root/node_exporter after the string $exporterArg.

Here’s the playbook I wrote initially to accomplish this task.

[ssuri@linuxnix ansible_playbooks]$ cat didnt_work.yml
---
- name: a test play
hosts: all
gather_facts: no

tasks:
- name: node-service replace in file
lineinfile:
dest: "/etc/systemd/system/node_exporter.service"
regexp: '\$exporterArg'
line: "-collector.textfile.directory /root/node_exporter"

The playbook sounds simple right. Look for $exporterArg string in the file /etc/systemd/system/node_exporter and add the text -collector.textfile.directory /root/node_exporter after it.

But this is what happened when I ran the playbook.

[ssuri@linuxnix ansible_playbooks]$ ansible-playbook -i localhost, didnt_work.yml -b

PLAY [a test play] *****************************************************************************************************

TASK [node-service replace in file] ************************************************************************************
changed: [localhost]

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

[ssuri@linuxnix ansible_playbooks]$

Now when I look at the file I find this.

[ssuri@linuxnix ansible_playbooks]$ cat /etc/systemd/system/node_exporter.service
WorkingDirectory=/opt/prometheus/
-collector.textfile.directory /root/node_exporter
[Install]

[ssuri@linuxnix ansible_playbooks]$

The playbook replaced the entire line containing the string $exporterArg with -collector.textfile.directory /root/node_exporter. That isn’t what we wanted to happen. I was also tempted to use the insertafter attribute instead of regexp thinking it might work but didn’t. The reason for that is the insertafter attribute inserts the line of text one line below the matched string.

So, here’s a quick tip about using regexp in the lineinfile module. The way this works is like a simple search and replace like you would use in sed while working in bash. To get around this I changed the line section of my playbook to include the entire updated line of text that I needed and that worked.

[ssuri@linuxnix ansible_playbooks]$ cat testplay.yml
---
- name: a test play
hosts: all
gather_facts: no

tasks:
- name: node-service replace in file
lineinfile:
dest: "/etc/systemd/system/node_exporter.service"
regexp: '\$exporterArg'
line: "ExecStart=/opt/prometheus/node_exporter --collector.systemd $exporterArg --collector.textfile.directory /root/node_exporter"

Now when I executed this playbook I was able to successfully perform the required modification in the file.

[ssuri@linuxnix ansible_playbooks]$ cat /etc/systemd/system/node_exporter.service
WorkingDirectory=/opt/prometheus/
ExecStart=/opt/prometheus/node_exporter --collector.systemd $exporterArg
[Install]

[ssuri@linuxnix ansible_playbooks]$ ansible-playbook -i localhost, testplay.yml -b

PLAY [a test play] *****************************************************************************************************

TASK [node-service replace in file] ************************************************************************************
changed: [localhost]

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

[ssuri@linuxnix ansible_playbooks]$
[ssuri@linuxnix ansible_playbooks]$ cat /etc/systemd/system/node_exporter.service
WorkingDirectory=/opt/prometheus/
ExecStart=/opt/prometheus/node_exporter --collector.systemd $exporterArg --collector.textfile.directory /root/node_exporter
[Install]

Conclusion

We hope that you found this real-world example of using the lineinfile module to be useful and we encourage you to try some interesting uses of this module especially when you decide to transition some of your data manipulation scripts to ansible.

The following two tabs change content below.

Sahil Suri

He started his career in IT in 2011 as a system administrator. He has since worked with HP-UX, Solaris and Linux operating systems along with exposure to high availability and virtualization solutions. He has a keen interest in shell, Python and Perl scripting and is learning the ropes on AWS cloud, DevOps tools, and methodologies. He enjoys sharing the knowledge he's gained over the years with the rest of the community.