Add the chassis_mac custom field and drop the switch device type
ci/woodpecker/pr/plan Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

Switches are created by hand in the NetBox UI, so the model and its port
templates have no consumer.
This commit is contained in:
2026-10-03 17:33:37 +10:00
parent efeb148be7
commit cead6ab2a0
5 changed files with 18 additions and 68 deletions
+9 -18
View File
@@ -7,12 +7,14 @@ reference data a host is planned against. Device records are not here.
| Owner | Objects |
|-------|---------|
| **this repo** | sites, prefixes, DHCP pool ranges, gateways, managed IPs, VLAN groups + VLANs, device types (incl. switch models and their interface templates), manufacturers, device roles, platforms, custom-field definitions, network profiles |
| **this repo** | sites, prefixes, DHCP pool ranges, gateways, managed IPs, VLAN groups + VLANs, device types, manufacturers, device roles, platforms, custom-field definitions, network profiles |
| **autodiscovery agent** | device records, interfaces, MAC addresses, switch cabling |
| **UI** | host names, role assignment, IP allocation (inventory -> planned) |
Writing a device from here would fight both of them, so the module declares no
`netbox_device`.
`netbox_device`. The handful of network switches are created by hand in the
NetBox UI; this repo only defines the `chassis_mac` field discovery matches them
on.
> Supersedes `terraform-infra`, which also drove Kea DHCP. Kea is managed by the
> [kea-operator](https://git.unkin.net/unkin/kea-operator) CRs in argocd-apps
@@ -23,7 +25,7 @@ Writing a device from here would fight both of them, so the module declares no
```
config/<region>/<dc>/sites.yaml # site slug -> name/description
config/<region>/<dc>/subnets/net-<a>-<b>-<c>-<d>-<len>.yaml
config/<region>/<dc>/device-types/<slug>.yaml # manufacturer, model, port templates
config/<region>/<dc>/device-types/<slug>.yaml # manufacturer, model
config/<region>/<dc>/device-roles.yaml
config/<region>/<dc>/platforms.yaml # slug -> display name
config/<region>/<dc>/vlans.yaml # keyed per site
@@ -64,22 +66,10 @@ repo without re-deriving them.
## Device types (`device-types/<slug>.yaml`)
`ports` declares interface templates as ranges, not 28 hand-written entries.
NetBox instantiates them on every device created from the type.
```yaml
manufacturer: TP-Link
model: Omada SG3429X
manufacturer: Dell
model: OptiPlex 3070
u_height: 1
ports:
- name_format: two-gigabitEthernet 1/0/%d # single %d
type: 2.5gbase-t
first: 1
count: 24
- name_format: ten-gigabitEthernet 1/0/%d
type: 10gbase-x-sfpp
first: 25
count: 4
```
## VLANs (`vlans.yaml`)
@@ -115,7 +105,8 @@ interfaces:
## Custom fields (`custom-fields.yaml`)
Field **definitions** for what bootapi reads: `pxe_enabled`,
`provision_template`, `domain`, `nameservers`, `gateway`. `content_types` are
`provision_template`, `domain`, `nameservers`, `gateway`; plus `chassis_mac`,
which autodiscovery matches an LLDP neighbour on. `content_types` are
NetBox `app_label.model` strings. `pxe_enabled`'s *value* is deliberately not
managed here — it is runtime state cleared by bootapi's `/provisioned` callback.
+7
View File
@@ -36,3 +36,10 @@ gateway:
content_types:
- dcim.device
- ipam.ipaddress
chassis_mac:
type: text
label: Chassis MAC
description: LLDP advertises a neighbour's ChassisID MAC but not its serial, so the autodiscovery agent matches an LLDP neighbour to this device by chassis MAC rather than the mutable SysName.
content_types:
- dcim.device
-14
View File
@@ -1,14 +0,0 @@
# TP-Link Omada SG3429X: 24x 2.5G copper + 4x 10G SFP+.
# NetBox instantiates these interfaces on every device created from the type.
manufacturer: TP-Link
model: Omada SG3429X
u_height: 1
ports:
- name_format: two-gigabitEthernet 1/0/%d
type: 2.5gbase-t
first: 1
count: 24
- name_format: ten-gigabitEthernet 1/0/%d
type: 10gbase-x-sfpp
first: 25
count: 4
-24
View File
@@ -1,20 +1,5 @@
locals {
manufacturers = toset([for t in var.device_types : t.manufacturer])
# One interface template per port, flattened to "<type slug>:<name>".
interface_templates = merge([
for slug, t in var.device_types : {
for e in flatten([
for p in t.ports : [
for i in range(p.count) : {
name = format(p.name_format, p.first + i)
type = p.type
mgmt_only = p.mgmt_only
}
]
]) : "${slug}:${e.name}" => merge(e, { device_type = slug })
}
]...)
}
resource "netbox_manufacturer" "this" {
@@ -33,15 +18,6 @@ resource "netbox_device_type" "this" {
u_height = each.value.u_height
}
resource "netbox_interface_template" "this" {
for_each = local.interface_templates
device_type_id = tonumber(netbox_device_type.this[each.value.device_type].id)
name = each.value.name
type = each.value.type
mgmt_only = each.value.mgmt_only
}
resource "netbox_device_role" "this" {
for_each = var.device_roles
+2 -12
View File
@@ -35,23 +35,13 @@ variable "subnets" {
variable "device_types" {
description = <<-EOT
Device-type catalogue keyed by NetBox slug. `ports` declares interface
templates as ranges: each block names `count` ports from `first`, formatted
with `name_format` (a single %d). NetBox instantiates them on every device
created from the type. Manufacturers are created from the distinct
`manufacturer` values.
Device-type catalogue keyed by NetBox slug. Manufacturers are created from
the distinct `manufacturer` values.
EOT
type = map(object({
manufacturer = string
model = string
u_height = optional(number, 1)
ports = optional(list(object({
name_format = string
type = string
first = optional(number, 1)
count = number
mgmt_only = optional(bool, false)
})), [])
}))
default = {}
}