merging dictionaries in ansible

2020-01-29 07:09发布

问题:

I'm currently building a role for installing PHP using ansible, and I'm having some difficulty merging dictionaries. I've tried several ways to do so, but I can't get it to work like I want it to:

# A vars file:
my_default_values:
  key = value

my_values:
  my_key = my_value


# In a playbook, I create a task to attempt merging the
# two dictionaries (which doesn't work):

- debug: msg="{{ item.key }} = {{ item.value }}"
  with_dict: my_default_values + my_values

# I have also tried:

- debug: msg="{{ item.key }} = {{ item.value }}"
  with_dict: my_default_values|union(my_values)

# I have /some/ success with using j2's update,
# but you can't use j2 syntax in "with_dict", it appears.
# This works:

- debug: msg="{{ my_default_values.update(my_values) }}"

# But this doesn't:

- debug: msg="{{ item.key }} = {{ item.value }}"
  with_dict: my_default_values.update(my_values)

Is there a way to merge two dictionaries, so I can use it with "with_dict"?

回答1:

In Ansible 2.0, there is a Jinja filter, combine, for this:

- debug: msg="{{ item.key }} = {{ item.value }}"
  with_dict: "{{ my_default_values | combine(my_values) }}"


回答2:

If you want hash merging I would turn the hash merging feature on in ansible. In your ansible config file turn hash merging on.

With hash_behaviour=merge you can have two var files with the same variable name:

defaults.yml:

values:
  key: value

overrides.yml:

values:
  my_key: my_value

In order for the two vars to be merged you will need to include both var files:

ansible-playbook some-play.yml ... -e@defaults.yml  -e@overrides.yml

And you will end up with this:

TASK: [debug var=values] ********************************************************
ok: [localhost] => {
    "values": {
        "key": value,
        "my_key": my_value
    }
}

Calling update on a variable can be done in Jinja but in general it will be messy, I wouldn't do it outside of your templates and even then try to avoid it altogether.



回答3:

It is now possible to use the anchor and extend features of YAML:

---
- hosts: localhost
  vars:
    my_default_values: &def
      key: value
    my_values:
      <<: *def
      my_key: my_value
  tasks:
    - debug: var=my_default_values
    - debug: var=my_values

Result:

TASK [debug]
ok: [localhost] => {
    "my_default_values": {
        "key": "value"
    }
}

TASK [debug] 
ok: [localhost] => {
    "my_values": {
        "key": "value", 
        "my_key": "my_value"
    }
}

I have no idea why this was not mentioned before.



回答4:

If you need the merged dictionary a few times, you can set it to a new "variable":

- set_fact: _my_values="{{ my_default_values|combine(my_values) }}"

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


回答5:

Try this role from Ansible Galaxy.

I did it some time ago for same reason. It can deep merge dictionaries from several vars files and set custom precedence of merging.

This role can work under Ansible 2.0+



回答6:

>>> key = 'default key'
>>> value = 'default value'
>>> my_key = 'my key'
>>> my_value = 'my value'
>>>
>>> my_default_values = {key: value}
>>> print my_default_values
{'default key': 'default value'}
>>>
>>> my_values = {my_key: my_value}
>>> print my_values
{'my key': 'my value'}
>>>
>>> with_dict = my_default_value.copy()
>>> print with_dict
{'default key': 'default value'}
>>> with_dict.update(my_values)
>>> print with_dict
{'default key': 'default value', 'my key': 'my value'}