Ansible. override single dictionary key [duplicate]

I am using ansible to manage configuration as for production, as well as for vagrant box. I have file with default values: group_vars/all.

---
env: prod
wwwuser: www-data

db:
    root_pwd: root_pwd
    pdo_driver: pdo_mysql
    host: localhost
    name: test
    user: test
    pwd: test
    charset: utf8

domain: somedomain
projectdir: /var/www/application
webrootdir: "{{ projectdir }}/web"

In host_vars/vagrantbox I want tohave something like:

db:
    root_pwd: super_easy_password

But this one is overriding completely db dictrionary, while I want to override single key. How to achieve that?

UPDATE 1 Just checked with ansible.cfg:

[defaults]
host_key_checking=false
hash_behaviour=merge

groups_vars/all

db:
    root_pwd: some_strong_pwd
    pdo_driver: pdo_mysql
    host: localhost
    name: dbname
    user: dbuser
    pwd: some password
    charset: utf8

host_vars/vagrantbox

db:
    root_pwd: root

I am getting following error:

One or more undefined variables: 'dict object' has no attribute 'name'

What I do wrong?


Solution 1:

By default, Ansible overrides variables at the first level. If you want to be able to merge dictionaries, you have to change your ansible.cfg file and set :

hash_behaviour=merge

(the default value being replace).

Note that the Ansible team does not recommend this. I guess this is a real dividing setting between users. A kind of decision that is done once for all : when you start using this feature, you can not go back, and you probably can not share your playbook with replace-type people.

However, you can still benefit from the playbooks out there (I don't hink playbooks use replace behaviour as a "feature"). It's like having an AB blood type, being an universal receiver... but since the magic usually happens at variable resolution, not inside tasks or templates, I think it is often possible to share your roles without any changes.

If you need to override a single key from, let's say, role parameters, you'll have to pass parameters in some convoluted way.

For instance, to override post_max_size and upload_max_size keys in a php5 dictionnary for a specific role, you'll have to do it this way :

- { role: php5-fpm, php5: { post_max_size: 40M,
                            upload_max_filesize: 20M }}

This being said, I use merge behaviour since the beginning, and I'm pretty happy with it. It is very handy to keep variables organised.

Solution 2:

Starting with Ansible 2.0, you can use the Jinja2 combine filter to merge YAML hashes/dictionaries without having to set hash_behavior=merge at a global level in your ansible.cfg file.

Relevant docs: https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#sts=Combining%20hashes/dictionaries%C2%B6

Solution 3:

The best way I have found is to use variables as the value of the dictionary item, and override that. I find this allows simple and powerful variable precedence with regards to ansible's order of variable

role/parent/defaults/main.yml

---
root_pw_value: ParentPassword

parent_dict:
  - root_pw: "{{ root_pw_value }}"

role/child/defaults/main.yml

Note: role/child/meta/main.yml contains dependencies: - { role: parent }

---
root_pw_value: ChildPassword

play-me.yml

---
  - hosts: all
    roles:
      - child

roles/parent/tasks/main.yml & roles/child/tasks/main.yml

- debug: var=parent_dict

Run ansible -i localhost, --connection="local" play-me.yml and you get the following output:

PLAY [all] ******************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [localhost]

TASK: [parent | debug var=parent_dict] **************************************** 
ok: [localhost] => {
    "var": {
        "parent_dict": [
            {
                "root_pw": "ParentPassword"
            }
        ]
    }
}

TASK: [child | debug var=parent_dict] ***************************************** 
ok: [localhost] => {
    "var": {
        "parent_dict": [
            {
                "root_pw": "ChildPassword"
            }
        ]
    }
}

PLAY RECAP ******************************************************************** 
localhost                  : ok=3    changed=0    unreachable=0    failed=0

And these are defaults. If you specify root_pw_value at more specific levels of precedence, such as inventory group/host variables, role variables, extra_vars on the command line, or anything from the precedence order[0] you'll get those.

[0] http://docs.ansible.com/ansible/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable