Skip to content

Commit 20b7bdd

Browse files
adding nautobot-sync post-inspection hook
1 parent 3772188 commit 20b7bdd

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

components/ironic/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ pod:
235235
sources:
236236
- secret:
237237
name: ironic-ks-etc
238+
- secret:
239+
name: ironic-nautobot-token
240+
optional: true
238241
ironic_api:
239242
ironic_api:
240243
volumeMounts:

python/ironic-understack/ironic_understack/conf.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@ def setup_conf():
1010
"device_types_dir",
1111
help="directory storing Device Type description YAML files",
1212
default="/var/lib/understack/device-types",
13-
)
13+
),
14+
cfg.StrOpt(
15+
"nautobot_url",
16+
help="Nautobot API URL",
17+
default=None,
18+
),
19+
cfg.StrOpt(
20+
"nautobot_token",
21+
help="Nautobot API token",
22+
secret=True,
23+
default=None,
24+
),
1425
]
1526
cfg.CONF.register_group(grp)
1627
cfg.CONF.register_opts(opts, group=grp)
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Ironic inspection hook to sync device information to Nautobot."""
2+
3+
import pynautobot
4+
from ironic.drivers.modules.inspector.hooks import base
5+
from oslo_log import log as logging
6+
7+
from ironic_understack.conf import CONF
8+
9+
LOG = logging.getLogger(__name__)
10+
11+
12+
class NautobotSyncHook(base.InspectionHook):
13+
"""Hook to sync discovered device information to Nautobot."""
14+
15+
def __call__(self, task, inventory, plugin_data):
16+
"""Sync device inventory to Nautobot.
17+
18+
:param task: Ironic task context containing node and driver info
19+
:param inventory: Hardware inventory dict from inspection
20+
:param plugin_data: Shared data dict between hooks
21+
"""
22+
try:
23+
nautobot_url = CONF.ironic_understack.nautobot_url
24+
nautobot_token = CONF.ironic_understack.nautobot_token
25+
26+
if not nautobot_url or not nautobot_token:
27+
LOG.warning(
28+
"Nautobot URL or token not configured, skipping sync for node %s",
29+
task.node.uuid,
30+
)
31+
return
32+
33+
# Initialize Nautobot client
34+
nautobot = pynautobot.api(url=nautobot_url, token=nautobot_token)
35+
36+
# Extract device information from inventory
37+
device_data = self._extract_device_data(task, inventory)
38+
39+
# Sync to Nautobot
40+
self._sync_to_nautobot(nautobot, device_data, task.node)
41+
42+
LOG.info(
43+
"Successfully synced device information to Nautobot for node %s",
44+
task.node.uuid,
45+
)
46+
47+
except (KeyError, ValueError, TypeError) as e:
48+
msg = (
49+
f"Failed to extract device information from inventory for node "
50+
f"{task.node.uuid}: {e}"
51+
)
52+
LOG.error(msg)
53+
# Don't fail inspection, just log the error
54+
except Exception as e:
55+
msg = f"Failed to sync device to Nautobot for node {task.node.uuid}: {e}"
56+
LOG.error(msg)
57+
# Don't fail inspection, just log the error
58+
59+
def _extract_device_data(self, task, inventory):
60+
"""Extract relevant device data from inventory."""
61+
data = {
62+
"serial": inventory.get("system_vendor", {}).get("serial_number"),
63+
"manufacturer": inventory.get("system_vendor", {}).get("manufacturer"),
64+
"model": inventory.get("system_vendor", {}).get("product_name"),
65+
"uuid": task.node.uuid,
66+
"name": task.node.name or task.node.uuid,
67+
}
68+
69+
# Extract interface information
70+
interfaces = []
71+
for iface in inventory.get("interfaces", []):
72+
if iface.get("mac_address"):
73+
interfaces.append(
74+
{
75+
"name": iface.get("name"),
76+
"mac_address": iface.get("mac_address"),
77+
"ipv4_address": iface.get("ipv4_address"),
78+
}
79+
)
80+
81+
data["interfaces"] = interfaces
82+
83+
return data
84+
85+
def _sync_to_nautobot(self, nautobot, device_data, node):
86+
"""Sync device data to Nautobot."""
87+
serial = device_data.get("serial")
88+
if not serial:
89+
LOG.warning("Node %s, cannot sync to Nautobot", node.uuid)
90+
return
91+
92+
# Check if device exists in Nautobot
93+
device = self._find_device(nautobot, serial)
94+
95+
if device:
96+
LOG.info("Device %s already exists in Nautobot", serial)
97+
# Update device if needed
98+
self._update_device(nautobot, device, device_data)
99+
else:
100+
LOG.info("Device %s not found in Nautobot, would create", serial)
101+
# Note: Creation requires location/rack info
102+
# which we don't have from inspection
103+
# This would need to be configured or derived from other sources
104+
105+
def _find_device(self, nautobot, serial):
106+
"""Find device in Nautobot by serial number."""
107+
try:
108+
devices = nautobot.dcim.devices.filter(serial=serial)
109+
if devices:
110+
return devices[0]
111+
except Exception:
112+
LOG.exception("Error querying Nautobot for device with serial %s", serial)
113+
return None
114+
115+
def _update_device(self, nautobot, device, device_data):
116+
"""Update device information in Nautobot."""
117+
# Update basic device info if needed
118+
# This is a placeholder - actual update logic would depend on requirements
119+
LOG.debug("Would update device %s with data: %s", device.id, device_data)

python/ironic-understack/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ dependencies = [
1414
"ironic>=29.0,<30",
1515
"pyyaml~=6.0",
1616
"understack-flavor-matcher",
17+
"pynautobot~=2.0",
1718
]
1819

1920
[project.entry-points."ironic.inspection.hooks"]
2021
resource-class = "ironic_understack.resource_class:ResourceClassHook"
22+
nautobot-sync = "ironic_understack.nautobot_sync:NautobotSyncHook"
2123

2224
[project.entry-points."ironic.hardware.interfaces.inspect"]
2325
redfish-understack = "ironic_understack.redfish_inspect_understack:UnderstackRedfishInspect"

0 commit comments

Comments
 (0)