You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Consolidate the duplicated "find a NetBox device by various identifiers" logic into a single, centralized utility function. This re-implements the (still open, never merged) PR #1826 from scratch against the current main, since the codebase has drifted and additional duplication has appeared in the meantime.
Today the same lookup pattern — try name, then fall back to cf_inventory_hostname (and sometimes serial / other custom fields) — is copy-pasted across many modules. There is even a private find_device_by_identifier() already living in osism/api.py (currently the only implementation), which should become the canonical, shared one.
Goal
Create osism/utils/netbox.py exposing a single reusable function and route all device lookups through it:
deffind_device_by_identifier(identifier: str, search_fields: list|None=None):
"""Find a NetBox device by multiple identifier types. Default search order: name, cf_inventory_hostname, serial. Returns the device object or None. """
Requirements for the helper:
Guard against utils.nb being unset and against empty/whitespace identifiers (return None).
Allow callers to override the searched fields via search_fields (e.g. netbox.py needs name, cf_alternative_name, cf_inventory_hostname, cf_external_hostname).
Use get(name=...) for the unique name field and filter(**{field: ...}) for custom fields / serial.
Add debug logging per search step and a single warning when nothing is found.
Catch and log lookup exceptions without crashing the caller.
Call sites to migrate
Replace the inline lookup blocks (verify against current main, line numbers have shifted since the PR):
osism/api.py — remove the local find_device_by_identifier() (around L265) and import the shared one; update the caller (around L558).
osism/commands/baremetal.py — multiple spots (device-role lookup, local_context_data extraction x2, name resolution, the [utils.nb.dcim.devices.get(name=name)] list build, NetBox-state clearing).
Behavioral notes to watch during re-implementation
These are the subtle points that need a deliberate decision rather than a blind copy of the old PR:
serial in the default search: Several call sites that previously only matched name + cf_inventory_hostname would, with a serial-inclusive default, start matching on serial too. Decide per call site whether broadening the match is intended; pass an explicit search_fields where it is not.
get() vs filter()[0] for name: The old api.py version used filter(name=...)[0] (takes first of possibly many); switching to get(name=...) raises on multiple matches. Keep the behavior consistent and intentional.
Preserve useful logging: The previous PR dropped the "Device found by inventory_hostname" info log in sonic.py. Keep meaningful, non-duplicated logging when centralizing.
Return shape: The helper returns a single device or None; call sites that build a devices list must wrap accordingly ([device] if device else []).
Acceptance criteria
osism/utils/netbox.py with find_device_by_identifier() exists and is unit-tested (nb unset, empty identifier, found by name / custom field / serial, not found, override fields).
All call sites above use the shared helper; no remaining inline dcim.devices.get(name=...) + filter(cf_inventory_hostname=...) fallback duplication.
No behavioral regressions for the documented cases (see notes above).
Summary
Consolidate the duplicated "find a NetBox device by various identifiers" logic into a single, centralized utility function. This re-implements the (still open, never merged) PR #1826 from scratch against the current
main, since the codebase has drifted and additional duplication has appeared in the meantime.Today the same lookup pattern — try
name, then fall back tocf_inventory_hostname(and sometimesserial/ other custom fields) — is copy-pasted across many modules. There is even a privatefind_device_by_identifier()already living inosism/api.py(currently the only implementation), which should become the canonical, shared one.Goal
Create
osism/utils/netbox.pyexposing a single reusable function and route all device lookups through it:Requirements for the helper:
utils.nbbeing unset and against empty/whitespace identifiers (returnNone).search_fields(e.g.netbox.pyneedsname,cf_alternative_name,cf_inventory_hostname,cf_external_hostname).get(name=...)for the uniquenamefield andfilter(**{field: ...})for custom fields / serial.Call sites to migrate
Replace the inline lookup blocks (verify against current
main, line numbers have shifted since the PR):osism/api.py— remove the localfind_device_by_identifier()(around L265) and import the shared one; update the caller (around L558).osism/commands/baremetal.py— multiple spots (device-role lookup,local_context_dataextraction x2, name resolution, the[utils.nb.dcim.devices.get(name=name)]list build, NetBox-state clearing).osism/commands/sonic.py—_get_device_from_netbox().osism/commands/console.py—get_primary_ipv4_from_netbox().osism/commands/netbox.py— the 4-field custom-field search (passsearch_fields=[...]).osism/tasks/conductor/utils.py—get_redfish_connection().osism/tasks/conductor/sonic/sync.py—sync_sonic().osism/utils/ssh.py—get_host_identifiers().osism/tasks/openstack.py— two spots (around L148 and L262) now carry the same pattern and should be migrated too.Behavioral notes to watch during re-implementation
These are the subtle points that need a deliberate decision rather than a blind copy of the old PR:
serialin the default search: Several call sites that previously only matchedname+cf_inventory_hostnamewould, with aserial-inclusive default, start matching on serial too. Decide per call site whether broadening the match is intended; pass an explicitsearch_fieldswhere it is not.get()vsfilter()[0]forname: The oldapi.pyversion usedfilter(name=...)[0](takes first of possibly many); switching toget(name=...)raises on multiple matches. Keep the behavior consistent and intentional.sonic.py. Keep meaningful, non-duplicated logging when centralizing.None; call sites that build adeviceslist must wrap accordingly ([device] if device else []).Acceptance criteria
osism/utils/netbox.pywithfind_device_by_identifier()exists and is unit-tested (nb unset, empty identifier, found by name / custom field / serial, not found, override fields).dcim.devices.get(name=...)+filter(cf_inventory_hostname=...)fallback duplication.References