Rename terraform-ipam -> terraform-infra; add devices + networks + puppetdb backfill
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline failed

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:
2026-08-05 00:51:06 +10:00
parent bfbe42f97e
commit 9a6f775300
20 changed files with 482 additions and 102 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ steps:
- make apply
backend_options:
kubernetes:
serviceAccountName: terraform-ipam
serviceAccountName: terraform-infra
resources:
requests:
memory: 512Mi
+1 -1
View File
@@ -11,7 +11,7 @@ steps:
- make plan
backend_options:
kubernetes:
serviceAccountName: terraform-ipam
serviceAccountName: terraform-infra
resources:
requests:
memory: 512Mi
+2 -2
View File
@@ -1,7 +1,7 @@
.PHONY: init plan apply format pre-commit
VAULT_AUTH_METHOD ?= approle
VAULT_K8S_ROLE ?= woodpecker_terraform_ipam
VAULT_K8S_ROLE ?= woodpecker_terraform_infra
VAULT_K8S_MOUNT ?= auth/k8s/au/syd1
VAULT_K8S_JWT_PATH ?= /var/run/secrets/kubernetes.io/serviceaccount/token
@@ -12,7 +12,7 @@ define vault_env
else \
export VAULT_TOKEN=$$(vault write -field=token auth/approle/login role_id=$$VAULT_ROLEID); \
fi && \
export CONSUL_HTTP_TOKEN=$$(vault read -field=token consul_root/au/syd1/creds/terraform-ipam)
export CONSUL_HTTP_TOKEN=$$(vault read -field=token consul_root/au/syd1/creds/terraform-infra)
endef
init:
+71 -49
View File
@@ -1,81 +1,103 @@
# terraform-ipam
# terraform-infra
YAML-driven Terraform/Terragrunt for IP address management: it manages NetBox
IPAM prefixes and Kea DHCP scopes together from a single subnet definition.
YAML-driven Terraform/Terragrunt for infrastructure state that belongs in NetBox +
Kea: subnets (NetBox prefixes + Kea DHCP scopes), logical networks, and physical
devices. **NetBox is authoritative for all IP/interface data**; this repo declares
intent and lets discovery/PuppetDB fill hardware reality.
Defining a subnet creates the NetBox prefix, optionally a DHCP scope in Kea (via
the `kea` provider against the KeaAPI), marks the DHCP range in NetBox, and
records the gateway.
> Renamed from `terraform-ipam` — scope now spans devices and provisioning, not just
> IPAM. The old repo name is archived by the terraform-git rename.
## Layout
```
config/<region>/<dc>/subnets/<name>.yaml # one file per subnet, filename = subnet name
config/<region>/<dc>/subnets/<name>.yaml # one file per subnet (prefix + DHCP scope)
config/<region>/<dc>/networks/<name>.yaml # logical networks devices join
config/<region>/<dc>/devices/<host>.yaml # one file per device (intent only)
config/<region>/<dc>/managed-ips.yaml # extra manually-managed IPs
environments/<region>/<dc>/terragrunt.hcl # one Terragrunt env per region/dc
modules/ipam/ # the subnet module
modules/infra/ # the module
ci/puppetdb_backfill.py # reality backfill generator (local/in-cluster)
```
`make plan` / `make apply` (via Terragrunt) reconcile the YAML against NetBox +
Kea. State lives in Consul under `infra/terraform/ipam/<region>/<dc>/state`.
`make plan` / `make apply` reconcile the YAML against NetBox + Kea. State lives in
Consul under `infra/terraform/infra/<region>/<dc>/state`.
## Subnet schema (`config/<region>/<dc>/subnets/<name>.yaml`)
## Subnet schema (`subnets/<name>.yaml`)
```yaml
prefix: 198.18.15.0/24
description: syd1 production
site: syd1 # NetBox site slug (optional; resolved to site_id)
dhcp: # omit the whole block for a NetBox-only prefix
site: syd1 # NetBox site slug (optional)
dhcp: # omit the block for a NetBox-only prefix
enabled: true
start: 200 # host-octet int; pool/range start
stop: 220 # host-octet int; pool/range stop
router: 254 # host-octet int; recorded as the gateway IP
dns: [198.18.200.7]
start: 200 # host-octet int
stop: 220
router: 254 # host-octet int -> gateway IP
dns: [198.18.200.7] # DHCP-scope DNS
next_server: 198.18.19.19
domain: main.unkin.net
```
Creates `netbox_prefix` (+ `netbox_ip_range` role `dhcp` + `kea_subnet` when `dhcp`
set, + gateway `netbox_ip_address`). A prefix backing a network is tagged `net:<name>`.
Resource mapping per subnet:
## Network schema (`networks/<name>.yaml`)
| YAML | Resource |
|---------------------|-----------------------------------------------------------------|
| `prefix` | `netbox_prefix` (`status=active`, `description`, `site_id`) |
| `dhcp.start/stop` | `netbox_ip_range` (role `dhcp`) + `kea_subnet` pool |
| `router` | `netbox_ip_address` `<router>/mask` description `gateway` |
| `dns/next_server/domain` | `kea_subnet` dns_servers / next_server / domain_name |
Per-network provisioning constants — hoisted here, never repeated per device.
Host-octet ints are expanded against the prefix with `cidrhost()`. A subnet with
no `dhcp` block is NetBox-only (e.g. `local.yaml` = 198.18.25.0/24 — excluded
from Kea). The `dhcp` role is a single shared `netbox_ipam_role`. PXE boot files
are handled by Kea cluster client-classes, not here.
```yaml
subnet: net-198-18-15 # which subnet's prefix backs this network
gateway: 198.18.15.254
dns: [198.18.19.16]
search: main.unkin.net
```
## Managed IPs (`config/<region>/<dc>/managed-ips.yaml`)
## Device schema (`devices/<host>.yaml`) — intent only
A list of `{ip, description}` (ip in full CIDR form) rendered as `netbox_ip_address`.
Declare only human intent. Serial, real interface names (which vary by model —
OptiPlex 3060 = `enp1s0`, 3050/7080 = `enp2s0`), MACs and inventory are owned by the
first-boot discovery image / PuppetDB, never hardcoded here.
```yaml
site: syd1
role: roles::base # puppet mgmt_class / ENC role -> NetBox device role
model_hint: optiplex-3070 # NetBox device type; discovery confirms via dmidecode
provision:
profile: almalinux9-dell_3070 # cobbler profile / kickstart template
platform: almalinux9
networks:
mgmt: 198.18.15.14/24 # requested IP; "" = next-available from the prefix
pxe: true
bootstrap_mac: a4:bb:6d:xx:xx:xx # TRANSITIONAL — see below
```
Mapping: `netbox_device` (+ `netbox_manufacturer`/`netbox_device_type`/
`netbox_device_role`), `netbox_ip_address` (static) or `netbox_available_ip_address`
(next-available, `ignore_changes` so a machine is never re-IPed). `bootstrap_mac` is
**transitional**: it seeds one placeholder `netbox_device_interface` + `netbox_mac_address`
so bootapi can key the PXE boot on MAC until the discovery image exists — remove it
once discovery/backfill populates the real NICs.
## PuppetDB reality backfill
`ci/puppetdb_backfill.py` sweeps already-provisioned hosts and emits their hardware
reality (serial/model/UUID + every interface's MAC/IPs, incl. overlay/loopback
addresses Cobbler lacks) as reviewable YAML — the NetBox *reality* side, not the
intent above. PuppetDB needs no auth but is **not reachable from CI**; run it
in-cluster/from puppet infra. Wiring its output into NetBox is a follow-up (issue).
## Providers
| Provider | Source | Notes |
|----------|--------|-------|
| netbox | `e-breuninger/netbox` 4.3.0 | community provider from the OpenTofu registry |
| kea | `artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/kea` 0.0.1 | KeaAPI; `cluster_ref=kea` |
| vault | `hashicorp/vault` 5.6.0 | reads the KeaAPI + NetBox tokens |
| netbox | `e-breuninger/netbox` 4.3.0 | OpenTofu registry |
| kea | `artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/kea` 0.0.1 | `cluster_ref=kea` |
| vault | `hashicorp/vault` 5.6.0 | reads NetBox + Kea tokens |
Endpoints (module variable defaults): NetBox `https://netbox.k8s.syd1.au.unkin.net`,
KeaAPI `http://kea-api.dhcp-system.svc:8080` (ClusterIP, in-cluster only).
Tokens: Vault KV v2 `kv/service/terraform/infra` fields `netbox_token` + `kea_token`.
Tokens are read at plan time from Vault KV v2 `kv/service/terraform/ipam` with
fields `netbox_token` and `kea_token` (the vault provider authenticates with the
CI `VAULT_TOKEN`).
## Blockers
## Deployment status / blockers
- **NetBox is not deployed** on the cluster yet (only on the unmerged
`benvin/netbox` argocd-apps branch; host `netbox.k8s.syd1.au.unkin.net`). Until
it is live and reachable, `plan`/`apply` against NetBox will fail.
- **KeaAPI is not deployed** yet (only on the unmerged `benvin/kea-deployment`
argocd-apps branch; `KeaAPI` CR `kea-api`, ns `dhcp-system`, ClusterIP `:8080`).
- **Seed `kv/service/terraform/ipam`** in Vault with `netbox_token` +
`kea_token` before the pipeline can authenticate to either API.
- **Confirm the `kea` provider 0.0.1** is published to the `terraform-unkin`
ArtifactAPI registry (release the provider repo if not).
- **NetBox not deployed** yet (unmerged `benvin/netbox` argocd branch); NetBox sites
must pre-exist. **KeaAPI not deployed** yet (argocd-apps PR #333). Seed
`kv/service/terraform/infra` before the pipeline can auth.
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Backfill NetBox device *reality* from PuppetDB for already-provisioned hosts.
This emits the hardware-owned facts a running host reports (serial, model, UUID,
and every interface's name/MAC/IPs including the overlay/loopback addresses
Cobbler never had) as reviewable per-host YAML. It targets the *reality* side of
NetBox (interfaces + inventory) for the ~13 existing physicals; it is NOT the
intent `config/.../devices/*.yaml` (a human writes those).
PuppetDB needs no auth (TLS terminates at the gateway; upstream is plain HTTP).
It is NOT reachable from CI, so run this in-cluster or from puppet infra:
./puppetdb_backfill.py --out ../reality \
--url http://puppetdb.puppet.svc.cluster.local:8080/pdb/query/v4/facts \
prodnxsr0001 prodnxsr0002 ...
Fact paths follow Facter conventions (networking.*, dmi.*); verify against a live
factset (`GET /pdb/query/v4/factsets`) before trusting output on a new estate.
"""
import argparse
import json
import os
import sys
import urllib.parse
import urllib.request
DEFAULT_URL = "http://puppetdb.puppet.svc.cluster.local:8080/pdb/query/v4/facts"
def query_facts(url, certname):
ast = json.dumps(["=", "certname", certname])
full = url + "?" + urllib.parse.urlencode({"query": ast})
with urllib.request.urlopen(full, timeout=30) as resp:
rows = json.load(resp)
return {row["name"]: row["value"] for row in rows}
def device_reality(certname, facts):
networking = facts.get("networking", {}) or {}
ifaces = networking.get("interfaces", {}) or {}
dmi = facts.get("dmi", {}) or {}
product = dmi.get("product", {}) or {}
interfaces = []
for name, data in sorted(ifaces.items()):
if name == "lo":
continue
ips = []
for b in (data.get("bindings", []) or []):
if b.get("address"):
ips.append(b["address"])
if data.get("ip") and data["ip"] not in ips:
ips.append(data["ip"])
interfaces.append({
"name": name,
"mac": data.get("mac"),
"ips": ips,
})
return {
"device": certname.split(".")[0],
"serial": product.get("serial_number") or facts.get("serialnumber"),
"model": product.get("name") or facts.get("productname"),
"uuid": product.get("uuid") or facts.get("uuid"),
"interfaces": interfaces,
}
def to_yaml(d):
# tiny dependency-free YAML emitter for this fixed shape
out = [
f"device: {d['device']}",
f"serial: {d['serial'] or ''}",
f"model: {d['model'] or ''}",
f"uuid: {d['uuid'] or ''}",
"interfaces:",
]
for i in d["interfaces"]:
out.append(f" - name: {i['name']}")
out.append(f" mac: {i['mac'] or ''}")
out.append(" ips: [%s]" % ", ".join(i["ips"]))
return "\n".join(out) + "\n"
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("hosts", nargs="+", help="certnames or short hostnames")
ap.add_argument("--url", default=os.environ.get("PUPPETDB_URL", DEFAULT_URL))
ap.add_argument("--out", help="write <host>.yaml here instead of stdout")
args = ap.parse_args()
for host in args.hosts:
certname = host if "." in host else f"{host}.main.unkin.net"
try:
facts = query_facts(args.url, certname)
except Exception as e: # noqa: BLE001
print(f"# {certname}: query failed: {e}", file=sys.stderr)
continue
if not facts:
print(f"# {certname}: no facts (not in PuppetDB)", file=sys.stderr)
continue
text = to_yaml(device_reality(certname, facts))
if args.out:
os.makedirs(args.out, exist_ok=True)
path = os.path.join(args.out, f"{certname.split('.')[0]}.yaml")
with open(path, "w") as fh:
fh.write(text)
print(f"wrote {path}", file=sys.stderr)
else:
print(text)
if __name__ == "__main__":
main()
+11
View File
@@ -0,0 +1,11 @@
site: syd1
role: roles::base
model_hint: optiplex-3070
provision:
profile: almalinux9-dell_3070
platform: almalinux9
networks:
mgmt: 198.18.15.14/24
pxe: true
# transitional: removed once the discovery image / puppetdb backfill populates real NIC MACs
bootstrap_mac: a4:bb:6d:a4:e5:c1
+11
View File
@@ -0,0 +1,11 @@
site: syd1
role: roles::base
model_hint: optiplex-3070
provision:
profile: almalinux9-dell_3070
platform: almalinux9
networks:
mgmt: 198.18.15.15/24
pxe: true
# transitional: removed once the discovery image / puppetdb backfill populates real NIC MACs
bootstrap_mac: a4:bb:6d:a6:30:c4
+11
View File
@@ -0,0 +1,11 @@
site: syd1
role: roles::base
model_hint: optiplex-3070
provision:
profile: almalinux9-dell_3070
platform: almalinux9
networks:
mgmt: 198.18.15.16/24
pxe: true
# transitional: removed once the discovery image / puppetdb backfill populates real NIC MACs
bootstrap_mac: a4:bb:6d:9f:22:13
+11
View File
@@ -0,0 +1,11 @@
site: syd1
role: roles::base
model_hint: optiplex-3070
provision:
profile: almalinux9-dell_3070
platform: almalinux9
networks:
mgmt: 198.18.15.17/24
pxe: true
# transitional: removed once the discovery image / puppetdb backfill populates real NIC MACs
bootstrap_mac: 8c:04:ba:9c:b6:08
+11
View File
@@ -0,0 +1,11 @@
site: syd1
role: roles::base
model_hint: optiplex-3070
provision:
profile: almalinux9-dell_3070
platform: almalinux9
networks:
mgmt: 198.18.15.18/24
pxe: true
# transitional: removed once the discovery image / puppetdb backfill populates real NIC MACs
bootstrap_mac: a4:bb:6d:a4:db:94
+11
View File
@@ -0,0 +1,11 @@
site: syd1
role: roles::base
model_hint: optiplex-3070
provision:
profile: almalinux9-dell_3070
platform: almalinux9
networks:
mgmt: 198.18.15.19/24
pxe: true
# transitional: removed once the discovery image / puppetdb backfill populates real NIC MACs
bootstrap_mac: a4:bb:6d:a4:56:11
+4
View File
@@ -0,0 +1,4 @@
subnet: net-198-18-15
gateway: 198.18.15.254
dns: [198.18.19.16]
search: main.unkin.net
+15 -1
View File
@@ -15,15 +15,29 @@ locals {
trimsuffix(basename(f), ".yaml") => yamldecode(file("${local.cfg_dir}/${f}"))
}
network_files = fileset(local.cfg_dir, "networks/*.yaml")
networks = {
for f in local.network_files :
trimsuffix(basename(f), ".yaml") => yamldecode(file("${local.cfg_dir}/${f}"))
}
device_files = fileset(local.cfg_dir, "devices/*.yaml")
devices = {
for f in local.device_files :
trimsuffix(basename(f), ".yaml") => yamldecode(file("${local.cfg_dir}/${f}"))
}
managed_ips_file = "${local.cfg_dir}/managed-ips.yaml"
managed_ips = fileexists(local.managed_ips_file) ? yamldecode(file(local.managed_ips_file)) : []
}
terraform {
source = "${get_repo_root()}/modules/ipam"
source = "${get_repo_root()}/modules/infra"
}
inputs = {
subnets = local.subnets
networks = local.networks
devices = local.devices
managed_ips = local.managed_ips
}
+1 -1
View File
@@ -5,7 +5,7 @@ generate "backend" {
terraform {
backend "consul" {
address = "https://consul.service.consul"
path = "infra/terraform/ipam/${path_relative_to_include()}/state"
path = "infra/terraform/infra/${path_relative_to_include()}/state"
scheme = "https"
lock = true
ca_file = "/etc/pki/tls/certs/ca-bundle.crt"
+99
View File
@@ -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]
}
}
+23 -2
View File
@@ -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.
+85
View File
@@ -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"
}
-45
View File
@@ -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"
}