diff --git a/ansible.cfg b/ansible.cfg index 4aa9bc9..e74504a 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -4,6 +4,7 @@ inventory = hosts callback_plugins = plugins/callback +inventory_plugins = plugins/inventory gathering = smart fact_caching = jsonfile diff --git a/plugins/inventory/pyrocufflink.py b/plugins/inventory/pyrocufflink.py new file mode 100644 index 0000000..c492277 --- /dev/null +++ b/plugins/inventory/pyrocufflink.py @@ -0,0 +1,108 @@ +import logging +from xml.etree import ElementTree as etree + +import libvirt +import yaml + +from ansible.plugins.inventory import BaseInventoryPlugin, Constructable + +DOCUMENTATION = r''' +name: pyrocufflink +extends_documentation_fragment: +- constructed +short_description: Pyrocufflink inventory source +version_added: '' +description: +- Dynamic inventory for Pyrocufflink machines +author: +- Dustin C. Hatch +options: + plugin: + description: + Token that ensures this is a source file for the 'pyrocufflink' plugin. + required: True + choices: ['pyrocufflink'] +''' + +log = logging.getLogger('ansible.plugins.inventory.pyrocufflink') + +METADATA_XMLNS = 'http://du5t1n.me/xmlns/libvirt/metadata/' + +DOMAIN_STATES = { + 0: 'no state', + 1: 'the domain is running', + 2: 'the domain is blocked on resource', + 3: 'the domain is paused by user', + 4: 'the domain is being shut down', + 5: 'the domain is shut off', + 6: 'the domain is crashed', + 7: 'the domain is suspended by guest power management', +} + +# Remove the default error handler, which prints to stderr +libvirt.registerErrorHandler(lambda *_: None, None) + + +class InventoryModule(BaseInventoryPlugin, Constructable): + NAME = 'pyrocufflink' + + def parse(self, inventory, loader, path, cache=True): + super().parse(inventory, loader, path, cache=cache) + config_data = self._read_config_data(path) + # set _options from config data + self._consume_options(config_data) + + assert self.inventory + conn = libvirt.open() + for dom in conn.listAllDomains(): + v = {} + + name = v['name'] = dom.name() + try: + v['title'] = dom.metadata(1, None) + except libvirt.libvirtError as e: + log.debug('Could not get title for domain %s: %s', name, e) + inventory_hostname = name + else: + inventory_hostname = v['title'] + self.inventory.add_host(inventory_hostname) + + try: + v['description'] = dom.metadata(0, None) + except libvirt.libvirtError as e: + log.debug('Could not get description for %s: %s', name, e) + + try: + metadata = dom.metadata(2, METADATA_XMLNS) + except libvirt.libvirtError as e: + log.debug( + 'Could not get extended domain metadata for %s: %s', + name, + e, + ) + else: + try: + v['metadata'] = parse_metadata(metadata) + except Exception as e: + log.error('Failed to parse metadata for %s: %s', name, e) + + state, reason = dom.state() + v['state_code'] = state + v['state_reason_code'] = reason + try: + v['state'] = DOMAIN_STATES[state] + except KeyError: + pass + if state == 1: + try: + guest_info = dom.guestInfo() + except libvirt.libvirtError as e: + log.error('Could not get guest info for %s: %s', name, e) + else: + self.inventory.set_variable(name, 'guest_info', guest_info) + self.inventory.set_variable(inventory_hostname, 'libvirt', v) + + +def parse_metadata(metadata: str): + tree = etree.fromstring(metadata) + return yaml.safe_load(tree.text)