radicaled
radicaled

Reputation: 2669

Combine nested dictionaries in ansible

I have 2 different dictionaries that contains application information I need to join together.

landscape_dictionary:

{
  "app_1": {
    "Category": "application",
    "SolutionID": "194833",
    "Availability": null,
    "Environment": "stage",
    "Vendor/Manufacturer": null
  },
  "app_2": false
}

app_info_dictionary:

{
  "app_1": {
    "app_id": "6886817",
    "owner": "[email protected]",
    "prod": [
      "server1"
    ],
    "stage": []
  },
  "app_2": {
    "app_id": "3415012",
    "owner": "[email protected]",
    "prod": [
      "server2"
    ],
    "stage": [
      "server3"
    ]
  }
}

This is the code I'm using to join both dictionaries

- set_fact:
    uber_dict: "{{app_info_dictionary}}"

- set_fact:
    uber_dict: "{{ uber_dict | default ({}) | combine(new_item, recursive=true) }}"
  vars:
    new_item: "{ '{{item.key}}' : { 'landscape': '{{landscape_dictionary[item.key]|default(false)}}' } }"
  with_dict: "{{ uber_dict }}"

- debug:
    msg: "{{item.key}}: {{item.value}}"
  with_dict: "{{uber_dict}}"

If the value in the landscape_dictionary is false it will add it to the uber_dict without problems. But if the value contains information, it fails.

This is the error:

fatal: [127.0.0.1]: FAILED! => {"msg": "|combine expects dictionaries, got u\"{ 'app_1' : { 'landscape': '{u'Category': u'application', u'SolutionID': u'194820', u'Availability': None, u'Environment': 'stage', u'Vendor/Manufacturer': None}' } }\""}

What could be the problem?
Do I need to do an extra combine when I set the var in the set_fact?

Thanks

Upvotes: 0

Views: 7003

Answers (2)

cs982005
cs982005

Reputation: 31

As @DustWolf notes in the comments,

For anyone from the Internet looking for the answer to: "How tp combine nested dictionaries in ansible", the answer is | combine(new_item, recursive=true)

This solves a closely related issue that has baffled myself and my team for months.

I will demonstrate:

Code:

---
- hosts: localhost
  gather_facts: false
  vars:
    my_default_values:
      key1: value1
      key2:
        a: 10
        b: 20
    my_custom_values:
      key3: value3
      key2:
        a: 30
    my_values: "{{ my_default_values | combine(my_custom_values, recursive=true) }}"
  tasks:
    - debug: var=my_default_values
    - debug: var=my_values

Output:

ok: [localhost] => 
  my_values:
    key1: value1
    key2:
      a: 30
    key3: value3

Note how key2 was completely replaced, thus losing key2.b

We changed this to:

my_values: "{{ my_default_values | combine(my_custom_values, recursive=true) }}"

Output:

  my_values:
    key1: value1
    key2:
      a: 30
      b: 20
    key3: value3

Upvotes: 3

mdaniel
mdaniel

Reputation: 33203

This syntax is not legal, or at the very least doesn't do what you think:

new_item: "{ '{{item.key}}' : { 'landscape': '{{landscape_dictionary[item.key]|default(false)}}' } }"

Foremost, ansible will only auto-coerce JSON strings into a dict, but you have used python syntax.

Secondarily, the way to dynamically construct a dict is not to use jinja2 to build up text but rather use the fact that jinja2 is almost a programming language:

new_item: "{{
  {
    item.key: {
      'landscape': landscape_dictionary[item.key]|default(false)
    }
  }
}}"

Any time you find yourself with nested jinja2 interpolation blocks, that is a code smell that you are thinking about the problem too much as text (by nested, I mean {{ something {{nested}} else }})

Upvotes: 0

Related Questions