Initial bootapi: NetBox-driven PXE/kickstart boot service
bootapi replaces Cobbler's PXE/kickstart side. It resolves a PXE-booting host
from NetBox (by MAC or hostname), renders an iPXE boot script and a kickstart
from Go text/templates, and serves them over HTTP. The ENC half already moved to
encapi; this covers the provisioning/boot half.
What's here:
- cmd/bootapi + internal/{config,model,netbox,render,server}; embedded default
templates under templates/ (AlmaLinux 9 + Fedora kickstarts, iPXE boot +
unknown-MAC fallbacks) ported from Cobbler's boot/bootstrap contract.
- NetBox client (v4.x API) behind a Resolver interface with a short-TTL cache;
tested against httptest fixtures using real NetBox JSON shapes.
- chi HTTP server: /ipxe/{mac}, /boot/ipxe?mac=, /ks/{ident}, healthz/readyz,
Prometheus /metrics. Unknown MAC -> safe fallback iPXE (200), unknown KS -> 404.
- Secrets (root pw hash, ssh keys) injected at render time from env/Vault, never
NetBox. Config is env-based per estate convention.
- Makefile (build/test/lint/docker + patch/minor/major), Dockerfile (distroless),
.woodpecker (pre-commit, golangci-lint v2 + go test -race, docker build on PR;
image push + Gitea binary release on v* tag), docs/ and example config.
go build/vet clean, go test -race green, golangci-lint v2 clean, pre-commit clean.
Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// Package model holds the provisioning data model bootapi renders templates
|
||||
// against. A Host is the normalized view of a NetBox device: enough to build a
|
||||
// kickstart and an iPXE boot script without the template author needing to know
|
||||
// anything about NetBox's API shapes.
|
||||
package model
|
||||
|
||||
// Host is the fully-resolved provisioning view of a single machine.
|
||||
//
|
||||
// Every field here is safe to reference from a kickstart or iPXE template. The
|
||||
// zero value of a field means "NetBox did not provide it"; templates should
|
||||
// guard optional fields (e.g. Gateway) accordingly.
|
||||
type Host struct {
|
||||
// Hostname is the short name (NetBox device name), e.g. "web01".
|
||||
Hostname string
|
||||
// Domain is the DNS domain the host lives in, e.g. "syd1.au.unkin.net".
|
||||
Domain string
|
||||
// FQDN is Hostname joined to Domain when a domain is known, else Hostname.
|
||||
FQDN string
|
||||
|
||||
// Platform is the NetBox platform slug, e.g. "almalinux9". It is the
|
||||
// primary template-selection key.
|
||||
Platform string
|
||||
// OSFamily is a coarse family derived from Platform ("almalinux",
|
||||
// "fedora", "rocky", ...). Handy for shared template logic.
|
||||
OSFamily string
|
||||
// OSVersion is the major version string when derivable, e.g. "9".
|
||||
OSVersion string
|
||||
// Arch is the CPU architecture, defaulting to "x86_64".
|
||||
Arch string
|
||||
|
||||
// Role is the NetBox device role slug, e.g. "kubernetes-worker". Available
|
||||
// as a secondary template-selection key and for %post logic.
|
||||
Role string
|
||||
|
||||
// Interfaces are the host's network interfaces, primary first.
|
||||
Interfaces []Interface
|
||||
|
||||
// PrimaryIP is the address of the primary interface (no prefix length),
|
||||
// e.g. "10.0.1.20". Empty when NetBox has no primary IP set.
|
||||
PrimaryIP string
|
||||
|
||||
// Nameservers are DNS resolvers to configure, when NetBox provides them
|
||||
// (via a custom field); otherwise empty and templates fall back to a
|
||||
// site default.
|
||||
Nameservers []string
|
||||
|
||||
// RootPasswordHash is a crypt(3) hash for the root account, sourced at
|
||||
// render time (env/Vault), NOT stored in NetBox. Empty means "locked
|
||||
// account / template default".
|
||||
RootPasswordHash string
|
||||
|
||||
// SSHAuthorizedKeys are public keys to install for root, sourced at render
|
||||
// time. Empty means none.
|
||||
SSHAuthorizedKeys []string
|
||||
|
||||
// TemplateOverride, when non-empty, names the template to use verbatim,
|
||||
// bypassing platform/role selection. Sourced from a NetBox custom field.
|
||||
TemplateOverride string
|
||||
|
||||
// Custom carries every NetBox custom field verbatim so templates can read
|
||||
// site-specific knobs without a code change. Keys are the custom-field
|
||||
// names as defined in NetBox.
|
||||
Custom map[string]any
|
||||
}
|
||||
|
||||
// Interface is one network interface of a Host.
|
||||
type Interface struct {
|
||||
// Name is the NetBox interface name, e.g. "eth0" / "bond0".
|
||||
Name string
|
||||
// MAC is the normalized (lower-case, colon-separated) hardware address.
|
||||
MAC string
|
||||
// IP is the interface address without prefix, e.g. "10.0.1.20". Empty for
|
||||
// interfaces with no assigned address.
|
||||
IP string
|
||||
// PrefixLen is the CIDR prefix length of IP, e.g. 24. Zero when unknown.
|
||||
PrefixLen int
|
||||
// Netmask is the dotted-quad form of PrefixLen, e.g. "255.255.255.0".
|
||||
Netmask string
|
||||
// Gateway is the default gateway for this interface's prefix, when NetBox
|
||||
// records one on the prefix. Empty otherwise.
|
||||
Gateway string
|
||||
// VLAN is the untagged VLAN id of the interface, or 0 when none.
|
||||
VLAN int
|
||||
// Primary reports whether this interface holds the device's primary IP.
|
||||
Primary bool
|
||||
}
|
||||
|
||||
// PrimaryInterface returns the primary interface (the one carrying the primary
|
||||
// IP), falling back to the first interface, or nil when there are none.
|
||||
func (h *Host) PrimaryInterface() *Interface {
|
||||
for i := range h.Interfaces {
|
||||
if h.Interfaces[i].Primary {
|
||||
return &h.Interfaces[i]
|
||||
}
|
||||
}
|
||||
if len(h.Interfaces) > 0 {
|
||||
return &h.Interfaces[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user