internal/gitops/ownership_test.go
Rama main · réplica de lectura del repositorio original.
package gitops
import (
"slices"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/sophotechlabs/spinoza/internal/api"
)
func fieldsEntry(manager, fields string) metav1.ManagedFieldsEntry {
return metav1.ManagedFieldsEntry{
Manager: manager,
Operation: metav1.ManagedFieldsOperationApply,
FieldsType: "FieldsV1",
FieldsV1: metav1.NewFieldsV1(fields),
}
}
func ssaObject(entries ...metav1.ManagedFieldsEntry) *unstructured.Unstructured {
live := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]any{"name": "web", "namespace": "shop"},
"spec": map[string]any{"replicas": int64(3)},
}}
live.SetManagedFields(entries)
return live
}
func ownersOf(t *testing.T, live *unstructured.Unstructured, controller string) map[string]string {
t.Helper()
found, _ := Ownership(live, controller)
out := map[string]string{}
for _, one := range found {
out[one.Path] = one.Declared + " -> " + one.Live
}
return out
}
func TestOwnershipNamesAFieldAnotherManagerTookFromArgo(t *testing.T) {
live := ssaObject(
fieldsEntry("argocd-controller", `{"f:spec":{"f:paused":{}}}`),
fieldsEntry("kubectl-edit", `{"f:spec":{"f:replicas":{}}}`),
)
got := ownersOf(t, live, api.ControllerArgo)
if got["spec.replicas"] != "argocd-controller -> kubectl-edit" {
t.Fatalf("ownership = %v, want spec.replicas taken by kubectl-edit", got)
}
if len(got) != 1 {
t.Fatalf("ownership = %v, want only what another writer holds", got)
}
}
func TestOwnershipNamesAFieldTakenFromFlux(t *testing.T) {
live := ssaObject(
fieldsEntry("kustomize-controller", `{"f:spec":{"f:replicas":{}}}`),
fieldsEntry("kubectl-edit", `{"f:spec":{"f:replicas":{}}}`),
)
got := ownersOf(t, live, api.ControllerFlux)
if got["spec.replicas"] != "kustomize-controller -> kubectl-edit" {
t.Fatalf("ownership = %v", got)
}
}
func TestOwnershipReadsTheApplicationControllerNameToo(t *testing.T) {
live := ssaObject(
fieldsEntry("argocd-application-controller", `{"f:spec":{"f:replicas":{}}}`),
fieldsEntry("kubectl-edit", `{"f:spec":{"f:replicas":{}}}`),
)
if got := ownersOf(t, live, api.ControllerArgo); len(got) != 1 {
t.Fatalf("ownership = %v, want the statefulset controller understood", got)
}
}
func TestOwnershipNamesASpecFieldTheControllerDoesNotHoldAtAll(t *testing.T) {
live := ssaObject(
fieldsEntry("argocd-controller", `{"f:spec":{"f:replicas":{}}}`),
fieldsEntry("some-operator", `{"f:spec":{"f:template":{"f:metadata":{"f:labels":{}}}}}`),
)
got := ownersOf(t, live, api.ControllerArgo)
want := "argocd-controller -> some-operator"
if got["spec.template.metadata.labels"] != want {
t.Fatalf("ownership = %v, want the field the controller does not hold", got)
}
}
func TestOwnershipReadsWhatARealApiServerWrote(t *testing.T) {
argo := `{"f:spec":{"f:selector":{},"f:template":{"f:spec":{"f:containers":` +
`{"k:{\"name\":\"pause\"}":{".":{},"f:image":{},"f:name":{}}}}}}}`
live := ssaObject(
fieldsEntry("argocd-controller", argo),
fieldsEntry("kubectl-edit", `{"f:spec":{"f:replicas":{}}}`),
)
got := ownersOf(t, live, api.ControllerArgo)
if len(got) != 1 {
t.Fatalf("ownership = %v, want the one field the other writer took", got)
}
if got["spec.replicas"] != "argocd-controller -> kubectl-edit" {
t.Fatalf("ownership = %v", got)
}
}
func TestOwnershipSaysSoWhenNothingHasMoved(t *testing.T) {
live := ssaObject(fieldsEntry("argocd-controller", `{"f:spec":{"f:replicas":{}}}`))
found, note := Ownership(live, api.ControllerArgo)
if found != nil {
t.Fatalf("ownership = %v, want none", found)
}
if !strings.HasPrefix(note, heldNote) {
t.Fatalf("note = %q, want it to say nothing else holds a spec field", note)
}
if !strings.HasSuffix(note, "argocd-controller") {
t.Fatalf("note = %q, want the manager named", note)
}
}
func TestOwnershipFallsBackToTheOldNoteWhenNoGitopsManagerIsThere(t *testing.T) {
live := ssaObject(fieldsEntry("kubectl-client-side-apply", `{"f:spec":{"f:replicas":{}}}`))
found, note := Ownership(live, api.ControllerArgo)
if found != nil {
t.Fatalf("ownership = %v, want none", found)
}
if note != noDeclaration {
t.Fatalf("note = %q, want the note that says there is nothing to compare", note)
}
}
func TestOwnershipIgnoresAStatusWriter(t *testing.T) {
entries := []metav1.ManagedFieldsEntry{
fieldsEntry("argocd-controller", `{"f:spec":{"f:replicas":{}}}`),
fieldsEntry("kube-controller-manager", `{"f:spec":{"f:replicas":{}}}`),
}
entries[1].Subresource = "status"
live := ssaObject(entries...)
if got := ownersOf(t, live, api.ControllerArgo); len(got) != 0 {
t.Fatalf("ownership = %v, want a status writer left out of it", got)
}
}
func TestOwnershipSkipsTheControllersOwnStatusEntry(t *testing.T) {
entries := []metav1.ManagedFieldsEntry{
fieldsEntry("argocd-controller", `{"f:status":{"f:replicas":{}}}`),
fieldsEntry("argocd-controller", `{"f:spec":{"f:paused":{}}}`),
fieldsEntry("kubectl-edit", `{"f:spec":{"f:replicas":{}}}`),
}
entries[0].Subresource = "status"
live := ssaObject(entries...)
if got := ownersOf(t, live, api.ControllerArgo); len(got) != 1 {
t.Fatalf("ownership = %v, want the spec entry used, not the status one", got)
}
}
func TestOwnershipCountsAFieldOnceWhenTwoWritersHoldIt(t *testing.T) {
live := ssaObject(
fieldsEntry("argocd-controller", `{"f:spec":{"f:paused":{}}}`),
fieldsEntry("kubectl-edit", `{"f:spec":{"f:replicas":{}}}`),
fieldsEntry("some-operator", `{"f:spec":{"f:replicas":{}}}`),
)
found, _ := Ownership(live, api.ControllerArgo)
if len(found) != 1 {
t.Fatalf("ownership = %+v, want one row for one field", found)
}
if found[0].Live != "kubectl-edit" {
t.Fatalf("taken by = %q, want the first writer that holds it", found[0].Live)
}
}
func TestFieldPathsReadTheShapesTheApiServerWrites(t *testing.T) {
cases := []struct {
name string
fields string
want string
}{
{
name: "a plain field",
fields: `{"f:spec":{"f:replicas":{}}}`,
want: "spec.replicas",
},
{
name: "a nested field",
fields: `{"f:spec":{"f:template":{"f:spec":{"f:serviceAccountName":{}}}}}`,
want: "spec.template.spec.serviceAccountName",
},
{
name: "a list entry keyed by name",
fields: `{"f:spec":{"f:template":{"f:spec":{"f:containers":{"k:{\"name\":\"web\"}":{"f:image":{}}}}}}}`,
want: "spec.template.spec.containers[web].image",
},
{
name: "a list entry keyed by something else",
fields: `{"f:spec":{"f:ports":{"k:{\"port\":80,\"protocol\":\"TCP\"}":{"f:targetPort":{}}}}}`,
want: "spec.ports[port=80,protocol=TCP].targetPort",
},
{
name: "a set entry",
fields: `{"f:spec":{"f:finalizers":{"v:\"keep\"":{}}}}`,
want: "spec.finalizers[keep]",
},
{
name: "an indexed entry",
fields: `{"f:spec":{"f:rules":{"i:0":{}}}}`,
want: "spec.rules[0]",
},
{
name: "a key the api server did not prefix",
fields: `{"f:spec":{"odd":{}}}`,
want: "spec.odd",
},
{
name: "a malformed field value",
fields: `{"f:spec":{"f:replicas":true}}`,
want: "spec.replicas",
},
{
name: "a key that is not readable json",
fields: `{"f:spec":{"f:ports":{"k:not-json":{}}}}`,
want: "spec.ports[not-json]",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
entry := fieldsEntry("argocd-controller", tc.fields)
paths := specPathsOf(&entry)
if !paths[tc.want] {
t.Fatalf("paths = %v, want %q among them", sorted(paths), tc.want)
}
})
}
}
func TestFieldPathsSurviveAnEntryThatSaysNothing(t *testing.T) {
cases := []struct {
name string
entry metav1.ManagedFieldsEntry
}{
{name: "no fields at all", entry: metav1.ManagedFieldsEntry{Manager: "argocd-controller"}},
{name: "fields that are not json", entry: fieldsEntry("argocd-controller", `not json`)},
{name: "no spec in them", entry: fieldsEntry("argocd-controller", `{"f:metadata":{"f:labels":{}}}`)},
{name: "a spec that is not a map", entry: fieldsEntry("argocd-controller", `{"f:spec":"nope"}`)},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if paths := specPathsOf(&tc.entry); len(paths) != 0 {
t.Fatalf("paths = %v, want none", sorted(paths))
}
})
}
}
func TestOwnershipStopsAfterTwentyFieldsAndCountsTheRest(t *testing.T) {
owned := map[string]any{}
for at := range 25 {
owned["f:field"+string(rune('a'+at))] = map[string]any{}
}
encoded := `{"f:spec":{`
parts := make([]string, 0, len(owned))
for key := range owned {
parts = append(parts, `"`+key+`":{}`)
}
slices.Sort(parts)
encoded += strings.Join(parts, ",") + `}}`
live := ssaObject(fieldsEntry("argocd-controller", encoded), fieldsEntry("kubectl-edit", encoded))
found, note := Ownership(live, api.ControllerArgo)
if len(found) != maxDriftFields {
t.Fatalf("ownership = %d rows, want %d", len(found), maxDriftFields)
}
if !strings.HasSuffix(note, "and 5 more") {
t.Fatalf("note = %q, want the rest counted", note)
}
}