8093933500
Work out which hosts a puppet-prod change can affect, so a catalog test can
fan out over exactly those hosts instead of the whole estate.
- add cmd/catalog-impact and internal/impact
- map hieradata paths onto fact selectors from the hiera hierarchy
- trace manifest classes back to role classes through include, contain,
class {} and hiera_include, to a fixed point
- fetch the estate fact table from pdbmux in one query via FactsByName
- widen to all hosts, loudly, for any path that matches no rule
- ship the binary and its completions in the RPM
90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const estateBody = `[
|
|
{"certname":"web1.example.net","name":"enc_role","value":"roles::apps::media::jellyfin"},
|
|
{"certname":"web1.example.net","name":"enc_role_tier1","value":"apps"},
|
|
{"certname":"web1.example.net","name":"enc_role_tier2","value":"media"},
|
|
{"certname":"web1.example.net","name":"enc_role_tier3","value":"jellyfin"},
|
|
{"certname":"web1.example.net","name":"country","value":"au"},
|
|
{"certname":"web1.example.net","name":"region","value":"syd1"},
|
|
{"certname":"web1.example.net","name":"virtual","value":"lxc"},
|
|
{"certname":"web1.example.net","name":"os","value":{"name":"AlmaLinux","release":{"major":"9","minor":"7"}}},
|
|
{"certname":"k8s1.example.net","name":"enc_role","value":"roles::infra::k8s::node"},
|
|
{"certname":"k8s1.example.net","name":"enc_role_tier1","value":"infra"},
|
|
{"certname":"k8s1.example.net","name":"enc_role_tier2","value":"k8s"},
|
|
{"certname":"k8s1.example.net","name":"enc_role_tier3","value":"node"},
|
|
{"certname":"k8s1.example.net","name":"country","value":"au"},
|
|
{"certname":"k8s1.example.net","name":"region","value":"syd1"},
|
|
{"certname":"k8s1.example.net","name":"virtual","value":"physical"},
|
|
{"certname":"k8s1.example.net","name":"os","value":{"name":"AlmaLinux","release":{"major":"9","minor":"8"}}}
|
|
]`
|
|
|
|
func fakePDB(t *testing.T) string {
|
|
t.Helper()
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(estateBody))
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
return srv.URL
|
|
}
|
|
|
|
func TestRunWithExplicitFileList(t *testing.T) {
|
|
var out bytes.Buffer
|
|
opts := &options{
|
|
repo: "../../internal/impact/testdata/repo",
|
|
base: "origin/develop",
|
|
files: []string{"modules/jellyfin/manifests/install.pp"},
|
|
pdb: fakePDB(t),
|
|
timeout: 5 * time.Second,
|
|
}
|
|
if err := run(context.Background(), &out, opts); err != nil {
|
|
t.Fatalf("run: %v", err)
|
|
}
|
|
if got, want := out.String(), "web1.example.net\n"; got != want {
|
|
t.Errorf("output = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRunExplain(t *testing.T) {
|
|
var out bytes.Buffer
|
|
opts := &options{
|
|
repo: "../../internal/impact/testdata/repo",
|
|
base: "origin/develop",
|
|
files: []string{"hieradata/common.yaml"},
|
|
pdb: fakePDB(t),
|
|
explain: true,
|
|
timeout: 5 * time.Second,
|
|
}
|
|
if err := run(context.Background(), &out, opts); err != nil {
|
|
t.Fatalf("run: %v", err)
|
|
}
|
|
if !strings.Contains(out.String(), "the whole estate is affected") {
|
|
t.Errorf("explain output = %q", out.String())
|
|
}
|
|
}
|
|
|
|
func TestRunRejectsConflictingOutputFlags(t *testing.T) {
|
|
opts := &options{jsonOut: true, explain: true}
|
|
if err := run(context.Background(), &bytes.Buffer{}, opts); err == nil {
|
|
t.Fatal("expected an error for --json with --explain")
|
|
}
|
|
}
|
|
|
|
func TestRunRejectsNegativeLimit(t *testing.T) {
|
|
opts := &options{limit: -1}
|
|
if err := run(context.Background(), &bytes.Buffer{}, opts); err == nil {
|
|
t.Fatal("expected an error for a negative --limit")
|
|
}
|
|
}
|