Krish
Krish

Reputation: 43

Ansible iterate on dict of dict

How i can iterate with Ansible on a dict with contain another dict ?

mydict can contain X "foo" and fooX (like foo1) can contain (at least) one or more params "bar".

I can't really change the vars type, which is currently :

mydict: dict
mydict.foo1: dict
mydict.foo1.params: dict

Ansible playbook : localvars.yml

---
- hosts: localhost
  connection: local
  gather_facts: false

  vars:
    mydict:
      foo1:
        dest: /tmp/foo1.txt
        content: "foo1"
        params:
          bar1:
            alias: foo1foo
          bar2:
            alias: foo1foo
      foo2:
        dest: /tmp/foo2.txt
        content: "foo2"
        params:
          bar1:
            alias: foo1foo

  tasks:
  - name: Print records
    debug:
      msg: |-
          Item {{ item.key }} dest {{ item.value.dest }} content {{ item.value.content }} params {{ item.value.params }}"
    loop: "{{ mydict | dict2items }}"    

Ansible command : ansible-playbook -vv -i localhost, localvars.yml

Expected results :

Getting mydict.fooX.params.barX.alias dynamically for each "barX" params in "fooX"
Getting mydict.fooX.dest dynamically for each "fooX"

Thank you for your help

I tried this :

- name: Using dict2items
  ansible.builtin.debug:
    msg: |-
      "item.key = {{ item.key }}
      item.value = {{ item.value }}"
  loop: "{{ mydict | dict2items }}"

Which is working fine to get all values in mydict like mydict.foo1.dest, but i'm struggling to get all params values

Thanks for your help

Upvotes: 1

Views: 418

Answers (2)

Vladimir Botka
Vladimir Botka

Reputation: 68404

The simplest option is an iteration of the attribute params in an included file, e.g. create a file

shell> cat iterate-params.yml
- debug:
    msg: |-
      item: {{ outer_item.key }}
      dest: {{ outer_item.value.dest }}
      content: {{ outer_item.value.content }}
      param: {{ item }}
  loop: "{{ outer_item.value.params|dict2items }}"
  loop_control:
    label: "{{ outer_item.key }}: {{ item.key }}"

and include it in the loop

    - include_tasks: iterate-params.yml
      loop: "{{ mydict|dict2items }}"
      loop_control:
        loop_var: outer_item
        label: "{{ outer_item.key }}"

gives

TASK [debug] *******************************************************
included: /export/scratch/tmp8/iterate-params.yml for localhost => (item=foo1)
included: /export/scratch/tmp8/iterate-params.yml for localhost => (item=foo2)

TASK [debug] *******************************************************
ok: [localhost] => (item=foo1: bar1) => 
  msg: |-
    item: foo1
    dest: /tmp/foo1.txt
    content: foo1
    param: {'key': 'bar1', 'value': {'alias': 'foo1foo'}}
ok: [localhost] => (item=foo1: bar2) => 
  msg: |-
    item: foo1
    dest: /tmp/foo1.txt
    content: foo1
    param: {'key': 'bar2', 'value': {'alias': 'foo1foo'}}

TASK [debug] *******************************************************
ok: [localhost] => (item=foo2: bar1) => 
  msg: |-
    item: foo2
    dest: /tmp/foo2.txt
    content: foo2
    param: {'key': 'bar1', 'value': {'alias': 'foo1foo'}}

The next option, if you can use the latest filter community.general.dict_kv, is the conversion of the attribute params to a list and iteration with_subelements

    - debug:
        msg: |-
          dest: {{ item.0.dest }}
          content: {{ item.0.content }}
          param: {{ item.1 }}
      with_subelements:
        - "{{ _values|zip(_params)|map('combine') }}"
        - params
      loop_control:
        label: "{{ item.0.dest }}"
      vars:
        _list: "{{ mydict|dict2items }}"
        _values: "{{ _list|map(attribute='value') }}"
        _params: "{{ _list|map(attribute='value.params')|
                           map('dict2items')|
                           map('community.general.dict_kv', 'params') }}"

gives

TASK [debug] ***********************************************************
ok: [localhost] => (item=/tmp/foo1.txt) => 
  msg: |-
    dest: /tmp/foo1.txt
    content: foo1
    param: {'key': 'bar1', 'value': {'alias': 'foo1foo'}}
ok: [localhost] => (item=/tmp/foo1.txt) => 
  msg: |-
    dest: /tmp/foo1.txt
    content: foo1
    param: {'key': 'bar2', 'value': {'alias': 'foo1foo'}}
ok: [localhost] => (item=/tmp/foo2.txt) => 
  msg: |-
    dest: /tmp/foo2.txt
    content: foo2
    param: {'key': 'bar1', 'value': {'alias': 'foo1foo'}}

Upvotes: 1

Zeitounator
Zeitounator

Reputation: 44818

This is a bit ugly but I believe it meets your requirement:

  - name: Print records
    vars:
      _records_to_items: "{{ mydict | dict2items }}"
      _params_to_items: "{{ _records_to_items | map(attribute='value.params') | map('dict2items') }}"
      _consolidate: "{{ _records_to_items | zip(_params_to_items) }}"
      my_msg: |-
        Item: {{ item.0.0.key }}
        dest: {{ item.0.0.value.dest }}
        content {{ item.0.0.value.content }}
        curent param
          name: {{ item.1.key }}
          value: {{ item.1.value }}
    debug:
      msg: "{{ my_msg | split('\n') }}"
    loop: "{{ _consolidate | subelements([1]) }}"
    loop_control:
      label: "Processing params for {{ item.0.0.key }}"

Which gives (running against your above data):

PLAY [localhost] **************************************************************************************************************************************************************************************************

TASK [Print records] **********************************************************************************************************************************************************************************************
ok: [localhost] => (item=Processing params for foo1) => {
    "msg": [
        "Item: foo1",
        "dest: /tmp/foo1.txt",
        "content foo1",
        "curent param",
        "  name: bar1",
        "  value: {'alias': 'foo1foo'}"
    ]
}
ok: [localhost] => (item=Processing params for foo1) => {
    "msg": [
        "Item: foo1",
        "dest: /tmp/foo1.txt",
        "content foo1",
        "curent param",
        "  name: bar2",
        "  value: {'alias': 'foo1foo'}"
    ]
}
ok: [localhost] => (item=Processing params for foo2) => {
    "msg": [
        "Item: foo2",
        "dest: /tmp/foo2.txt",
        "content foo2",
        "curent param",
        "  name: bar1",
        "  value: {'alias': 'foo1foo'}"
    ]
}

PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Upvotes: 1

Related Questions