You've already forked ansible-netbox-inventory
feat: ip address and virtual machine inventory sources
hosts land in a single "netbox" group with hostvars under _meta; IP addresses use the DNS name as inventory hostname when set, virtual machines expose their primary IP as ansible_host when they have one.
This commit is contained in:
@@ -162,6 +162,88 @@ def load_config(path: str | None = None) -> dict[str, Any]:
|
||||
return merged
|
||||
|
||||
|
||||
def strip_prefix(address: str) -> str:
|
||||
"""Return a bare IP address without the CIDR prefix length."""
|
||||
return address.split("/", 1)[0]
|
||||
|
||||
|
||||
def ip_host_entry(ip: dict[str, Any]) -> tuple[str, dict[str, Any]]:
|
||||
"""
|
||||
Turn a NetBox IP address record into an inventory (name, hostvars) pair.
|
||||
|
||||
The DNS name is used as the inventory hostname when set, the bare address
|
||||
otherwise; the bare address always ends up in ansible_host.
|
||||
"""
|
||||
address = strip_prefix(ip.get("address") or "")
|
||||
name = ip.get("dns_name") or address
|
||||
hostvars = {
|
||||
"ansible_host": address,
|
||||
"netbox_ip_address": ip.get("address") or "",
|
||||
"netbox_dns_name": ip.get("dns_name") or "",
|
||||
"netbox_tenant": (ip.get("tenant") or {}).get("name") or "",
|
||||
"netbox_vrf": (ip.get("vrf") or {}).get("name") or "",
|
||||
"netbox_description": ip.get("description") or "",
|
||||
}
|
||||
return name, hostvars
|
||||
|
||||
|
||||
def vm_host_entry(vm: dict[str, Any]) -> tuple[str, dict[str, Any]]:
|
||||
"""
|
||||
Turn a NetBox virtual machine record into an inventory (name, hostvars) pair.
|
||||
|
||||
ansible_host is only set when the virtual machine has a primary IP, so that
|
||||
Ansible falls back to resolving the name otherwise.
|
||||
"""
|
||||
name = vm.get("name") or ""
|
||||
hostvars = {
|
||||
"netbox_status": (vm.get("status") or {}).get("value") or "",
|
||||
"netbox_cluster": (vm.get("cluster") or {}).get("name") or "",
|
||||
"netbox_site": (vm.get("site") or {}).get("name") or "",
|
||||
"netbox_tenant": (vm.get("tenant") or {}).get("name") or "",
|
||||
}
|
||||
primary = vm.get("primary_ip") or {}
|
||||
if primary.get("address"):
|
||||
hostvars["ansible_host"] = strip_prefix(primary["address"])
|
||||
return name, hostvars
|
||||
|
||||
|
||||
def fetch_entries(client: NetBoxClient, source: str) -> list[tuple[str, dict[str, Any]]]:
|
||||
"""Fetch all hosts of the configured source from NetBox."""
|
||||
if source == SOURCE_IP_ADDRESSES:
|
||||
records = client.get_all("ipam/ip-addresses")
|
||||
return [ip_host_entry(record) for record in records if record.get("address")]
|
||||
records = client.get_all("virtualization/virtual-machines", {"exclude": "config_context"})
|
||||
return [vm_host_entry(record) for record in records if record.get("name")]
|
||||
|
||||
|
||||
def build_inventory(config: dict[str, Any], client: NetBoxClient) -> dict[str, Any]:
|
||||
"""
|
||||
Build the full --list inventory structure for Ansible.
|
||||
|
||||
Raises:
|
||||
InventoryError: On an unknown source or NetBox API failures.
|
||||
"""
|
||||
source = config["source"]
|
||||
if source not in SOURCES:
|
||||
raise InventoryError(
|
||||
f'Unknown source "{source}", valid sources: {", ".join(SOURCES)}.'
|
||||
)
|
||||
entries = fetch_entries(client, source)
|
||||
hostvars: dict[str, dict[str, Any]] = {}
|
||||
for name, host in entries:
|
||||
if name in hostvars:
|
||||
# duplicate inventory names keep both hosts by falling back to the
|
||||
# bare address as the name; identical duplicates are dropped
|
||||
name = host.get("ansible_host") or name
|
||||
if name in hostvars:
|
||||
continue
|
||||
hostvars[name] = host
|
||||
return {
|
||||
GROUP_NAME: {"hosts": sorted(hostvars)},
|
||||
"_meta": {"hostvars": hostvars},
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Entry point for the Ansible dynamic inventory protocol."""
|
||||
parser = argparse.ArgumentParser(description="Ansible dynamic inventory backed by NetBox.")
|
||||
@@ -174,8 +256,11 @@ def main() -> int:
|
||||
print(json.dumps({}))
|
||||
return 0
|
||||
try:
|
||||
load_config()
|
||||
print(json.dumps({GROUP_NAME: {"hosts": []}, "_meta": {"hostvars": {}}}, indent=2))
|
||||
config = load_config()
|
||||
client = NetBoxClient(
|
||||
config["url"], config["token"], config["verify_ssl"], config["timeout"]
|
||||
)
|
||||
print(json.dumps(build_inventory(config, client), indent=2, sort_keys=True))
|
||||
except InventoryError as exc:
|
||||
print(f"netbox-inventory: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user