Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3780118
D2782.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
5 KB
Referenced Files
None
Subscribers
None
D2782.diff
View Options
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()
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Nov 26, 11:48 (16 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2264566
Default Alt Text
D2782.diff (5 KB)
Attached To
Mode
D2782: Document hypervisor versions
Attached
Detach File
Event Timeline
Log In to Comment