1#!/usr/local/bin/python3.8
2"""
3Vagrant external inventory script. Automatically finds the IP of the booted vagrant vm(s), and
4returns it under the host group 'vagrant'
5
6Example Vagrant configuration using this script:
7
8    config.vm.provision :ansible do |ansible|
9      ansible.playbook = "./provision/your_playbook.yml"
10      ansible.inventory_path = "./provision/inventory/vagrant.py"
11      ansible.verbose = true
12    end
13"""
14
15# Copyright (C) 2013  Mark Mandel <mark@compoundtheory.com>
16#               2015  Igor Khomyakov <homyakov@gmail.com>
17#
18# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
19
20from __future__ import (absolute_import, division, print_function)
21__metaclass__ = type
22
23#
24# Thanks to the spacewalk.py inventory script for giving me the basic structure
25# of this.
26#
27
28import sys
29import os.path
30import subprocess
31import re
32from paramiko import SSHConfig
33from optparse import OptionParser
34from collections import defaultdict
35import json
36
37from ansible.module_utils._text import to_text
38from ansible.module_utils.six.moves import StringIO
39
40
41_group = 'vagrant'  # a default group
42_ssh_to_ansible = [('user', 'ansible_user'),
43                   ('hostname', 'ansible_host'),
44                   ('identityfile', 'ansible_ssh_private_key_file'),
45                   ('port', 'ansible_port')]
46
47# Options
48# ------------------------------
49
50parser = OptionParser(usage="%prog [options] --list | --host <machine>")
51parser.add_option('--list', default=False, dest="list", action="store_true",
52                  help="Produce a JSON consumable grouping of Vagrant servers for Ansible")
53parser.add_option('--host', default=None, dest="host",
54                  help="Generate additional host specific details for given host for Ansible")
55(options, args) = parser.parse_args()
56
57#
58# helper functions
59#
60
61
62# get all the ssh configs for all boxes in an array of dictionaries.
63def get_ssh_config():
64    return dict((k, get_a_ssh_config(k)) for k in list_running_boxes())
65
66
67# list all the running boxes
68def list_running_boxes():
69
70    output = to_text(subprocess.check_output(["vagrant", "status"]), errors='surrogate_or_strict').split('\n')
71
72    boxes = []
73
74    for line in output:
75        matcher = re.search(r"([^\s]+)[\s]+running \(.+", line)
76        if matcher:
77            boxes.append(matcher.group(1))
78
79    return boxes
80
81
82# get the ssh config for a single box
83def get_a_ssh_config(box_name):
84    """Gives back a map of all the machine's ssh configurations"""
85
86    output = to_text(subprocess.check_output(["vagrant", "ssh-config", box_name]), errors='surrogate_or_strict')
87    config = SSHConfig()
88    config.parse(StringIO(output))
89    host_config = config.lookup(box_name)
90
91    # man 5 ssh_config:
92    # > It is possible to have multiple identity files ...
93    # > all these identities will be tried in sequence.
94    for id in host_config['identityfile']:
95        if os.path.isfile(id):
96            host_config['identityfile'] = id
97
98    return dict((v, host_config[k]) for k, v in _ssh_to_ansible)
99
100
101# List out servers that vagrant has running
102# ------------------------------
103if options.list:
104    ssh_config = get_ssh_config()
105    meta = defaultdict(dict)
106
107    for host in ssh_config:
108        meta['hostvars'][host] = ssh_config[host]
109
110    print(json.dumps({_group: list(ssh_config.keys()), '_meta': meta}))
111    sys.exit(0)
112
113# Get out the host details
114# ------------------------------
115elif options.host:
116    print(json.dumps(get_a_ssh_config(options.host)))
117    sys.exit(0)
118
119# Print out help
120# ------------------------------
121else:
122    parser.print_help()
123    sys.exit(0)
124