Using Ansible ini_file with nested dict?

What I am trying to achieve is the following. Suppose we have a playbook similar to the following one:

---

- hosts: local
  tasks:
    - ini_file:
      path: test.ini
      create: yes
      section: "{{???}}"
      option: "{{???}}"
      value: "{{???}}"
    loop: "{{inidict ...???}}"
  vars:
    inidict:
      section1:
        option1: value1
        option2: value2
      section2:
        option1: value1
        option2: value2

The above playbook is invalid because of the lines with ??? in them.

Now my goal here is to map this dict to sections/options/values inside the INI file and get the following INI contents:

[section1]
option1 = value1
option2 = value2

[section2]
option1 = value1
option2 = value2

Unfortunately this appears to be failing as I need to add a secondary level of nesting for the loop to somehow loop over sections in the outer loop and then over options/values in the inner one.

Is there a more elegant or "ansibly" (for lack of an Ansible term corresponding to pythonic) way to achieve this than by wrapping one loop level in a role and iterating over section names using, say include_role, while passing the section name and list of options/values as a variable?

I suppose one alternative could be to "flatten" the dictionary in a way that would yield something that amounts to (YAML):

  vars:
    inicontents:
      - section: section1
        key: option1
        value: value1
      - section: section1
        key: option2
        value: value2
      - section: section2
        key: option1
        value: value1
      - section: section2
        key: option2
        value: value2

I'd just like to keep it DRY and avoid the duplication of the section name, when clearly the dict would perfectly map onto the "data model" of the INI file.


Solution 1:

I don't know of a way to do this with ansible by default other than using your "flatted" version. Fortunately you can have the best of both worlds and write your own filter plugin for flattening the dict for you. Just add somthing like the following to the filter_plugins directory in your project directory or in ~/.ansible/plugins/filter_plugins and name it something like filters.py:

class FilterModule(object):
    def filters(self):
        return { 'ini_dict_flatten': self.ini_dict_flatten }

    def ini_dict_flatten(self, arg):
        ret = []
        for section, subdict in arg.items():
            for key, value in subdict.items():
                ret.append(dict(section=section,
                                key=key,
                                value=value))
        return ret

After this you can use the new filter in your project like this:

- hosts: localhost
  tasks:
    - ini_file:
        path: /tmp/test.ini
        create: yes
        section: "{{ item.section }}"
        option: "{{ item.key }}"
        value: "{{ item.value }}"
      loop: "{{ inidict | ini_dict_flatten }}"
  vars:
    inidict:
      section1:
        option1: value1
        option2: value2
      section2:
        option1: value1
        option2: value2

It helps sometimes if you know python since that way you can write your own plugins for ansible.

EDIT: I will include a much less readable and not recommended solution as well. Since the loop: works with json formatted strings and jinja2 is applied to strings in ansible before they're processed you can use jinja logic to create the "flattend" for you directly in the playbook. This solution is a lot uglier and less reusable than writing your own filter but thecnically still works.

- hosts: localhost
  vars:
    inidict:
      section1:
        option1: value1
        option2: value2
      section2:
        option1: value1
        option2: value2
  tasks:
    - ini_file:
        path: /tmp/test.ini
        create: yes
        section: "{{ item.section }}"
        option: "{{ item.key }}"
        value: "{{ item.value }}"
      loop: >-
        [
        {% for section, subdict in inidict.items() %}
          {% for key, value in subdict.items() %}
            {'section': '{{ section }}',
             'key': '{{ key }}',
             'value': '{{ value }}'}
            {% if not loop.last %}
              ,
            {% endif %}
          {% endfor %}
          {% if not loop.last %}
            ,
          {% endif %}
        {% endfor %}
        ]