Skip to content

feat(dcim): support NetBox device-type and module-type imports - #76

Draft
petercrocker wants to merge 1 commit into
mainfrom
feat-netbox-import-module-bays
Draft

feat(dcim): support NetBox device-type and module-type imports#76
petercrocker wants to merge 1 commit into
mainfrom
feat-netbox-import-module-bays

Conversation

@petercrocker

@petercrocker petercrocker commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Problem

Importing the NetBox devicetype-library into Infrahub loses most of what a modular chassis and a module type describe, and a batch of module types fails to load outright.

Changes

All additive or relaxing. Nothing is removed.

base/dcim.yml

Enable generate_template on DcimDevice. Template* kinds only exist where a node declares it, so object templates could not be used without editing the library. Additive for anyone who never creates a template.

DcimDeviceType.weight is deliberately left as Number — see the notes below.

extensions/module_bay (new)

Module bays are the physical slots of a chassis, as distinct from the modules installed into them. extensions/modules already models the installed module, keyed by a serial_number that a device type cannot supply — nothing modelled the slot. Without a bay node a modular chassis converts to an empty object template: a DCS-7508N has 24 bays and zero interfaces of its own, so every port is lost.

  • The NetBox label lands in bay_label, not label. Infrahub auto-populates an attribute literally named label from name when it is unset — and title-cases it. An unlabelled bay comes back labelled with its own name (Fabric Module Slot 1), and LabelTest Bay becomes Labeltest Bay, so "NetBox supplied no label" becomes indistinguishable from "label equals name". On a DCS-7508N only 10 of 24 bays carry one (Supervisor, Line Card). Verified: with bay_label the unlabelled bays correctly read back None.
  • position is Text, not Number. Bay positions are free-form; the DCS-7508N alone uses F1F6 and PSU-1PSU-8 alongside 110. A Number attribute rejects 14 of its 24 bays.
  • The Component/Parent pair is deliberately asymmetric. The Parent side peers DcimGenericDevice because human_friendly_id traverses device__name__value, and name is not declared on DcimPhysicalDevice (peering there fails validation outright). The Component side sits on DcimPhysicalDevice so DcimVirtualDevice does not inherit slots it cannot have. Both anchorings validate; only this one gives the right inherited set.

extensions/module_port (new)

The ports a module provides, as declared by its module type — what NetBox lists under interfaces, console-ports and power-ports. Previously these had nowhere to go at all.

These are deliberately not DcimInterface. I tested that first: DcimInterface.device is a mandatory Parent, and Infrahub requires relationships used in a uniqueness constraint to be mandatory, so relaxing it so an interface could hang off a module fails outright:

DcimConsoleInterface.uniqueness_constraints: cannot use device relationship,
relationship must be mandatory. (`device`)

Every interface kind is keyed [device, name__value] with a device__name__value HFID, so that route means dismantling interface identity schema-wide.

A DeviceModulePort is instead a declaration parented by the module: name, category (interface / console / power / front / rear), port_type, mgmt_only, maximum_draw. One typed collection on DeviceGenericModule.ports rather than five parallel relationships.

  • port_type is Text, not Dropdown. NetBox uses well over a hundred type slugs across the three lists; a Dropdown fails the load on every slug not enumerated.
  • Port names keep NetBox's {module} token verbatim. A template is not bound to a bay, so the token cannot be resolved at import time. Substituting it and creating the real device interfaces is a generator step once the module is installed — the traversal a generator needs is documented in the file, and is verified to exist end to end (DcimDevice.modulesDeviceLinecard.module_bay.position + .portsDcimDevice.interfaces).

extensions/modules/modules.yml

  • Drop unique from DeviceGenericModuleType.part_number. A hard blocker on bulk import. Many published module types carry no part number, and Infrahub counts repeated nulls as a uniqueness violation, so the second part-number-less module type in any batch fails:

    ['DeviceLinecardTypeUpsert'] Violates uniqueness constraint 'part_number'
    

    Three of five module types in the test set hit this. Identity is still guaranteed by the existing name__value + manufacturer constraint.

  • Add weight_grams (Number). Infrahub has no float attribute kind, so a weight is a whole number or nothing — and modules are exactly the light hardware that integer kilograms destroy. A transceiver or supervisor rounds to 0 kg, which reads as data rather than as a missing value.

experimental/modules_linecards/linecard.yml

  • Enable generate_template on DeviceLinecard, so a module type can be imported as a reusable blueprint. The unique serial_number inherited from DeviceGenericModule is not carried onto the generated template at all — Infrahub keys it on template_name instead. Verified: serial_number is not even queryable on TemplateDeviceLinecard.
  • DeviceLinecard.slot becomes optional. A NetBox module type describes a model rather than an installed card and carries no slot, so a mandatory slot made every imported module type unloadable.

Verification

Schema loaded onto a branch of a live Infrahub instance (1.10.6), then three device types and five module types converted and loaded — all five generated object files, zero errors:

device templates
  cisco-c9300-48p      ifaces=51  bays=6    1U  weight=8 kg
  arista-dcs-7508n     ifaces=0   bays=24  13U  weight=43 kg
  juniper-ex4300-48t   ifaces=53  bays=3    1U  weight=7 kg

module templates + their declared ports
  mod-DCS-7500-SUP2     2 interface (1000base-t, mgmt) + 1 console (rj-45)
  mod-DCS-7500R-36CQ   36 interface (100gbase-x-qsfp28)
  mod-EX-PWR-320-AC     1 power (iec-60320-c14, 320 W)
  mod-DCS-7508R-FM      0 - correct, it has none
  mod-A9K-AC-PEM-V3     0 - its 4 bays are bays inside a module

totals: 33 TemplateDcimModuleBay, 40 TemplateDeviceModulePort

The module set is deliberately varied: a supervisor, a 36-port line card, a fabric module with no components, a PSU using NetBox 4.3 profile/attribute_data, and a module that itself has bays. All 24 bay positions round-trip intact including the non-numeric ones. yamllint, markdownlint and find_cycles.py clean.

Notes for reviewers

  • Removing unique does not take effect on an already-loaded branch. Re-loading the corrected schema onto a branch that already had unique: true left the constraint in place; only a branch created fresh picked it up. Not a problem for this PR (the schema ships correct), but worth knowing for anyone migrating an existing instance.
  • DcimDeviceType.weight is untouched, but it has a known sharp edge. With no float kind, importers round to whole kilograms — which rounds roughly 300 published device types (sub-500 g transceivers and access points) to 0, and a zero looks like data. Fixing it properly means a weight_grams attribute on DcimDeviceType too, mirroring what this adds for modules. Left out to avoid two competing weight attributes in the core schema without a maintainer decision; happy to add it.
  • Module ports are declarations, not live interfaces. This PR gives them a home and preserves every field; turning them into cabled, addressable device interfaces needs the generator described above. That is being written separately.
  • Docs are not regenerated here, matching d5ae01b and a7ccf78. Running invoke docs.generate on main also surfaces unrelated pre-existing drift (sfp, security, lag, mlag, …) which felt wrong to fold in.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 2, 2026

Copy link
Copy Markdown

Deploying schema-library with  Cloudflare Pages  Cloudflare Pages

Latest commit: b8df83d
Status: ✅  Deploy successful!
Preview URL: https://24559ea3.schema-library.pages.dev
Branch Preview URL: https://feat-netbox-import-module-ba.schema-library.pages.dev

View logs

@petercrocker
petercrocker force-pushed the feat-netbox-import-module-bays branch 3 times, most recently from 301918a to 0e14203 Compare August 4, 2026 10:23
Importing the NetBox devicetype-library into Infrahub loses most of what
a modular chassis and a module type describe, and a batch of module types
fails to load outright. Verified end to end against a live instance with
three device types and five module types, including a modular chassis and
its supervisor, line card, fabric module, PSU and a module with bays.

base/dcim.yml
  Enable generate_template on DcimDevice. Template* kinds only exist
  where a node declares it, so object templates could not be used at all
  without editing the library. Additive for anyone who never creates a
  template.

extensions/module_bay (new)
  Module bays are the physical slots of a chassis, as distinct from the
  modules installed into them. extensions/modules already models the
  installed module, keyed by a serial number a device type cannot
  supply; nothing modelled the slot. Without a bay node a modular
  chassis converts to an empty object template - a DCS-7508N has 24 bays
  and zero interfaces of its own, so every port is lost.

  position is Text, not Number: bay positions are free-form, and the
  DCS-7508N alone uses 'F1'..'F6' and 'PSU-1'..'PSU-8' alongside
  '1'..'10'.

  The NetBox label lands in bay_label, NOT label. Infrahub auto-populates
  an attribute literally named label from name when unset, and title-cases
  it, so an unlabelled bay comes back labelled with its own name and
  'no label' becomes indistinguishable from 'label equals name'. On a
  DCS-7508N only 10 of 24 bays carry one.

  The Parent side peers DcimGenericDevice because human_friendly_id
  traverses device__name__value and name is not declared on
  DcimPhysicalDevice. The Component side sits on DcimPhysicalDevice so
  DcimVirtualDevice does not inherit slots it cannot have.

extensions/module_port (new)
  The ports a module provides, as declared by its module type - what
  NetBox lists under interfaces, console-ports and power-ports.

  These deliberately are not DcimInterface. DcimInterface.device is a
  mandatory Parent, and Infrahub requires relationships used in a
  uniqueness constraint to be mandatory, so relaxing it fails with
  "cannot use device relationship, relationship must be mandatory" and
  would break the device__name__value human_friendly_id too. A
  DeviceModulePort is a declaration parented by the module, carrying
  name, category, the NetBox type slug, mgmt_only and maximum_draw.

  port_type is Text, not Dropdown: NetBox uses well over a hundred type
  slugs across the three lists, and a Dropdown fails the load on every
  slug not enumerated.

  Port names keep NetBox's {module} token verbatim - a template is not
  bound to a bay, so it cannot be resolved at import time. Substituting
  it and creating the real device interfaces is a generator step once the
  module is installed; the traversal it needs is documented in the file.

extensions/modules/modules.yml
  - Drop unique from DeviceGenericModuleType.part_number. Many published
    module types carry no part number, and Infrahub counts repeated
    nulls as a uniqueness violation, so the second part-number-less
    module type in a batch fails with "Violates uniqueness constraint
    'part_number'". Three of five module types in the test set hit this.
    Identity is still guaranteed by the name+manufacturer constraint.
  - Add weight_grams. Infrahub has no float attribute kind, so a weight
    is a whole number or nothing, and modules are exactly the light
    hardware that integer kilograms destroy: a transceiver or supervisor
    rounds to 0 kg, which reads as data rather than as a missing value.

experimental/modules_linecards/linecard.yml
  - Enable generate_template on DeviceLinecard, so a module type can be
    imported as a reusable blueprint. The unique serial_number inherited
    from DeviceGenericModule is not carried onto the generated template
    at all - Infrahub keys it on template_name instead.
  - DeviceLinecard.slot becomes optional. A NetBox module type describes
    a model rather than an installed card and carries no slot, so a
    mandatory slot made every imported module type unloadable.

Nothing is removed. DcimDeviceType.weight is left as Number: importers
round to whole kilograms. Docs are not regenerated here, matching
d5ae01b and a7ccf78.
@petercrocker
petercrocker force-pushed the feat-netbox-import-module-bays branch from 0e14203 to b8df83d Compare August 6, 2026 11:34
@BaptisteGi

Copy link
Copy Markdown
Contributor

@petercrocker I completely revisited the device module extension here https://github.com/opsmill/schema-library/pull/75/changes#diff-3388ec71d8a9c2c95e93b3f50658e4fb0de13acc38cdc98e3908738c435e3b0d
I think we are pretty much on the same page, once the refactoring PR is merged happy to make the integration of this one

@petercrocker petercrocker mentioned this pull request Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants