How can I loop over a list of dicts and their content lists

Viewed 79

I have the following var

---

- hosts: all

  vars:
    new_service:
      name: test
      Unit:
      - option: Description
        value: "Testname"
      Service:
      - option: ExecStart
        value: "/usr/bin/python3 -m http.server 8080"
      - option: WorkingDirectory
        value: /home/test/testservice/html

I want to be able to use the ini_file module to create a service template so that the above var is converted into the following ini file

[Unit]
Description=Testname

[Service]
ExecStart=/usr/bin/python3 -m http.server 8080
WorkingDirectory=/home/test/testservice/html

I cannot figure out how to loop over this. I was thinking to use the product() so as to loop over nested lists, maybe something like this?

  - name: "Create new unit section of service file"
    ini_file:
      path: "~{{ USERNAME }}/.config/systemd/user/{{ new_service[name] }}"
      section: "{{ item.0 }}"
      option: "{{ item.1.option }}"
      value: "{{ item.1.value }}"
    loop: "{{ ['unit', 'service'] | product({{ new_service[item.0] }})"

But I do not believe item is defined in the loop definition itself

(The reason I'm going with ini_file rather than template is because I want the service file creation to be able to handle any number of fields on demand)

1 Answers

You can still use a template to have a variable number of sections and options. Using loop with ini_file here is not efficient IMO. The only real use case would be if you need to keep the original contents of the file only adding new ones. But performance will be dramatically lower than a single template, especially if your have a lot of elements.

The only difficulty I see is that you have a name attribute in your dict which is not a section title. But it can be easily ruled out.

template.j2

{% for section in new_service %}
{% if section != 'name' %}
[{{ section }}]
{% for option in new_service[section] %}
{{ option.option }}={{ option.value }}
{% endfor %}

{% endif %}
{% endfor %}

Back to original question

If you really want to go through the loop route, it is still possible but will require quite a bit of effort with your actual data structure (loop/set_fact/... to finally get a single loopable structure).

If possible, I would change it to the following:

new_service:
  name: test
  sections:
    - section: Unit
      options:
        - option: Description
          value: "Testname"
    - section: Service
      options:
        - option: ExecStart
          value: "/usr/bin/python3 -m http.server 8080"
        - option: WorkingDirectory
          value: /home/test/testservice/html

And you can then directly loop through this structure using the subelements lookup. Note that "name" (on top level) is not a var but a string identifier for your service name value and should be used as such (fixed in my below example):

  - name: "Create new unit section of service file"
    ini_file:
      path: "~{{ USERNAME }}/.config/systemd/user/{{ new_service.name }}"
      section: "{{ item.0.section }}"
      option: "{{ item.1.option }}"
      value: "{{ item.1.value }}"
    loop: "{{ lookup('subelements', new_service.sections, 'options') }}"

You can easily adapt my first example template to this new data structure as well if needed.

Related