From 31a52f84f712fbc060ff638401330083c39e4c1b Mon Sep 17 00:00:00 2001 From: Robin Pierre Cordier Date: Wed, 9 Feb 2022 18:06:11 -0500 Subject: [PATCH] Add: Kheop inventory plugin --- plugins/inventory/kheops.py | 252 ++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 plugins/inventory/kheops.py diff --git a/plugins/inventory/kheops.py b/plugins/inventory/kheops.py new file mode 100644 index 0000000..313985c --- /dev/null +++ b/plugins/inventory/kheops.py @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Robin Cordier <> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# pylint: disable=raise-missing-from +# pylint: disable=super-with-arguments + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +from pprint import pprint + +DOCUMENTATION = ''' + name: kheops + plugin_type: inventory + short_description: Kheops host classifier + requirements: + - requests >= 1.1 + description: + - This plugin does not create hosts, but it assign new variables from Kheops to hosts + - The main usecase of this plugin is to act as a node + - Get host variables from Kheops (http://kheops.io/) + - + - This plugin get all hosts from inventory and add lookep up keys + - It's important to make this inventory source loaded after all other hosts has been declared. To force whit behavior, you can name your inventory file by `zzz_` to be sure it will be the last one to be parsed + extends_documentation_fragment: + - inventory_cache + - constructed + options: + plugin: + description: token that ensures this is a source file for the C(kheops) plugin. + required: True + choices: ['kheops', 'barbu_it.ansible_kheops.kheops'] + + + # Query configuration + # ========================== + query_namespace: + description: + - The Kheops namespace to use + default: 'default' + env: + - name: ANSIBLE_KHEOPS_NAMESPACE + query_scope: + description: + - A hash containing the scope to use for the request, the values will be resolved as Ansible facts. + - Use a dot notation to dig deeper into nested hash facts. + default: {} + query_keys: + description: + - A list of keys to lookup + default: {} + + + # Instance configuration + # ========================== + configuration: + description: + - Path to Kheops configuration yaml file + default: 'kheops.yml' + env: + - name: ANSIBLE_KHEOPS_CONFIG + log_level: + description: + - Khéops logging level + choices: ['DEBUG', 'INFO', 'WARNING', 'ERROR'] + default: 'WARNING' + env: + - name: ANSIBLE_KHEOPS_LOG_LEVEL + + # turfu # Client configuration + # turfu token: + # turfu description: + # turfu - The Kheops token to use to authenticate against Kheops server. + # turfu default: '' + # turfu env: + # turfu - name: ANSIBLE_KHEOPS_TOKEN + # turfu host: + # turfu description: + # turfu - Hostname of the Kheops Server. + # turfu default: '127.0.0.1' + # turfu env: + # turfu - name: ANSIBLE_KHEOPS_HOST + # turfu port: + # turfu description: + # turfu - Kheops port to connect to. + # turfu default: '9843' + # turfu env: + # turfu - name: ANSIBLE_KHEOPS_PORT + # turfu protocol: + # turfu description: + # turfu - The URL protocol to use. + # turfu default: 'http' + # turfu choices: ['http', 'https'] + # turfu env: + # turfu - name: ANSIBLE_KHEOPS_PROTOCOL + # turfu validate_certs: + # turfu description: + # turfu - Whether or not to verify the TLS certificates of the Kheops server. + # turfu type: boolean + # turfu default: False + # turfu env: + # turfu - name: ANSIBLE_KHEOPS_VALIDATE_CERTS + + # Uneeded # Misc + # Uneeded version: + # Uneeded description: + # Uneeded - Kheops API version to use. + # Uneeded default: 1 + # Uneeded choices: [1] + # Uneeded env: + # Uneeded - name: ANSIBLE_KHEOPS_VERSION + # Uneeded cache: + # Uneeded description: + # Uneeded - Enable Kheops inventory cache. + # Uneeded default: false + # Uneeded type: boolean + # Uneeded env: + # Uneeded - name: ANSIBLE_KHEOPS_CACHE + # Uneeded policy: + # Uneeded description: + # Uneeded - Kheops policy to use for the lookups. + # Uneeded default: 'default' + +''' + +EXAMPLES = ''' +# zzz_dev.kheops.yml +plugin: kheops +host: 127.0.0.1 +token: xxx:yyy +query_scope: + fqdn: inventory_hostname + + +# zzz_prod.kheops.yml +plugin: kheops +host: kheops.domain.tld +protocol: https +token: xxx:yyy +query_scope: + fqdn: inventory_hostname + hostgroup: foreman_hostgroup_title + organization: foreman_organization_name + location: foreman_location_name + environment: foreman_environment_name +''' + +import sys +import os +import logging +from ansible import constants as C +from ansible.errors import AnsibleError +from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, Constructable + +#import kheops.app as Kheops +from kheops.app import Kheops + +sys.path.append("/home/jez/prj/bell/training/tiger-ansible/ext/kheops") + + +class InventoryModule(BaseInventoryPlugin, Cacheable, Constructable): + NAME = 'kheops' + + def verify_file(self, path): + valid = False + if super(InventoryModule, self).verify_file(path): + if path.endswith(('kheops.yaml', 'kheops.yml')): + valid = True + return valid + + def parse(self, inventory, loader, path, cache): + '''Return dynamic inventory from source ''' + + super(InventoryModule, self).parse(inventory, loader, path, cache) + self._read_config_data(path) + + # Read configuration + self.keys = self.get_option('query_keys') + self.scope = self.get_option('query_scope') + self.namespace = self.get_option('query_namespace') + + self.strict = self.get_option('strict') + self.configuration = self.get_option('configuration') + self.log_level = self.get_option('log_level') + + self.cache_enabled = os.environ.get( + 'ANSIBLE_KHEOPS_CACHE', + str(self.get_option('cache')) + ).lower() in ('true', '1', 't') + + + # Determine cache behavior + #attempt_to_read_cache = self.cache_enabled and cache + + # Khéops instance + kheops = Kheops(config=self.configuration, namespace=self.namespace) + + # Khéops log support + log_level = getattr(logging, self.log_level, 'DEBUG') + + logger = logging.getLogger('kheops') + logger.setLevel(log_level) + + # See for logging: https://medium.com/opsops/debugging-requests-1989797736cc + class ListLoggerHandler(logging.Handler): + def emit(self, record): + msg = self.format(record) + + list_logging_handler = ListLoggerHandler() + main_logger = logging.getLogger() + main_logger.addHandler(list_logging_handler) + main_logger.setLevel(logging.DEBUG) + + # from pyinstrument import Profiler + #profiler = Profiler() + #profiler.start() + + # Loop over each keys + for host_name in inventory.hosts: + host = self.inventory.get_host(host_name) + + # Build the scope + scope = {} + host_vars = host.get_vars() + for key, val in self.scope.items(): + scope[key] = host_vars.get(val, None) + self.display.vvv(f"Kheops scope for {host.name}: {scope} for keys: {self.keys}") + + # Fetch the results + ret = kheops.lookup( + keys=[ key_src for key_dst, key_src in self.keys.items() ], + scope=scope, + #trace=True, + #explain=True, + ) + + # Inject variables into host + for ansible_key, kheops_key in self.keys.items(): + ansible_value = ret.get(kheops_key) + host.set_variable(ansible_key, ansible_value) + + # Call constructed inventory plugin methods + hostvars = self.inventory.get_host(host_name).get_vars() + self._set_composite_vars(self.get_option('compose'), hostvars, host_name, self.strict) + self._add_host_to_composed_groups(self.get_option('groups'), hostvars, host_name, self.strict) + self._add_host_to_keyed_groups(self.get_option('keyed_groups'), hostvars, host_name, self.strict) + + + #profiler.stop() + ##profiler.print() + #profiler.open_in_browser() +