I am looking for a way to modify a configuration file while preserving local modifications. The format of the configuration file is similar to this:
entry1: This is a local
entry2: modification.
entry3:
The file consists of a variable number of keys (entry1, entry2, entry3, and possibly later entry4, etc. - it could go up to 100, and in the future maybe even up to 2000 or so) that should be set up by Ansible, followed by additional options that another program will add to the configuration file.
I want Ansible to be able to add new keys to the file, while preserving any of the existing local modifications.
The most natural fit seems lineinfile - but unfortunately it only gives me the option of either preserving the local modifications (using backref=yes), or adding new keys (backref=yes will not add new lines).
Is there a good way to accomplish what I need?
I can modify the existing entries with lineinfile and backref enabled:
- lineinfile:
path: myfile.conf
regexp: "^{{ item }}: (.*)"
backref: yes
line: "{{ item }}: \1"
with_items:
- entry1
- entry2
- entry3
- entry4
But this will not add entry4 to my file.
Or I can use backref: no
- lineinfile:
path: myfile.conf
regexp: "^{{ item }}: (.*)"
backref: no
line: "{{ item }}:"
with_items:
- entry1
- entry2
- entry3
- entry4
But this will clobber local modifications for entry1, entry2 and entry3.
Or I could change the regexp:
- lineinfile:
path: myfile.conf
regexp: "^"
backref: no
line: "{{ item }}:"
with_items:
- entry1
- entry2
- entry3
- entry4
But this will of course add each key on every run.
I've also looked at using a template (but have not found an easy way to use it to manipulate an existing file).
Of course I can write my own Python module, but there must be an easier way to do this?
If you are modifying well known configuration files, you can use Augeas, with this ansible augeas plugin: https://github.com/paluh/ansible-augeas
This is one example how to use augeas:
Another option is to use lineinfile command to make sure the line that you want is available in the file: http://docs.ansible.com/ansible/latest/lineinfile_module.html
example:
I found a way how to do this. The trick is to first use grep to find only the missing entries.
grep will error out if the file does not exist, so first I am creating the file:
copy: dest: myfile.conf content: "" force: no # set mode, owner, group to taste
Now use grep to find only the missing items. Grep will return 0 if the entry already exists, or 1 when it doesn't. Normally, a return code of 1 means failure to Ansible. failed_when: True changes that. Since this is only retrieving information and not changing anything, it should never report as "changed", so changed_when needs to be set as well.
command: 'grep "^{{ item }}" myfile.conf' with_items: - entry1 - entry2 - entry3 - entry4 failed_when: False changed_when: False register: grep_output
Grep will produce the output into the registered variable grep_output. When used in a loop, grep_output will contain an array called results, which contains one hash for each item from the loop. In that array, we find all the information we need: both the return code (called rc) and the original item from the loop (called item).
Now we can add only the missing entries by checking for rc. I'm not sure if the regexp is even required here.
lineinfile: path: myfile.conf regexp: "^{{ item.item }} *(.*)" insertafter: EOF line: '{{ item.item }}' state: present when: "item.rc > 0" with_items: "{{ grep_output.results }}"