Ansible/Vagrant/Dynamic Inventory
From charlesreid1
This page covers how to manage a dynamic inventory file when using Vagrant boxes. This avoids the need to edit a hosts file by hand.
Contents
Static vs dynamic inventory
Ansible/Vagrant/Static Inventory - static inventory requires the hosts file (containing the list of machines that Ansible is managing) be kept up to date by hand. This can be a burden if details are changing or if things are allocated automatically.
Ansible/Vagrant/Dynamic Inventory - dynamic inventory uses something like an API or a database to obtain information about the machines that Ansible is managing. This makes scaling and generalization much easier.
Managing a dynamic inventory file for Vagrant boxes
The following script is a dynamic inventory script run by Ansible. This example script is written in Python. It takes two input arguments, --host=X
and --list
, and interacts with Vagrant to return the requested information about the managed hosts to Ansible.
Getting List of Hosts
Start with how to get a list of hosts for the --list
flag.
Vagrant provides a command to show info about all running vagrant machines:
vagrant status
This info can be used to create the list of hosts for the dynamic inventory file. A more convenient format for parsing is:
vagrant status --machine-readable
which prints everything in CSV format. The code:
def list_running_hosts(): cmd = "vagrant status --machine-readable" status = subprocess.check_output(cmd.split()).rstrip().decode('utf-8') hosts = [] for line in status.split('\n'): (_, host, key, value) = line.split(',')[:4] if key == 'state' and value == 'running': hosts.append(host) return hosts
Getting Machine Configuration Details
Now we need to get host-specific details for the --host=X
flag.
Next we can use the vagrant ssh-config
command to get info about the machine-specific configuration details of each machine, which is necessary for Ansible to connect to them. To parse SSH config files, we can use a Python library called pamiko.
def get_host_details(host): cmd = "vagrant ssh-config {}".format(host) p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) config = paramiko.SSHConfig() config.parse(p.stdout) c = config.lookup(host) return {'ansible_host': c['hostname'], 'ansible_port': c['port'], 'ansible_user': c['user'], 'ansible_private_key_file': c['identityfile'][0]}
Parsing user arguments
A quick argparser tutorial for the flags we're interested in parsing:
def parse_args(): parser = argparse.ArgumentParser(description="Vagrant inventory script") group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--list', action='store_true') group.add_argument('--host') return parser.parse_args()
Now we can call this function and use args.list or args.host to access the values of the flags.
Putting it all together
Here is the final dynamic inventory script:
(In the ansible repo under contrib/inventory: https://github.com/ansible/ansible/tree/devel/contrib/inventory)
dynamic_vagrant.py:
#!/usr/bin/env python # Adapted from Mark Mandel's implementation # https://github.com/ansible/ansible/blob/stable-2.1/contrib/inventory/vagrant.py # License: GNU General Public License, Version 3 <http://www.gnu.org/licenses/> import argparse import json import paramiko import subprocess import sys def parse_args(): parser = argparse.ArgumentParser(description="Vagrant inventory script") group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--list', action='store_true') group.add_argument('--host') return parser.parse_args() def list_running_hosts(): cmd = "vagrant status --machine-readable" status = subprocess.check_output(cmd.split()).rstrip().decode('utf-8') hosts = [] for line in status.split('\n'): (_, host, key, value) = line.split(',')[:4] if key == 'state' and value == 'running': hosts.append(host) return hosts def get_host_details(host): cmd = "vagrant ssh-config {}".format(host) p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) config = paramiko.SSHConfig() config.parse(p.stdout) c = config.lookup(host) return {'ansible_host': c['hostname'], 'ansible_port': c['port'], 'ansible_user': c['user'], 'ansible_private_key_file': c['identityfile'][0]} def main(): args = parse_args() if args.list: hosts = list_running_hosts() json.dump({'vagrant': hosts}, sys.stdout) else: details = get_host_details(args.host) json.dump(details, sys.stdout) if __name__ == '__main__': main()
Running the dynamic inventory script
Once we've written the dynamic inventory script and we're ready to run Ansible using it, we can pass the inventory script using the -i flag:
ansible -i dynamic_vagrant.py -u ubuntu myvagrantbox -m ping
Flag