Skip to content

Consolidate NetBox device lookup into a centralized utility (re-implement #1826) #2343

Description

@berendt

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 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:

def find_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).
  • osism/commands/sonic.py_get_device_from_netbox().
  • osism/commands/console.pyget_primary_ipv4_from_netbox().
  • osism/commands/netbox.py — the 4-field custom-field search (pass search_fields=[...]).
  • osism/tasks/conductor/utils.pyget_redfish_connection().
  • osism/tasks/conductor/sonic/sync.pysync_sonic().
  • osism/utils/ssh.pyget_host_identifiers().
  • New since PR Consolidate NetBox device lookup into centralized utility #1826: 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  • CI green.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions