Skip to content

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

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

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.

Rebased onto Schema Library v2 (#75)

This PR predates #75, which landed the v2 model and delivered a good part of what this branch originally carried. It has been rebased, and three pieces are now dropped because #75 already ships them:

Originally in this PR Status after #75
generate_template: true on DcimDevice Already on main (base/dcim.yml)
Drop unique from the module type's part_number Already on mainDcimGenericModuleType.part_number is not unique
extensions/module_bay (a new DcimModuleBay) Superseded — #75 ships DcimModuleBay in extensions/device_module

extensions/modules/modules.yml was deleted by #75, so the changes this PR made to it are re-homed onto extensions/device_module/device_module.yml. What remains is the delta that v2 does not yet cover.

Changes

All additive or relaxing. Nothing is removed.

extensions/device_module/device_module.yml

Two design deltas ported onto #75's DcimModuleBay, plus one onto its module type generic.

  • DcimModuleBay.position becomes Text. Bay positions are free-form. The DCS-7508N alone uses F1F6 and PSU-1PSU-8 alongside 110, and A9K-AC-PEM-V3 starts its bays at position: '0'. Against v2's Number with min_value: 1, 15 of the DCS-7508N's 24 bays fail — the 14 non-numeric ones, plus '0', which is numeric but still violates the minimum.
  • Add DcimModuleBay.bay_label. The NetBox label lands here, not in an attribute named label. Infrahub auto-populates an attribute literally named label from name when unset, and title-cases it, so "NetBox supplied no label" becomes indistinguishable from "label equals name". On a DCS-7508N only 10 of 24 bays carry one. This is distinct from v2's existing role dropdown, which enumerates a bay's purpose (supervisor, line_card, …) rather than carrying NetBox's free text — the two coexist.
  • Add DcimGenericModuleType.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.

extensions/module_port (new)

The one piece with no equivalent anywhere in v2. The ports a module provides, as declared by its module type — what NetBox lists under interfaces, console-ports and power-ports.

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 DcimModulePort 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 DcimGenericModule.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.
  • Keyed on module__computed_name__value, not serial_number. The pre-rebase version used the module's serial number. Schema library v2 #75 made DcimGenericModule.serial_number optional and non-unique, so it can no longer key anything; computed_name is unique and mandatory.
  • 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.

experimental/modules_linecards/linecard.yml

  • Enable generate_template on DeviceLinecard, so a module type can be imported as a reusable blueprint rather than as an installed card.
  • 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.

objects/extensions/{device_module,device_psu_module}

Quote the demo bay positions, now that position is Text.

Docs

Regenerated with invoke docs.generate — unlike the pre-rebase version of this PR, which skipped them.

Verification

Full CI-equivalent schema load against a live Infrahub (latest): invoke schemas.load-all-schemas loads all 57 extensions, All good! ✨.

Bay positions round-trip — the real DCS-7508N and A9K-AC-PEM-V3 values:

name                             position   bay_label
Chassis Fabric Module Slot 1     F1         None
Chassis Fabric Module Slot 6     F6         None
Chassis PSU Slot 1               PSU-1      None
Chassis PSU Slot 8               PSU-8      None
Chassis Power Module Slot 0      0          PM0
Chassis Slot 1                   1          Supervisor

The bay_label naming is load-bearing, probed with a throwaway node carrying both a label attribute and a control:

name                     label (attr)             control_label
Fabric Module Slot 1     Fabric Module Slot 1     None
labeltest bay            Labeltest Bay            None

An attribute named label is auto-populated from name and title-cased. bay_label correctly reads back None where NetBox supplied nothing.

Module templating works end to end. TemplateDcimDevice, TemplateDcimModuleBay, TemplateDcimModulePort and TemplateDeviceLinecard all generate. Creating the real DCS-7500R-36CQ line card as a template, with all 36 interfaces from the fixture:

template_name : mod-DCS-7500R-36CQ
serial_number : None
module_bay    : None
linecard_type : DCS-7500R-36CQ | weight_grams = 9979
ports count   : 36
first / last  : Ethernet{module}/1/1 / Ethernet{module}/9/1
all keep {module} token: True

(first / last is lexical, hence /9/1 last; the fixture declares Ethernet{module}/1/1 through Ethernet{module}/36/1.)

Note module_bay: NoneDcimGenericModule.module_bay is a mandatory relationship, but Infrahub relaxes mandatory relationships on generated templates the same way it omits unique attributes, so a module type templates fine without being installed anywhere.

yamllint, markdownlint and find_cycles.py clean.

Notes for reviewers

  • This PR edits two files Schema library v2 #75 just landed (extensions/device_module/device_module.yml and its demo objects). That is deliberate: keeping the v2 DcimModuleBay and adding a second, near-identical bay node would ship two overlapping models. If you would rather the position/bay_label change be argued separately from the new module_port extension, say so and I will split it.
  • 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.
  • Removing unique does not take effect on an already-loaded branch. Re-loading a corrected schema onto a branch that already had unique: true leaves the constraint in place; only a fresh branch picks it up. No longer this PR's concern (Schema library v2 #75 ships part_number non-unique), but worth knowing for anyone migrating an existing instance.
  • Unrelated pre-existing drift on main, not fixed here. invoke docs.generate overwrites hand-written notes in extensions/{location_minimal,location_site,rack}/README.md, and docs/docs/reference/security.mdx is stale against its schema (it still says InfraIPAddress/InfraGenericDevice where experimental/security/security.yml has said IpamIPAddress/DcimGenericDevice since d5ae01b). Both reverted out of this branch to keep the diff honest; both worth a separate fix.

@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: dc9ec12
Status: ✅  Deploy successful!
Preview URL: https://849969a7.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 4 times, most recently 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

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. This closes the remaining gaps against the v2 model landed
in #75, which already introduced DcimModuleBay, DcimGenericModule and
DcimGenericModuleType in extensions/device_module.

Three pieces of the original change are already delivered by #75 and are
dropped here: generate_template on DcimDevice, the non-unique part_number on
the module-type generic, and a ModuleBay node of its own (extensions/module_bay
is superseded by DcimModuleBay).

extensions/device_module/device_module.yml
  DcimModuleBay.position becomes Text. Bay positions are free-form: a
  DCS-7508N uses '1'..'10' but also 'F1'..'F6' and 'PSU-1'..'PSU-8', so a
  Number attribute with min_value 1 rejects 14 of its 24 bays.

  Add DcimModuleBay.bay_label. The NetBox label lands here, NOT in an
  attribute named 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. Distinct from the existing role dropdown, which enumerates
  the bay's purpose rather than carrying NetBox's free text.

  Add DcimGenericModuleType.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 a supervisor
  rounds to 0 kg, which reads as data rather than as a missing value.

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 DcimModulePort is a declaration
  parented by DcimGenericModule, carrying name, category, the NetBox type
  slug, mgmt_only and maximum_draw.

  Keyed on module__computed_name__value, not serial_number: #75 made
  DcimGenericModule.serial_number optional and non-unique, so it cannot key
  anything, while computed_name is unique and mandatory.

  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.

experimental/modules_linecards/linecard.yml
  Enable generate_template on DeviceLinecard, so a module type can be
  imported as a reusable blueprint rather than as an installed card.

  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.

objects/extensions/{device_module,device_psu_module}
  Quote the demo bay positions now that position is Text.

Reference docs regenerated with invoke docs.generate.
@petercrocker
petercrocker force-pushed the feat-netbox-import-module-bays branch from 5a225fb to dc9ec12 Compare September 6, 2026 12:43
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