e200c0f7d2
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).
192 lines
4.5 KiB
Go
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
|
|
}
|