Ansible: regex capture groups with lineinfile to preserve yaml indentation

One of the features of the ‘lineinfile‘ regexp parameter is the ability to use regular expression capture groups in the line output.  That allows you to extract values on a found line when constructing the output line.

Specifically, that can mean pulling information such as hostname/port, file path, or preserving the yaml indentation of an original line as I will show in this article.

Please understand there are more yaml native ways of manipulating yaml with Ansible, such as the ansible-role-yedit role or using a tool such as yq from an Ansible shell command, however yaml indentation provides a clear example of regex capture groups for our purpose here.  Assume a sample yaml file:

root:
  child1:
    key1: value1
    #key2: value2
  arrays:
    - animals
        - bear
        - cat
        - dog
        #- elephant
    - servers
        - www.google.com
        - www.ansible.com

A regular expression like below can capture the amount of spacing in front of the item, so that instead of having to hardcode a certain number of spaces, you can simply use the “\1” as representative of the first capture group when creating the output.

'^(\s*)[#]?{{ item.search }}(: )*'

Consider the Ansible playbook below:

- name: Replace values in yml file
      lineinfile:
        backup: no
        backrefs: yes
        state: present
        path: ./my.yml
        regexp: '^(\s*)[#]?{{ item.search }}(: )*'
        line: '\1{{ item.replace }}'
      with_items:
        - { search: 'key1', replace: 'key1: NEWvalue1' }
        - { search: 'key2', replace: 'key2: NEWvalue2' }
        - { search: '\- elephant', replace: '- heffalump' }
        - { search: '\- www.ansible.com', replace: '- www.redhat.com' }

This regular expression can take either scalar (e.g. ‘key2’) or list items (e.g. ‘- elephant’) and update their value and remove the comment hash that may prefix it.  Notice that the output line has the “\1” capture group which preserves the same spacing as the original line.

Running the sample yaml through the ansible playbook results in:

root:
  child1:
    key1: NEWvalue1
    key2: NEWvalue2
  arrays:
    - animals
        - bear
        - cat
        - dog
        - heffalump
    - servers
        - www.google.com
        - www.redhat.com

The ‘key1’ value has been updated, the ‘key2’ has been uncommented, the place where ‘elephant’ was commented out in the animal array has been updated with ‘heffalump’, and www.ansible.com has been replaced with www.redhate.com.

If you have Ansible installed and want to test on a local playbook, download my files here:

This playbook goes one step further and shows how child entries can be added to the yaml at the specified level.  The below adds a ‘key3’ at the same level as ‘key2’ and adds a new ‘deer’ animal right under the cat array item.

    
- name: add item to yml file at correct indentation level
  lineinfile:
    backup: no
    backrefs: yes
    state: present
    path: ./my.yml
    regexp: '^(\s*)[#]?{{ item.search }}(.*)'
    line: '\1{{ item.search }}\2\n\1{{ item.add }}'
  with_items:
    - { search: 'key2', add: 'key3: INSvalue3' }
    - { search: '- cat', add: '- deer' }

 

REFERENCES

ansible.com, lineinfile documentation

stackoverflow.com, yaml editing module for ansible

github kwoodson, ansible-role-yedit