Files
encapic/render_test.go
benvin e200c0f7d2
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline was successful
Add encapic ENC client
The uv/python ENC script fails its first-invocation dependency resolution on
fresh compiler pods (exits 135/2), which breaks puppet agent catalog
compilation. encapic is a stdlib-only Go replacement with no runtime
dependency resolution.

- Add encapic CLI: fetch the cobbler-wire ENC document from encapi
  (ENCAPI_URL override, in-cluster default compiled in) and reshape it to
  match the python script byte-for-byte (classes to list, enc_role/enc_env
  parameters, environment dropped when testing), exiting non-zero on any
  HTTP/parse failure so the puppet exec ENC fails safe.
- Hand-parse the small fixed cobbler-wire YAML and hand-emit the output using
  the standard library only.
- Add table-driven normalisation tests, httptest 200/404/500/timeout tests,
  and a golden-output test.
- Add Makefile (build/test/lint/fmt + patch/minor/major with version
  ldflags), .gitignore, .pre-commit-config.yaml, and Woodpecker pipelines
  (build/test/pre-commit on pull_request; release on v* tags).
2026-07-25 09:55:31 +10:00

192 lines
4.5 KiB
Go

package main
import "testing"
func TestRenderENC(t *testing.T) {
tests := []struct {
name string
doc cobblerDoc
want string
}{
{
name: "single class with develop environment",
doc: cobblerDoc{
classes: []string{"roles::infra::storage::vault"},
environment: "develop",
hasEnv: true,
parameters: map[string]string{},
},
want: "classes:\n" +
"- roles::infra::storage::vault\n" +
"environment: develop\n" +
"parameters:\n" +
" enc_env: develop\n" +
" enc_role:\n" +
" - roles::infra::storage::vault\n",
},
{
name: "environment testing is dropped from top level but kept in enc_env",
doc: cobblerDoc{
classes: []string{"roles::base"},
environment: "testing",
hasEnv: true,
parameters: map[string]string{},
},
want: "classes:\n" +
"- roles::base\n" +
"parameters:\n" +
" enc_env: testing\n" +
" enc_role:\n" +
" - roles::base\n",
},
{
name: "multiple classes",
doc: cobblerDoc{
classes: []string{"roles::a", "roles::b"},
environment: "production",
hasEnv: true,
parameters: map[string]string{},
},
want: "classes:\n" +
"- roles::a\n" +
"- roles::b\n" +
"environment: production\n" +
"parameters:\n" +
" enc_env: production\n" +
" enc_role:\n" +
" - roles::a\n" +
" - roles::b\n",
},
{
name: "no environment key at all",
doc: cobblerDoc{
classes: []string{"roles::base"},
hasEnv: false,
parameters: map[string]string{},
},
want: "classes:\n" +
"- roles::base\n" +
"parameters:\n" +
" enc_role:\n" +
" - roles::base\n",
},
{
name: "pre-existing parameters are preserved and sorted with computed ones",
doc: cobblerDoc{
classes: []string{"roles::base"},
environment: "develop",
hasEnv: true,
parameters: map[string]string{"zeta": "1", "alpha": "2"},
},
want: "classes:\n" +
"- roles::base\n" +
"environment: develop\n" +
"parameters:\n" +
" alpha: '2'\n" +
" enc_env: develop\n" +
" enc_role:\n" +
" - roles::base\n" +
" zeta: '1'\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := renderENC(tt.doc)
if err != nil {
t.Fatalf("renderENC returned error: %v", err)
}
if got != tt.want {
t.Errorf("renderENC mismatch\n--- want ---\n%s\n--- got ---\n%s", tt.want, got)
}
})
}
}
func TestParseCobbler(t *testing.T) {
tests := []struct {
name string
body string
want cobblerDoc
wantErr bool
}{
{
name: "map form classes with 4-space indent (encapi cobbler wire)",
body: "classes:\n roles::infra::storage::vault: {}\nenvironment: develop\nparameters: {}\n",
want: cobblerDoc{
classes: []string{"roles::infra::storage::vault"},
environment: "develop",
hasEnv: true,
},
},
{
name: "list form classes",
body: "classes:\n- roles::base\n- roles::extra\nenvironment: develop\nparameters: {}\n",
want: cobblerDoc{
classes: []string{"roles::base", "roles::extra"},
environment: "develop",
hasEnv: true,
},
},
{
name: "inline flow list classes",
body: "classes: [roles::a, roles::b]\nenvironment: develop\nparameters: {}\n",
want: cobblerDoc{
classes: []string{"roles::a", "roles::b"},
environment: "develop",
hasEnv: true,
},
},
{
name: "empty classes map",
body: "classes: {}\nenvironment: develop\nparameters: {}\n",
want: cobblerDoc{
classes: nil,
environment: "develop",
hasEnv: true,
},
},
{
name: "unexpected top-level key",
body: "bogus: value\n",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseCobbler([]byte(tt.body))
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got none")
}
return
}
if err != nil {
t.Fatalf("parseCobbler returned error: %v", err)
}
if !equalStrings(got.classes, tt.want.classes) {
t.Errorf("classes = %v, want %v", got.classes, tt.want.classes)
}
if got.environment != tt.want.environment {
t.Errorf("environment = %q, want %q", got.environment, tt.want.environment)
}
if got.hasEnv != tt.want.hasEnv {
t.Errorf("hasEnv = %v, want %v", got.hasEnv, tt.want.hasEnv)
}
})
}
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}