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 %}
]