Skip to content

Instantly share code, notes, and snippets.

@pushrbx
Created November 13, 2025 14:27
Show Gist options
  • Select an option

  • Save pushrbx/dd651e9ff677650b394bb4cc95b05636 to your computer and use it in GitHub Desktop.

Select an option

Save pushrbx/dd651e9ff677650b394bb4cc95b05636 to your computer and use it in GitHub Desktop.
Ansible inventory plugin for vagrant
DOCUMENTATION = r"""
name: vagrant
extends_documentation_fragment:
- constructed
short_description: Vagrant inventory source
author:
- pushrbx <[email protected]>
description:
- Get inventory hosts from Vagrant.
options:
plugin:
description: token that ensures this is a source file for the 'vagrant' plugin.
required: True
choices: ['vagrant', 'pushrbx.vagrant.vagrant']
inventory_hostname:
description: >
What to register as the inventory hostname. If set to "hostname" the "HostName" section of the ssh config
returned by vagrant is used. If set to "host" the "Host" section of the ssh config returned by vagrant is
used.
type: string
choices:
- hostname
- host
default: host
vagrantfile_path:
description: Path to the Vagrantfile.
type: string
default: ./Vagrantfile
"""
EXAMPLES = r"""
plugin: vagrant
inventory_hostname: hostname
vagrantfile_path: ${MOLECULE_SCENARIO_DIRECTORY}/Vagrantfile
"""
import os
import re
import subprocess
import shutil
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
from ansible.module_utils.common.text.converters import to_text
from ansible.errors import AnsibleError
from io import StringIO
from paramiko import SSHConfig, SSHConfigDict
class InventoryModule(BaseInventoryPlugin, Constructable):
NAME = "vagrant"
mapping = {}
def list_running_boxes(self):
vagrantfile_path = self.get_option("vagrantfile_path")
if self.templar:
vagrantfile_path = self.templar.template(vagrantfile_path)
vagrantfile_path = os.path.expandvars(vagrantfile_path)
vagrantfile_dir = os.path.dirname(vagrantfile_path)
output = to_text(
subprocess.check_output(["vagrant", "global-status", "--prune"]),
errors='surrogate_or_strict'
).split('\n')
for line in output:
match = re.search(r"^([a-zA-Z0-9]+)\s+([^/ ]+).*(running)\s+(.+?)$", line)
if match:
box_id = str(match.group(1))
box_name = str(match.group(2))
box_up = str(match.group(3))
box_dir = str(match.group(4)).strip()
if box_dir != vagrantfile_dir:
continue
if box_name == "default":
pretty_box_name = box_dir
else:
pretty_box_name = box_name
if box_up == "running":
self.mapping[box_id] = pretty_box_name
return self.mapping
@staticmethod
def get_a_ssh_config(box_id, box_name) -> SSHConfigDict:
output = to_text(subprocess.check_output(["vagrant", "ssh-config", box_id]), errors='surrogate_or_strict')
output = re.sub(r'^(\[fog\]\[WARNING\].*)', '', output, flags=re.MULTILINE)
config = SSHConfig()
config.parse(StringIO(output))
host_config = config.lookup(box_name)
host_config["host"] = box_name
if len(host_config) == 1:
host_config = config.lookup('default')
for identity in host_config['identityfile']:
if os.path.isfile(identity):
host_config['identityfile'] = identity
return host_config
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path, cache=cache)
config_data = self._read_config_data(path)
self._consume_options(config_data)
if shutil.which("vagrant") is None:
raise AnsibleError("The vagrant inventory plugin requires vagrant to be installed")
if self.get_option("vagrantfile_path") is None:
raise AnsibleError("The vagrant inventory plugin requires vagrantfile_path to be set")
for box_id, box_name in self.list_running_boxes().items():
try:
ssh_config = self.get_a_ssh_config(box_id, box_name)
except Exception as e:
self.inventory.set_variable(box_name, "ERROR", str(e))
self.inventory.set_variable(box_id, "ERROR", str(e))
ssh_config = {
"host": box_name,
"hostname": box_name,
}
inventory_hostname = {
"host": ssh_config.get("host"),
"hostname": ssh_config.get("hostname"),
}.get(self.get_option("inventory_hostname"))
inventory_hostname_alias = {
"host": ssh_config.get("hostname"),
"hostname": ssh_config.get("host"),
}.get(self.get_option("inventory_hostname"))
self.inventory.add_host(inventory_hostname)
self.inventory.add_group(inventory_hostname_alias)
self.inventory.add_child(inventory_hostname_alias, inventory_hostname)
self.inventory.set_variable(inventory_hostname, "ansible_user", ssh_config.get("user", "vagrant"))
self.inventory.set_variable(inventory_hostname, "ansible_host", ssh_config.get("hostname"))
self.inventory.set_variable(inventory_hostname, "ansible_port", ssh_config.get("port", 22))
if os.path.exists(ssh_config.get("identityfile")):
self.inventory.set_variable(inventory_hostname, "ansible_private_key_file", ssh_config.get("identityfile"))
# Get variables for compose
self.inventory.hosts[inventory_hostname].address = ssh_config.get("hostname")
variables = self.inventory.hosts[inventory_hostname].get_vars()
# Set composed variables
self._set_composite_vars(
self.get_option('compose'),
variables,
inventory_hostname,
self.get_option('strict'),
)
# Add host to composed groups
self._add_host_to_composed_groups(
self.get_option('groups'),
variables,
inventory_hostname,
self.get_option('strict'),
)
# Add host to keyed groups
self._add_host_to_keyed_groups(
self.get_option('keyed_groups'),
variables,
inventory_hostname,
self.get_option('strict'),
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment