diff --git a/utils/netbox/document-hypervisors.py b/utils/netbox/document-hypervisors.py new file mode 100755 --- /dev/null +++ b/utils/netbox/document-hypervisors.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 + +# ------------------------------------------------------------- +# NetBox — Document hypervisors facts in NetBox +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Project: Nasqueron +# License: BSD-2-Clause +# Description: This script connects to hypervisors, +# gather facts like last version used, +# and document them to NetBox config context. +# Dependencies: PyYAML, pynetbox +# ------------------------------------------------------------- + + +import logging +import os +import sys + +import pynetbox +import subprocess +import yaml + + +TAG_VMWARE = "VMWare ESXi" + + +# ------------------------------------------------------------- +# Get NetBox config and credentials +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def get_netbox_config_from_salt(): + config_path = "/usr/local/etc/salt/master.d/netbox.conf" + + if not os.path.exists(config_path): + return False, None + + with open(config_path) as fd: + salt_config = yaml.safe_load(fd) + salt_config = salt_config["ext_pillar"][0]["netbox"] + return True, { + "server": salt_config["api_url"].replace("/api/", ""), + "token": salt_config["api_token"], + } + + +def get_netbox_config_from_config_dir(): + try: + config_path = os.path.join(os.environ["HOME"], ".config", "netbox", "auth.yaml") + except KeyError: + return False, None + + if not os.path.exists(config_path): + return False, None + + with open(config_path) as fd: + return True, yaml.safe_load(fd) + + +def get_netbox_config(): + methods = [get_netbox_config_from_salt, get_netbox_config_from_config_dir] + + for method in methods: + has_config, config = method() + if has_config: + return config + + raise RuntimeError("Can't find NetBox config") + + +# ------------------------------------------------------------- +# Service container +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def init_app(): + """Prepare a services container for appplication.""" + config = get_netbox_config() + + return { + "config": config, + "netbox": connect_to_netbox(config), + } + + +def connect_to_netbox(config): + return pynetbox.api(config["server"], token=config["token"]) + + +# ------------------------------------------------------------- +# Document hypervisors +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def document_hypervisors(app): + logging.info("Query NetBox DCIM to get devices") + devices = app["netbox"].dcim.devices.all() + for device in devices: + if is_device_tagged(device, TAG_VMWARE): + document_vmware_hypervisor(app["netbox"], device) + + +def document_vmware_hypervisor(nb, device): + logging.info(f"Documenting {device.name}") + + ip = get_device_primary_ip_address(device) + logging.info(f"Connecting to {ip}") + version = ssh(ip, ["vmware", "-v"]) + logging.debug(f"Version found: {version}") + + update_actual_context_version(device, version) + + +# ------------------------------------------------------------- +# Helper functions to use NetBox API +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def is_device_tagged(device, tag): + tags = [tag.name for tag in device.tags] + return tag in tags + + +def get_device_primary_ip_address(device): + pos = device.primary_ip.address.find("/") + return device.primary_ip.address[0:pos] + + +def update_actual_context_version(device, version): + try: + current_version = device.local_context_data["actual_context"]["version"] + if version == current_version: + return + except KeyError: + pass + + if "actual_context" not in device.local_context_data: + device.local_context_data["actual_context"] = {} + + device.local_context_data["actual_context"]["version"] = version + logging.info(f"Saving new local context for {device.name}: version is {version}") + device.save() + + +# ------------------------------------------------------------- +# Helper functions to use SSH +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def ssh(host, command): + return run_process(["ssh", host, *command]) + + +def run_process(command): + process = subprocess.run(command, capture_output=True) + + if process.returncode == 0: + return process.stdout.decode("UTF-8").strip() + + if len(process.stderr) > 0: + print(process.stderr.decode("UTF-8").strip(), file=sys.stderr) + logging.warning( + f"When running {command}: {process.stderr} (exit code: {process.returncode})" + ) + return None + + +# ------------------------------------------------------------- +# Application entry-point +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def run_document_hypervisors(): + app = init_app() + document_hypervisors(app) + + +if __name__ == "__main__": + LOGLEVEL = os.environ.get("LOGLEVEL", "WARNING").upper() + logging.basicConfig(level=LOGLEVEL) + + run_document_hypervisors()