Rename terraform-ipam -> terraform-infra; add devices + networks + puppetdb backfill
Scope now spans devices and provisioning, not just IPAM. - rename module/consul-path/role ipam -> infra - networks config (subnet binding + gateway/dns/search constants); prefixes tagged net:<name> - intent-only devices module: netbox_device + device_type/role/manufacturer, static or next-available IPs (sticky via ignore_changes), transitional bootstrap_mac interface for bootapi PXE keying - seed 6 pending hosts prodnxsr0014-0019 (mgmt IPs .14-.19, optiplex-3070) - ci/puppetdb_backfill.py: emit NetBox reality (serial/model/uuid/interfaces) for existing hosts Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
locals {
|
||||
# network name -> backing prefix id (from the subnets above).
|
||||
network_prefix_id = { for nname, n in var.networks : nname => netbox_prefix.this[n.subnet].id }
|
||||
|
||||
# Distinct device inventory objects to create in NetBox.
|
||||
device_models = toset([for d in var.devices : d.model_hint if d.model_hint != null])
|
||||
device_roles = toset([for d in var.devices : d.role])
|
||||
|
||||
# Devices that carry a transitional bootstrap MAC (placeholder PXE interface).
|
||||
bootstrap_devices = { for k, d in var.devices : k => d if d.bootstrap_mac != null }
|
||||
|
||||
# Flatten device network memberships: "<device>:<network>" -> {device, network, ip}.
|
||||
device_networks = merge([
|
||||
for dname, d in var.devices : {
|
||||
for nname, ip in d.networks : "${dname}:${nname}" => {
|
||||
device = dname
|
||||
network = nname
|
||||
ip = ip
|
||||
}
|
||||
}
|
||||
]...)
|
||||
|
||||
static_device_ips = { for k, dn in local.device_networks : k => dn if dn.ip != "" }
|
||||
auto_device_ips = { for k, dn in local.device_networks : k => dn if dn.ip == "" }
|
||||
}
|
||||
|
||||
resource "netbox_manufacturer" "dell" {
|
||||
count = length(local.device_models) > 0 ? 1 : 0
|
||||
name = "Dell"
|
||||
slug = "dell"
|
||||
}
|
||||
|
||||
resource "netbox_device_type" "this" {
|
||||
for_each = local.device_models
|
||||
|
||||
manufacturer_id = netbox_manufacturer.dell[0].id
|
||||
model = title(replace(each.value, "-", " "))
|
||||
slug = each.value
|
||||
}
|
||||
|
||||
resource "netbox_device_role" "this" {
|
||||
for_each = local.device_roles
|
||||
|
||||
name = each.value
|
||||
slug = lower(replace(each.value, "::", "-"))
|
||||
color_hex = "9e9e9e"
|
||||
}
|
||||
|
||||
resource "netbox_device" "this" {
|
||||
for_each = var.devices
|
||||
|
||||
name = each.key
|
||||
device_type_id = netbox_device_type.this[each.value.model_hint].id
|
||||
role_id = netbox_device_role.this[each.value.role].id
|
||||
site_id = tonumber(data.netbox_site.this[each.value.site].id)
|
||||
status = each.value.pxe ? "staged" : "active"
|
||||
}
|
||||
|
||||
# Transitional: bootapi keys PXE on MAC and no discovery image exists yet, so seed a
|
||||
# single placeholder interface carrying bootstrap_mac. Discovery replaces it with the
|
||||
# real (model-specific) NICs later; the interface name is a neutral label, not a
|
||||
# hardware assumption.
|
||||
resource "netbox_device_interface" "bootstrap" {
|
||||
for_each = local.bootstrap_devices
|
||||
|
||||
device_id = netbox_device.this[each.key].id
|
||||
name = "bootstrap"
|
||||
type = "1000base-t"
|
||||
}
|
||||
|
||||
resource "netbox_mac_address" "bootstrap" {
|
||||
for_each = local.bootstrap_devices
|
||||
|
||||
mac_address = each.value.bootstrap_mac
|
||||
device_interface_id = netbox_device_interface.bootstrap[each.key].id
|
||||
}
|
||||
|
||||
resource "netbox_ip_address" "device" {
|
||||
for_each = local.static_device_ips
|
||||
|
||||
ip_address = each.value.ip
|
||||
status = "active"
|
||||
description = each.value.device
|
||||
device_interface_id = try(netbox_device_interface.bootstrap[each.value.device].id, null)
|
||||
}
|
||||
|
||||
resource "netbox_available_ip_address" "device" {
|
||||
for_each = local.auto_device_ips
|
||||
|
||||
prefix_id = local.network_prefix_id[each.value.network]
|
||||
status = "active"
|
||||
description = each.value.device
|
||||
device_interface_id = try(netbox_device_interface.bootstrap[each.value.device].id, null)
|
||||
|
||||
# Machines are never re-IPed (a replacement is a new machine); keep the allocation sticky.
|
||||
lifecycle {
|
||||
ignore_changes = [prefix_id]
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,18 @@ locals {
|
||||
# Subnets that declare a gateway.
|
||||
gateways = { for k, v in var.subnets : k => v if v.router != null }
|
||||
|
||||
# Distinct NetBox site slugs referenced by any subnet.
|
||||
sites = toset([for v in var.subnets : v.site if v.site != null])
|
||||
# Distinct NetBox site slugs referenced by any subnet or device.
|
||||
sites = toset(concat(
|
||||
[for v in var.subnets : v.site if v.site != null],
|
||||
[for d in var.devices : d.site],
|
||||
))
|
||||
|
||||
# net:<name> tags to apply to each subnet's prefix (a prefix may back many networks).
|
||||
prefix_net_tags = {
|
||||
for sk in keys(var.subnets) : sk => [
|
||||
for nname, n in var.networks : "net:${nname}" if n.subnet == sk
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
data "netbox_site" "this" {
|
||||
@@ -17,6 +27,14 @@ data "netbox_site" "this" {
|
||||
slug = each.value
|
||||
}
|
||||
|
||||
# One tag per network so prefixes can be discovered by network membership.
|
||||
resource "netbox_tag" "network" {
|
||||
for_each = var.networks
|
||||
|
||||
name = "net:${each.key}"
|
||||
slug = "net-${each.key}"
|
||||
}
|
||||
|
||||
resource "netbox_prefix" "this" {
|
||||
for_each = var.subnets
|
||||
|
||||
@@ -24,6 +42,9 @@ resource "netbox_prefix" "this" {
|
||||
status = "active"
|
||||
description = each.value.description
|
||||
site_id = each.value.site != null ? tonumber(data.netbox_site.this[each.value.site].id) : null
|
||||
tags = [for t in local.prefix_net_tags[each.key] : t]
|
||||
|
||||
depends_on = [netbox_tag.network]
|
||||
}
|
||||
|
||||
# Role tagging a range as DHCP-managed; created once and shared by every range.
|
||||
@@ -0,0 +1,85 @@
|
||||
variable "subnets" {
|
||||
description = "Map of subnets keyed by name (config file basename)."
|
||||
type = map(object({
|
||||
prefix = string
|
||||
description = optional(string, "")
|
||||
site = optional(string)
|
||||
router = optional(number)
|
||||
dns = optional(list(string), [])
|
||||
next_server = optional(string)
|
||||
domain = optional(string)
|
||||
dhcp = optional(object({
|
||||
enabled = optional(bool, true)
|
||||
start = number
|
||||
stop = number
|
||||
}))
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = <<-EOT
|
||||
Named logical networks devices join, keyed by network name. `subnet` binds the
|
||||
network to a subnet (its prefix is tagged net:<name> and used for device IP
|
||||
allocation). gateway/dns/search are per-network provisioning constants (carried
|
||||
for downstream consumers; not per-device).
|
||||
EOT
|
||||
type = map(object({
|
||||
subnet = string
|
||||
gateway = optional(string)
|
||||
dns = optional(list(string), [])
|
||||
search = optional(string)
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "devices" {
|
||||
description = <<-EOT
|
||||
Intent-only device declarations keyed by device name. Reality (serial, real
|
||||
interface names, MACs) is owned by discovery/PuppetDB, not this file.
|
||||
`networks` maps a network name to a requested IP in CIDR form, or "" to
|
||||
allocate the next-available from the network's prefix. `bootstrap_mac` is
|
||||
transitional: it seeds a placeholder interface so bootapi can key the PXE boot
|
||||
on MAC until the discovery image exists.
|
||||
EOT
|
||||
type = map(object({
|
||||
site = string
|
||||
role = string
|
||||
model_hint = optional(string)
|
||||
provision = optional(object({
|
||||
profile = optional(string)
|
||||
platform = optional(string)
|
||||
}))
|
||||
networks = optional(map(string), {})
|
||||
pxe = optional(bool, false)
|
||||
bootstrap_mac = optional(string)
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "managed_ips" {
|
||||
description = "Manually managed extra IP addresses (full CIDR form, e.g. 198.18.15.5/24)."
|
||||
type = list(object({
|
||||
ip = string
|
||||
description = optional(string, "")
|
||||
}))
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "vault_address" {
|
||||
description = "Vault server address for the token data source."
|
||||
type = string
|
||||
default = "https://vault.service.consul:8200"
|
||||
}
|
||||
|
||||
variable "netbox_server_url" {
|
||||
description = "NetBox server base URL."
|
||||
type = string
|
||||
default = "https://netbox.k8s.syd1.au.unkin.net"
|
||||
}
|
||||
|
||||
variable "kea_endpoint" {
|
||||
description = "KeaAPI base URL (in-cluster ClusterIP service)."
|
||||
type = string
|
||||
default = "http://kea-api.dhcp-system.svc:8080"
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
variable "subnets" {
|
||||
description = "Map of subnets keyed by name (config file basename)."
|
||||
type = map(object({
|
||||
prefix = string
|
||||
description = optional(string, "")
|
||||
site = optional(string)
|
||||
router = optional(number)
|
||||
dns = optional(list(string), [])
|
||||
next_server = optional(string)
|
||||
domain = optional(string)
|
||||
dhcp = optional(object({
|
||||
enabled = optional(bool, true)
|
||||
start = number
|
||||
stop = number
|
||||
}))
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "managed_ips" {
|
||||
description = "Manually managed extra IP addresses (full CIDR form, e.g. 198.18.15.5/24)."
|
||||
type = list(object({
|
||||
ip = string
|
||||
description = optional(string, "")
|
||||
}))
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "vault_address" {
|
||||
description = "Vault server address for the token data source."
|
||||
type = string
|
||||
default = "https://vault.service.consul:8200"
|
||||
}
|
||||
|
||||
variable "netbox_server_url" {
|
||||
description = "NetBox server base URL."
|
||||
type = string
|
||||
default = "https://netbox.k8s.syd1.au.unkin.net"
|
||||
}
|
||||
|
||||
variable "kea_endpoint" {
|
||||
description = "KeaAPI base URL (in-cluster ClusterIP service)."
|
||||
type = string
|
||||
default = "http://kea-api.dhcp-system.svc:8080"
|
||||
}
|
||||
Reference in New Issue
Block a user