I like Ansible. It’s not perfect but nothing is. Recently I was playing around with the tinc vpn system and wanted a way to set a custom fact per virtual machine based on the vms public ip address. I figured the best way to do that would be to setup a custom fact. It turns out there isn’t that much documentation on just how to do that, or I simply can’t find it. So I’m going to describe what I did in order to setup and use a custom fact.
I just wanted a simple script to parse the ip address of the server and return a private ip based on the last octet of the address.
This is the script I ended up using.
#!/bin/bash
IP_INDEX=`hostname -i | cut -f 4 -d "."`
cat <<EOF
{
"vpn_ip" : "10.0.0.$IP_INDEX"
}
EOF
Using ansible I put that file into the /etc/ansible/facts.d directory. Note it has to be executable, return json, and end with the .fact extension.
ubuntu@trusty2:~$ cd /etc/ansible/facts.d/
ubuntu@trusty2:/etc/ansible/facts.d$ ./tinc_facts.fact
{
"vpn_ip" : "10.0.0.22"
}
Pretty simple so far.
So, the first time that file is loaded onto the server Ansible setup won’t have it yet (unless it was loaded up in a previous role) so if you do load the facts file in the same role you’ll have to use setup in the task list to load the custom fact.
Here’s a snippet of my playbook. It’s creating the facts.d directory, copying over the script, and finally re-running setup with the ansible_local filter.
I should be registering a variable and only reload the ansible_local if the facts.d scripts have changed on this particular run.
- name: ensure custom facts directory exists
file: >
path=/etc/ansible/facts.d
recurse=yes
state=directory
- name: install custom fact module for IP address
template: >
src=tinc_facts.sh.j2
dest=/etc/ansible/facts.d/tinc_facts.fact
mode=0755
- name: reload ansible_local
setup: filter=ansible_local
Otherwise, on the next Ansible run it’ll be available.
It seems as though the fact gets loaded into the ansible_local namespace under the name of the name of the fact script.
$ ansible -m setup trusty2 | grep -A 4 ansible_local
"ansible_local": {
"tinc_facts": {
"vpn_ip": "10.0.0.22"
}
},
I use the fact like this in my playbook.
- name: ensure subnet ip address is properly set in tinc host file
lineinfile: >
dest=/etc/tinc/{{ vpn_name }}/hosts/{{ ansible_hostname }}
line="Subnet = {{ ansible_local.tinc_facts.vpn_ip }}/32"
create=yes
notify:
- restart tinc
And there we go, relatively simple custom facts for Ansible. Now we can do all sorts of silly things like make new ips based on old ips. haha.