Files
unkin-agent 8093933500
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
add the catalog-impact binary
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
2026-09-12 16:15:00 +10:00

89 lines
3.7 KiB
Go

package impact
import (
"reflect"
"testing"
)
// fixtureNodes mirrors the shape of the real estate: one country, two regions,
// physical and virtual hosts, and a mix of OS releases.
func fixtureNodes() []Node {
return []Node{
{Certname: "web1", Role: "roles::apps::media::jellyfin",
Tier1: "apps", Tier2: "media", Tier3: "jellyfin",
Country: "au", Region: "syd1", Virtual: "lxc",
OSName: "AlmaLinux", OSMajor: "9", OSMinor: "7"},
{Certname: "k8s1", Role: "roles::infra::k8s::node",
Tier1: "infra", Tier2: "k8s", Tier3: "node",
Country: "au", Region: "syd1", Virtual: "physical",
OSName: "AlmaLinux", OSMajor: "9", OSMinor: "8"},
{Certname: "k8s2", Role: "roles::infra::k8s::node",
Tier1: "infra", Tier2: "k8s", Tier3: "node",
Country: "au", Region: "drw1", Virtual: "physical",
OSName: "Debian", OSMajor: "12", OSMinor: "1"},
{Certname: "prox1", Role: "roles::infra::proxmox::node",
Tier1: "infra", Tier2: "proxmox", Tier3: "node",
Country: "nz", Region: "akl1", Virtual: "physical",
OSName: "AlmaLinux", OSMajor: "8", OSMinor: "10"},
{Certname: "bare1", Role: "roles::base", Tier1: "base",
Country: "au", Region: "syd1", Virtual: "lxc",
OSName: "AlmaLinux", OSMajor: "9", OSMinor: "7"},
}
}
func TestSelectorSelect(t *testing.T) {
nodes := fixtureNodes()
tests := []struct {
name string
sel Selector
want []string
}{
{"all", Selector{All: true}, []string{"web1", "k8s1", "k8s2", "prox1", "bare1"}},
{"certname", Selector{Certname: "k8s2"}, []string{"k8s2"}},
{"role exact", Selector{RoleExact: "roles::infra::k8s::node"}, []string{"k8s1", "k8s2"}},
{"tier1 prefix", Selector{RoleTiers: []string{"infra"}}, []string{"k8s1", "k8s2", "prox1"}},
{"tier2 prefix", Selector{RoleTiers: []string{"infra", "k8s"}}, []string{"k8s1", "k8s2"}},
{"tier3 exact", Selector{RoleTiers: []string{"apps", "media", "jellyfin"}}, []string{"web1"}},
{"tier1 only node", Selector{RoleTiers: []string{"base"}}, []string{"bare1"}},
{"tier prefix longer than node", Selector{RoleTiers: []string{"base", "x"}}, nil},
{"country", Selector{Country: "au"}, []string{"web1", "k8s1", "k8s2", "bare1"}},
{"country region", Selector{Country: "au", Region: "syd1"}, []string{"web1", "k8s1", "bare1"}},
{"country region role", Selector{Country: "au", Region: "syd1", RoleTiers: []string{"infra"}},
[]string{"k8s1"}},
{"country role", Selector{Country: "au", RoleTiers: []string{"infra", "k8s"}}, []string{"k8s1", "k8s2"}},
{"virtual", Selector{Virtual: "physical"}, []string{"k8s1", "k8s2", "prox1"}},
{"os name", Selector{OSName: "AlmaLinux"}, []string{"web1", "k8s1", "prox1", "bare1"}},
{"os major", Selector{OSName: "AlmaLinux", OSMajor: "9"}, []string{"web1", "k8s1", "bare1"}},
{"os major minor", Selector{OSName: "AlmaLinux", OSMajor: "9", OSMinor: "7"},
[]string{"web1", "bare1"}},
{"os name case insensitive", Selector{OSName: "almalinux", OSMajor: "8"}, []string{"prox1"}},
{"os other name", Selector{OSName: "Debian", OSMajor: "12"}, []string{"k8s2"}},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := tc.sel.Select(nodes); !reflect.DeepEqual(got, tc.want) {
t.Errorf("Select() = %v, want %v", got, tc.want)
}
})
}
}
func TestSelectorString(t *testing.T) {
if got := (Selector{All: true}).String(); got != "all hosts" {
t.Errorf("all selector = %q", got)
}
got := Selector{Country: "au", Region: "syd1", RoleTiers: []string{"infra", "puppet"}}.String()
want := "role-prefix=infra::puppet country=au region=syd1"
if got != want {
t.Errorf("String() = %q, want %q", got, want)
}
}
func TestNodeTiersTruncatesAtGap(t *testing.T) {
n := Node{Tier1: "base"}
if got := n.Tiers(); !reflect.DeepEqual(got, []string{"base"}) {
t.Errorf("Tiers() = %v", got)
}
}