pkg/ownership/view_golden_test.go
Rama main · réplica de lectura del repositorio original.
package ownership_test
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"slices"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/skyhook-io/radar/pkg/ownership"
"github.com/skyhook-io/radar/pkg/ownership/ownershiptest"
"github.com/skyhook-io/radar/pkg/resourceid"
)
var updateGolden = flag.Bool("update-ownership-golden", false, "rewrite the ownership view golden")
// The golden records every answer a View gives about every object of the
// ownership fixture, for a caller who may read everything and for one who
// may read only alpha and argocd. It was checked by hand when written; a
// change that alters an answer regenerates it with -update-ownership-golden
// and justifies each changed line.
func TestViewGolden(t *testing.T) {
w := ownershiptest.Fixture()
refs := make([]resourceid.Ref, 0, len(w.Objects))
for _, o := range w.Objects {
refs = append(refs, o.Ref)
}
slices.SortFunc(refs, func(a, b resourceid.Ref) int { return compareRef(a, b) })
record := map[string][]goldenAnswer{}
for name, caller := range map[string]ownership.Access{
"everything": ownership.Unrestricted(),
"alpha and argocd": access{namespaces: []string{"alpha", "argocd"}},
} {
v := view(t, w, caller)
for _, ref := range refs {
record[name] = append(record[name], answer(v, ref))
}
}
got, err := json.MarshalIndent(record, "", " ")
if err != nil {
t.Fatal(err)
}
got = append(got, '\n')
path := filepath.Join("testdata", "view-fixture.json")
if *updateGolden {
if err := os.MkdirAll("testdata", 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, got, 0o644); err != nil {
t.Fatal(err)
}
return
}
want, err := os.ReadFile(path)
if err != nil {
t.Fatalf("%v (run with -update-ownership-golden to create it)", err)
}
if !bytes.Equal(got, want) {
t.Fatalf("view answers differ from %s; rerun with -update-ownership-golden and justify each changed line", path)
}
}
func compareRef(a, b resourceid.Ref) int {
for _, c := range [][2]string{{a.Group, b.Group}, {a.Kind, b.Kind}, {a.Namespace, b.Namespace}, {a.Name, b.Name}} {
if c[0] != c[1] {
if c[0] < c[1] {
return -1
}
return 1
}
}
return 0
}
type goldenAnswer struct {
Object string `json:"object"`
Subject string `json:"subject"`
References []string `json:"references,omitempty"`
Owner string `json:"owner,omitempty"`
Children []string `json:"children,omitempty"`
Coverage []string `json:"childrenCoverage,omitempty"`
Chain string `json:"controllerChain,omitempty"`
Members []string `json:"members,omitempty"`
Membership string `json:"membership,omitempty"`
}
func resolutionText(r ownership.OwnerResolution) string {
s := r.State.String()
if r.Ref != (resourceid.Ref{}) {
s += " " + r.Ref.String()
}
if r.Gap.Reason != "" {
s += fmt.Sprintf(" (%s %s/%s)", r.Gap.Reason, r.Gap.Kind, r.Gap.Namespace)
}
return s
}
func answer(v *ownership.View, ref resourceid.Ref) goldenAnswer {
a := goldenAnswer{Object: ref.String()}
subject, refs := v.References(ref)
a.Subject = subject.Access.String()
if subject.Access == ownership.Allowed {
a.Subject = subject.Read.State.String()
if subject.Read.State == ownership.Unavailable {
a.Subject += " " + string(subject.Read.Gap.Reason)
}
}
if !subject.Present() {
return a
}
for _, r := range refs {
a.References = append(a.References, resolutionText(r))
}
switch owner := v.Owner(ref); {
case owner.Several:
a.Owner = "several, none a controller"
case owner.Reference != nil:
a.Owner = fmt.Sprintf("reference %d: %s", referenceIndex(owner), resolutionText(owner.Resolution))
}
children := v.Children(ref)
for _, c := range children.Children {
s := c.Ref.String()
if slices.ContainsFunc(c.OwnerReferences, func(r metav1.OwnerReference) bool {
return r.UID == subject.Read.Object.UID && isControllerRef(r)
}) {
s += " (controller)"
}
a.Children = append(a.Children, s)
}
for _, g := range children.Gaps {
a.Coverage = append(a.Coverage, fmt.Sprintf("not read: %s %s/%s", g.Reason, g.Kind, g.Namespace))
}
if children.Narrowed {
a.Coverage = append(a.Coverage, "narrowed to what the caller may read")
}
chain := v.ControllerChain(ref)
end := chain.Chain[len(chain.Chain)-1]
switch {
case chain.Cycle:
a.Chain = "cycle after " + end.String()
case chain.Truncated:
a.Chain = "truncated at " + end.String()
case chain.UnfollowedReference != nil:
a.Chain = fmt.Sprintf("%s, stopped by %s", end, resolutionText(*chain.UnfollowedReference))
case end != ref:
a.Chain = end.String()
}
if ownership.IsRecordingManager(ref.GroupKind()) {
m := v.Members(ref)
a.Membership = fmt.Sprintf("%s; %d missing, %d withheld, %d unknown", m.Membership, m.Missing, m.Withheld, m.Unknown)
for _, o := range m.Members {
a.Members = append(a.Members, o.Ref.String())
}
for _, g := range m.Gaps {
a.Membership += fmt.Sprintf("; not read: %s %s/%s", g.Reason, g.Kind, g.Namespace)
}
}
return a
}