Created
November 13, 2025 14:27
-
-
Save pushrbx/dd651e9ff677650b394bb4cc95b05636 to your computer and use it in GitHub Desktop.
Ansible inventory plugin for vagrant
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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