373d21a744
Postgres-backed External Node Classifier for Puppet, replacing Cobbler. - encapi HTTP server (chi + pgx): read/write API + two ENC document shapes (reshaped for the exec terminus; cobbler-wire for enc_direct_facts.rb) - encapi-cli: classify/node/role/status CRUD + import-cobbler seeder - pkg/client Go SDK; unit tests across all packages (DB via testcontainers) - Dockerfile (distroless), Makefile, nfpm RPM (encapi-cli + encapi-enc wrapper), Woodpecker CI, docs/cutover.md
83 lines
2.8 KiB
Go
83 lines
2.8 KiB
Go
// Package enc renders the External Node Classifier documents Puppet consumes.
|
|
//
|
|
// Two shapes are produced from the same node/role data:
|
|
//
|
|
// - Final(): the reshaped document the exec node_terminus expects — classes
|
|
// as a LIST, environment omitted when it equals "testing", and
|
|
// parameters carrying enc_role (list) + enc_env. This mirrors the old
|
|
// /opt/cobbler-enc/cobbler-enc wrapper output.
|
|
// - Cobbler(): the raw cobbler-wire form — classes as a MAP keyed by role,
|
|
// environment always present. This mirrors what
|
|
// cobbler's /cblr/svc/op/puppet/hostname/<host> returned, so the
|
|
// enc_direct_facts.rb fact (which reads classes.keys.first + environment)
|
|
// keeps working by only swapping its base URL.
|
|
package enc
|
|
|
|
import (
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"git.unkin.net/unkin/encapi/pkg/models"
|
|
)
|
|
|
|
// TestingEnvironment is the sentinel environment that Puppet leaves implicit:
|
|
// when a node's environment is "testing" the key is dropped from ENC output so
|
|
// the agent falls back to its configured default environment.
|
|
const TestingEnvironment = "testing"
|
|
|
|
// mergeParams builds the effective parameter set for a node. Precedence,
|
|
// lowest to highest: distro-provided params, role default params, node params.
|
|
// The returned map is always non-nil.
|
|
func mergeParams(node models.Node, role models.Role, distro map[string]any) map[string]any {
|
|
out := map[string]any{}
|
|
for k, v := range distro {
|
|
out[k] = v
|
|
}
|
|
for k, v := range role.DefaultParams {
|
|
out[k] = v
|
|
}
|
|
for k, v := range node.Params {
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Final renders the reshaped ENC document (see package doc) as YAML.
|
|
func Final(node models.Node, role models.Role, distro map[string]any) ([]byte, error) {
|
|
params := mergeParams(node, role, distro)
|
|
// enc_role and enc_env are authoritative and computed; set them last so
|
|
// user params can never shadow them.
|
|
params["enc_role"] = []string{node.Role}
|
|
params["enc_env"] = node.Environment
|
|
|
|
doc := map[string]any{
|
|
"classes": []string{node.Role},
|
|
"parameters": params,
|
|
}
|
|
if node.Environment != TestingEnvironment {
|
|
doc["environment"] = node.Environment
|
|
}
|
|
return yaml.Marshal(doc)
|
|
}
|
|
|
|
// Cobbler renders the cobbler-wire-compatible ENC document as YAML.
|
|
func Cobbler(node models.Node, role models.Role, distro map[string]any) ([]byte, error) {
|
|
params := mergeParams(node, role, distro)
|
|
|
|
// classes is a map keyed by role name; the value is the role's params so
|
|
// class-scoped parameters survive for callers that consume them.
|
|
classParams := map[string]any{}
|
|
for k, v := range role.DefaultParams {
|
|
classParams[k] = v
|
|
}
|
|
for k, v := range node.Params {
|
|
classParams[k] = v
|
|
}
|
|
|
|
doc := map[string]any{
|
|
"classes": map[string]any{node.Role: classParams},
|
|
"environment": node.Environment,
|
|
"parameters": params,
|
|
}
|
|
return yaml.Marshal(doc)
|
|
}
|