8f356346eb
Implements the six review comments on PR #1: - Per-host PXE-enable gate: read NetBox pxe_enabled custom field; a known host with it false gets the safe local-boot script (Cobbler netboot_enabled). Add a token-guarded POST /provisioned/{ident} callback that clears pxe_enabled in NetBox, plus a %post snippet in the default kickstarts that calls it. - Templates from a git repo: bootapi clones a templates repo and re-pulls every BOOTAPI_TEMPLATE_GIT_INTERVAL (default 3m), atomically swapping the template set (last-good kept on parse failure; embedded defaults are the startup fallback). Metrics for syncs/failures/generation. - Distro catalog (catalog/*.yaml): NetBox host -> boot images/kickstart, so adding an OS is a YAML + template change. Ships almalinux + fedora entries (artifactapi remotes); debian/talos path documented. - Boot images from the artifactapi almalinux/fedora remotes via the catalog. - Bind resolvers, puppet server/CA and PUPPETCA_URL env file now target the k8s services (198.18.200.7; puppet(ca).k8s.syd1.au.unkin.net). - Boot path served over plain HTTP (installers lack CA trust) with an optional parallel HTTPS listener; docs say do not 301 the boot endpoints. New packages: internal/catalog, internal/gitsync. NetBox client gains a pxe_enabled write (token needs that scope - noted in docs). `bootapi validate` subcommand validates a template/catalog set for the templates-repo CI. go build/vet clean, go test -race green, golangci-lint v2 clean, pre-commit clean. Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
120 lines
4.6 KiB
Go
120 lines
4.6 KiB
Go
// 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 {
|
|
// DeviceID is the NetBox device id, used by the provisioned-callback to
|
|
// PATCH the pxe_enabled custom field.
|
|
DeviceID int
|
|
|
|
// 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
|
|
|
|
// PXEEnabled gates network install for this host, mirroring Cobbler's
|
|
// netboot_enabled. When false, bootapi serves the safe local-boot script
|
|
// from /ipxe even for a KNOWN host, so a provisioned machine does not
|
|
// re-install on its next PXE. nil means the NetBox custom field is unset,
|
|
// which is treated as ENABLED (a host without the field still installs).
|
|
// The end-of-kickstart callback (POST /provisioned) flips this to false.
|
|
PXEEnabled *bool
|
|
|
|
// 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
|
|
}
|
|
|
|
// ShouldPXEInstall reports whether bootapi should serve an installer boot script
|
|
// for this host. Unset (nil) is treated as enabled so hosts predating the
|
|
// custom field still provision.
|
|
func (h *Host) ShouldPXEInstall() bool {
|
|
return h.PXEEnabled == nil || *h.PXEEnabled
|
|
}
|
|
|
|
// 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
|
|
}
|