Ansible – Avoid duplicates between group and host vars

ansibleansible-playbook

I'm new at using ansible for server management and I need some help managing users and group's membership definition according to host and hosts-group, with a minimum of duplication and a maximum of scalability.
(25 users/20 groups over 50 hosts, with differents "sudo" and " groups membership" at the end).
The idea is to have:

  • "groups_vars" files defining the users (list or hash) to create on each host of the hostgroup.
  • "host_vars" files defining users for a specific host. (At the end, I will need nested groups, more than specific host_vars files)

I need these "*_vars files" contents to be merged and not to be replaced (I understand how "vars precedence" work) because I want to avoid user declaration duplication.
To achieve this, I used hash syntax in "*_vars" files and set "hash_behaviour=merge" in /etc/ansible/ansible.cfg.
Here are my files :
My inventory :

all:
  children:
    type_a:
      hosts:
        vm1:
        vm2:

My debugging playbook :

- hosts: type_a
  tasks:
    - name: Debugging
      debug:
        msg: "{{ users }}"

group_vars/type_a.yaml :

users:
  user1:
    name: user1
  user2:
    name: user2

host_vars/vm1.yaml

users:
  user3_vm1_specific:
    name: user3_vm1_specific

At the end, I need the 3 users on the "vm1" and only "user1" and "user2" on "vm2" and then I will use the vars for the user creation.
Using the merge option (that will be deprecated in newer version of ansible) is working, but doesn't seem to be a best practice.
I searched here on ServFault and on other web sites, and most of the time the solutions are:

  • to duplicate the user definition
    (more than 8 properties for each user and too many hostsgroup: unacceptable.)
  • to use an other name for the second user list, then to assemble both using "{{ user_list1 + user_list2 }}".
    Not very scalable if we want to add many nested groups. You will need to add custom named list each time. It also, makes duplicates if "host_vars" and "group_vars" have the same user defined: it does not merge the content, but declares it twice with a different content each time.

My first solution is working, but using a near-deprecated option.
So what are the best practices in managing vars in this kind of situation ? (already have read the ansible documentation about vars but it didn't really helped me).

Also, maybe ansible tower or foreman could solve this problem ?

Regards
M.

Best Answer

Ansible Jinja2 has a combine() filter, that can be used to merge two dicts together.

In your case, you would have:

group_vars/type_a.yaml

group_users:
  user1:
    name: user1
  user2:
    name: user2

host_vars/vm1.yaml

host_users:
  user3_vm1_specific:
    name: user3_vm1_specific

And debugging playbook:

- hosts: type_a
  tasks:
    - name: Debugging
      debug:
        msg: "{{ group_users | combine(host_users) }}"

I think the merge strategy is deprecated because it is all or nothing and it can cause undesired side-effects.