Merge Variables from Different Groups in Ansible

Posted on Jul 13 2017 in Configuration Management

Now and then it would be handy if Ansible let you merge variables from multiple sources. I had a use case where an ansible role would configure VRRP for floating IPs on servers. Most of the time, this works great: I set a variable or two in a group or host var context, and the role uses those to accomplish its purpose. However, I had a case where I wanted two floating IPs on one server, and these were two logically unrelated reasons.

Because I had two unrelated reasons for doing the same config, I didn't want to just mash the variables together -- which context do I pollute? Will I understand why I had to do that later? Yuck. I found myself coveting Chef's ability to set attributes that get merged. Could I do that with Ansible?

I had found some people with the same question in an Ansible GitHub Issue, where I came across an interesting piece of code by Leapfrog Online: an Ansible action plugin called merge_vars.

merge_vars lets you merge other vars with a common suffix into a new variable using an Ansible action, which is executed on the executor, not remotely. So, my VRRP variable setup was able to look something like this:

# nfs-cluster-a.yml
---
nfs_a_vip__to_merge:
  nfs_a_vip:
    virtual_router_id: 51
    shared_iface: "{{ ansible_default_ipv4.interface }}"
    auth_pass: "{{ vault_nfs_vip_pass }}"
    shared_vips:
      - "10.3.4.11"
# ipa-failover.yml
---
ipa_vip__to_merge:
  ipa_vip:
    virtual_router_id: 50
    shared_iface: "{{ ansible_default_ipv4.interface }}"
    auth_pass: "{{ vault_ipa_vip_pass }}"
    shared_vips:
      - "10.3.0.40"

I set floating IP configuration in two unrelated group_vars files.

Then, I was able to bring them together in a single play:

# floating-ips.yml
---
- hosts: all
  become: yes
  pre_tasks:
    - name: Merge floating IP configs
      merge_vars:
        suffix_to_merge: vip__to_merge
        merged_var_name: merged_vips
        expected_type: 'dict'
  roles:
    - role: uZer.keepalived
      keepalived_vrrp_instances: "{{ merged_vips }}"

Note the use of the merge_vars action. When this is called, it creates a new variable merged_vips, which is equivalent to if I had constructed it like this manually:

merged_vips:
  nfs_a_vip:
    virtual_router_id: 51
    shared_iface: "{{ ansible_default_ipv4.interface }}"
    auth_pass: "{{ vault_nfs_vip_pass }}"
    shared_vips:
      - "10.3.4.11"
  ipa_vip:
    virtual_router_id: 50
    shared_iface: "{{ ansible_default_ipv4.interface }}"
    auth_pass: "{{ vault_ipa_vip_pass }}"
    shared_vips:
      - "10.3.0.40"