Anand Patel
Anand Patel

Reputation: 6431

Ansible - How to keep appending new keys to a dictionary when using set_fact module with with_items?

I want to add keys to a dictionary when using set_fact with with_items. This is a small POC which will help me complete some other work. I have tried to generalize the POC so as to remove all the irrelevant details from it.

When I execute following code it is shows a dictionary with only one key that corresponds to the last item of the with_items. It seems that it is re-creating a new dictionary or may be overriding an existing dictionary for every item in the with_items. I want a single dictionary with all the keys.

Code:

---
- hosts: localhost
  connection: local
  vars:
      some_value: 12345
      dict: {}
  tasks:
     - set_fact: {
          dict: "{
             {{ item }}: {{ some_value }}
             }"
            }
       with_items:
          - 1
          - 2
          - 3
     - debug: msg="{{ dict }}"

Upvotes: 30

Views: 91403

Answers (4)

Andreas
Andreas

Reputation: 851

This can also be done without resorting to plugins, tested in Ansible 2.2.

---
- hosts: localhost
  connection: local
  vars:
    some_value: 12345
    dict: {}
  tasks:
  - set_fact:
      dict: "{{ dict | combine( { item: some_value } ) }}"
    with_items:
      - 1
      - 2
      - 3
  - debug: msg="{{ dict }}"

Alternatively, this can be written without the complex one-liner with an include file.

  tasks:
  - include: append_dict.yml
    with_items: [1, 2, 3]

append_dict.yml:

- name: "Append dict: define helper variable"
  set_fact:
    _append_dict: "{ '{{ item }}': {{ some_value }} }"

- name: "Append dict: execute append"
  set_fact:
    dict: "{{ dict | combine( _append_dict ) }}"

Output:

TASK [debug]
*******************************************************************
ok: [localhost] => {
    "msg": {
        "1": "12345",
        "2": "12345",
        "3": "12345"
    }
}

Single quotes ' around {{ some_value }} are needed to store string values explicitly.

This syntax can also be used to append from a dict elementwise using with_dict by referring to item.key and item.value.

Manipulations like adding pre- and postfixes or hashes can be performed in the same step, for example

    set_fact:
      dict: "{{ dict | combine( { item.key + key_postfix: item.value + '_' +  item.value | hash('md5') } ) }}"

Upvotes: 58

Stamatis Katsaounis
Stamatis Katsaounis

Reputation: 1

Another solution could be this one, tested in Ansible 2.9.6.

This solutions adds the extra benefit that you do not have to declare _dict beforehand in vars section. This is achieved by the | default({}) pipe which ensures that the loop will not fail in the first iteration when _dict is empty.

In addition, the renaming of dict to _dict is necessary since the dict is a special keyword reserved for <class 'dict'>. Referenced (unfortunately only at devel branch yet) here.

---
- hosts: localhost
  connection: local
  vars:
    some_value: 12345
  tasks:
  - set_fact:
      _dict: "{{ _dict | default({}) | combine( { item: some_value } ) }}"
    with_items:
      - 1
      - 2
      - 3
  - debug: msg="{{ _dict }}"

Upvotes: 0

Alexandre Babouin
Alexandre Babouin

Reputation: 71

this does not seems to work any more on ansible 2.5

---
- hosts: localhost
  connection: local
  vars:
    some_value: 12345
    dict: {}
  tasks:
  - set_fact:
      dict: "{{ dict | combine( { item: some_value } ) }}"
    with_items:
      - 1
      - 2
      - 3
  - debug: msg="{{ dict }}"

returns only last value {"dict":{"3": "some value"}}

I suggest you could do this :

- set_fact:
    __dict: |
        {% for item in  [1,2,3] %}
        {{item}}: "value"
        {% endfor %}

- set_fact:
    final_dict: "{{__dict|from_yaml}}"

- debug: 
  var: final_dict

Upvotes: 0

Ben Whaley
Ben Whaley

Reputation: 34436

Use a filter plugin.

First, make a new file in your ansible base dir called filter_plugins/makedict.py.

Now create a new function called "makedict" (or whatever you want) that takes a value and a list and returns a new dictionary where the keys are the elements of the list and the value is always the same.

class FilterModule(object):
     def filters(self):
         return { 'makedict': lambda _val, _list: { k: _val for k in _list }  }

Now you can use the new filter in the playbook to achieve your desired result:

- hosts: 127.0.0.1
  connection: local
  vars:
      my_value: 12345
      my_keys: [1, 2, 3]
  tasks:
    - set_fact: my_dict="{{ my_value | makedict(my_keys) }}"
    - debug: msg="{{ item.key }}={{ item.value }}"
      with_dict: "{{my_dict}}"

You can customize the location of the filter plugin using the filter_plugins option in ansible.cfg.

Upvotes: 9

Related Questions