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.
Sahil Suri
Latest posts by Sahil Suri (see all)
- Google Cloud basics: Activate Cloud Shell - May 19, 2021
- Create persistent swap partition on Azure Linux VM - May 18, 2021
- DNF, YUM and RPM package manager comparison - May 17, 2021
- Introduction to the aptitude package manager for Ubuntu - March 26, 2021
- zypper package management tool examples for managing packages on SUSE Linux - March 26, 2021