#!/usr/bin/env python3 import docker import requests import socket import netifaces import os from pathlib import Path import sys CONFIG_PATH = "/etc/netbox-docker-sync.conf" # === Konfigurationsdatei lesen === def read_config(filepath): if not Path(filepath).is_file(): print(f"[FEHLER] Datei '{filepath}' nicht gefunden.") sys.exit(1) config = {} try: with open(filepath) as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue if "=" in line: key, value = line.split("=", 1) config[key.strip()] = value.strip() except Exception as e: print(f"[FEHLER] Fehler beim Lesen der Datei: {e}") sys.exit(1) # Pflichtfelder prüfen required_keys = ["NETBOX_CLUSTER_ID", "NETBOX_URL", "NETBOX_TOKEN"] for key in required_keys: if key not in config or not config[key]: print(f"[FEHLER] Konfigurationswert fehlt oder leer: {key}") sys.exit(1) # Cluster ID validieren if not config["NETBOX_CLUSTER_ID"].isdigit(): print("[FEHLER] NETBOX_CLUSTER_ID ist keine gültige Zahl.") sys.exit(1) config["NETBOX_CLUSTER_ID"] = int(config["NETBOX_CLUSTER_ID"]) return config # === Konfiguration laden === cfg = read_config(CONFIG_PATH) CLUSTER_ID = cfg["NETBOX_CLUSTER_ID"] NETBOX_URL = cfg["NETBOX_URL"] NETBOX_TOKEN = cfg["NETBOX_TOKEN"] NETBOX_HEADERS = { "Authorization": f"Token {NETBOX_TOKEN}", "Content-Type": "application/json", "Accept": "application/json" } # === Standard-Netzwerkinterface automatisch ermitteln === def detect_default_interface(): try: gws = netifaces.gateways() return gws['default'][netifaces.AF_INET][1] except Exception as e: print(f"[!] Konnte Standard-Interface nicht ermitteln: {e}") return "lo" DOCKER_IFACE = os.environ.get("DOCKER_IFACE") or detect_default_interface() # === IP-Adresse des Dockerhosts korrekt ermitteln === def get_host_ip(): try: ip = netifaces.ifaddresses(DOCKER_IFACE)[netifaces.AF_INET][0]['addr'] if ip.startswith("127.") or ip == "": raise ValueError("Ungültige IP-Adresse erkannt.") return ip except Exception as e: print(f"[!] Konnte Host-IP über Interface {DOCKER_IFACE} nicht ermitteln: {e}") return "0.0.0.0" # Docker-Client initialisieren client = docker.from_env() DOCKER_HOSTNAME = socket.gethostname() DOCKER_HOST_IP = get_host_ip() # === Container erfassen und mit NetBox abgleichen === for container in client.containers.list(): name = container.name image = container.image.tags[0] if container.image.tags else "unknown" container_ip = container.attrs["NetworkSettings"]["IPAddress"] # --- Hier Labels auslesen --- labels = container.labels project = labels.get("com.docker.compose.project", "") service = labels.get("com.docker.compose.service", "") version = labels.get("com.docker.compose.version", "") # Exposed Ports auslesen (nur IPv4) ports_raw = container.attrs["NetworkSettings"]["Ports"] or {} exposed_ports = [] for port, mappings in ports_raw.items(): if mappings: for mapping in mappings: host_ip = mapping['HostIp'] if host_ip == "0.0.0.0" or host_ip.count(".") == 3: exposed_ports.append(f"{host_ip}:{mapping['HostPort']} -> {port}") else: exposed_ports.append(f"(no mapping) -> {port}") ports_string = ", ".join(exposed_ports) print(f"[→] Verarbeite Container: {name} ({container_ip}) Ports: {ports_string}") # Payload für NetBox-Eintrag vm_payload = { "name": name, "status": "active", "cluster": CLUSTER_ID, "custom_fields": { "container_ip": container_ip, "docker_host": DOCKER_HOSTNAME, "docker_host_ip": DOCKER_HOST_IP, "docker_image": image, "docker_ports": ports_string, "compose_project": project, "compose_service": service, "stack_version": version } } # Existiert die VM bereits in NetBox? r = requests.get( f"{NETBOX_URL}/virtualization/virtual-machines/?name={name}", headers=NETBOX_HEADERS ) if r.ok and r.json()["count"] > 0: vm_id = r.json()["results"][0]["id"] update = requests.patch( f"{NETBOX_URL}/virtualization/virtual-machines/{vm_id}/", headers=NETBOX_HEADERS, json=vm_payload ) print(f"[=] Aktualisiert: {name} ({update.status_code})") else: create = requests.post( f"{NETBOX_URL}/virtualization/virtual-machines/", headers=NETBOX_HEADERS, json=vm_payload ) print(f"[+] Erstellt: {name} ({create.status_code})")