Merging variables in Ansible with roles

I'm configuring my environment via the following:

inventory.yml

all:
  children:
    production:
      1.2.3.4
    staging:
      1.2.3.5

In group_vars/all.yml I'm setting up a hash of users which will be added in a playbook. I'd like to be able to add users specifically to group_vars/staging.yml that would be merged with the same setting in my group_vars/all.yml.

Is there a proper way to merge the hash or declare an inheritance in this case?


See DEFAULT_HASH_BEHAVIOUR. Quoting

"This setting controls how variables merge in Ansible. By default, Ansible will override variables in specific precedence orders, as described in Variables. When a variable of higher precedence wins, it will replace the other value. Some users prefer that variables that are hashes (aka ‘dictionaries’ in Python terms) are merged. This setting is called ‘merge’. ..."

For example, given the inventory and group_vars

shell> cat hosts
all:
  children:
    production:
      hosts:
        1.2.3.4
    staging:
      hosts:
        1.2.3.5

shell> cat group_vars/all.yml
users:
  admin:
    shell: /bin/bash
  ansible:
    shell: /bin/sh

shell> cat group_vars/production/users.yml 
users:
  dealer:
    shell: /usr/sbin/nologin

shell> cat group_vars/staging/users.yml 
users:
  tester:
    shell: /bin/bash

The playbook

shell> cat pb.yml
- hosts: all
  tasks:
    - debug:
        var: users

by default overrides the dictionaries. Gives (abridged)

shell> ansible-playbook pb.yml

TASK [debug] ****
ok: [1.2.3.4] => 
  users:
    dealer:
      shell: /usr/sbin/nologin
ok: [1.2.3.5] => 
  users:
    tester:
      shell: /bin/bash

When ANSIBLE_HASH_BEHAVIOUR is set to merge the dictionaries the playbook gives (abridged)

shell> ANSIBLE_HASH_BEHAVIOUR=merge ansible-playbook pb.yml

TASK [debug] ****
ok: [1.2.3.4] => 
  users:
    admin:
      shell: /bin/bash
    ansible:
      shell: /bin/sh
    dealer:
      shell: /usr/sbin/nologin
ok: [1.2.3.5] => 
  users:
    admin:
      shell: /bin/bash
    ansible:
      shell: /bin/sh
    tester:
      shell: /bin/bash

This setting will be deprecated in 2.13.

Quoting the Deprecated detail

"This feature is fragile and not portable, leading to continual confusion and misuse"

Quoting the Deprecated alternatives

"the combine filter explicitly"

For example, rename the common users' dictionary to users_all

shell> cat group_vars/all.yml
users_all:
  admin:
    shell: /bin/bash
  ansible:
    shell: /bin/sh

Then the filter combine merges the dictionaries

shell> cat pb.yml
- hosts: all
  tasks:
    - debug:
        var: users_all|combine(users)

gives (abridged)

shell> ansible-playbook pb.yml

TASK [debug] ****
ok: [1.2.3.4] => 
  users_all|combine(users):
    admin:
      shell: /bin/bash
    ansible:
      shell: /bin/sh
    dealer:
      shell: /usr/sbin/nologin
ok: [1.2.3.5] => 
  users_all|combine(users):
    admin:
      shell: /bin/bash
    ansible:
      shell: /bin/sh
    tester:
      shell: /bin/bash