Merging groups and hostvars in Ansible variables

I was googling for information on creating round-robin DNS records in Route53 with Ansible and came across this post by Daniel Hall who’d figured out a way of merging the his host group list with their hostvars
in Ansible. After a bit of emailing with him I’ve figured a way to do it without having to add an extra filter as he did originally - by digging more into Jinja2.
What’s the problem?
1. A host group in ansible is a list of their addresses - e.g. ["foo.example.com", "bar.example.com"]
2. hostvars
is a dictionary of all the known ‘facts’ for each system, e.g.:
{
"foo.example.com": {
"ansible_default_ipv4": {
"address": "192.168.1.7"
}
},
...
}
3. Given a host group, we want to create a single string that is the comma-separated concatenation of all of their ip4 addresses from hostvars
.
It uses a few of the lesser-used jinja2 features which I’ll talk through.
Here’s the example playbook (test.yml
):
- hosts: all
connection: local
tasks:
- debug:
# Comma-separated string of all ip4 addresses of hosts
msg: |
{% set comma = joiner(",") %}
{% for item in ['127.0.0.1', '127.0.0.1'] -%}
{{ comma() }}{{ hostvars[item].ansible_default_ipv4.address }}
{%- endfor %}
You can test it by running with:
ansible-playbook test.yml -i 127.0.0.1,
Yes, the way you test Ansible without an inventory file is a comma-separated list of hosts - but the comma is needed for just one host.
It gives the output (cows disabled for conciseness):
PLAY [127.0.0.1] **************************************************************
GATHERING FACTS ***************************************************************
ok: [127.0.0.1]
TASK: [debug ] ****************************************************************
ok: [127.0.0.1] => {
"msg": "192.168.1.7,192.168.1.7"
}
PLAY RECAP ********************************************************************
127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0
Success!
How does it work?
First thing to notice is the inner loop - {% for item in ...
. I’m looping on an example array but you can easily replace this with a hosts list. At the end of the loop declaration, a dash is added before the end of the tag - -%}
- this means jinja2 will collapse any whitespace it encounters in the loop.
Secondly, the ipv4 address is output with a simple lookup: hostvars[item].ansible_default_ipv4.address
. There are a lot of facts that ansible gathers; add the task debug: var=hostvars
to the playbook to see all variables available.
Thirdly, there’s this joiner
thing being used. This is a template function in Jinja2 precisely for the purpose of joining strings with separators. It outputs the string you pass it on its 2nd+ calls. It’s better documented on Jinja2 itself.
Anyway, this was fun… especially since I didn’t need to implement round-robin DNS myself in the end! Also, there is maybe an easier way and I don’t know it. If you know, please tell me.
Learn how to make your tests run quickly in my book Speed Up Your Django Tests.
One summary email a week, no spam, I pinky promise.
Tags: ansible