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