diff --git a/e2e/testcases/ignore_mutation_test.go b/e2e/testcases/ignore_mutation_test.go index f1f8bb8adc..77c3986d49 100644 --- a/e2e/testcases/ignore_mutation_test.go +++ b/e2e/testcases/ignore_mutation_test.go @@ -24,15 +24,19 @@ import ( "github.com/GoogleContainerTools/config-sync/e2e/nomostest/taskgroup" nomostesting "github.com/GoogleContainerTools/config-sync/e2e/nomostest/testing" "github.com/GoogleContainerTools/config-sync/e2e/nomostest/testpredicates" + "github.com/GoogleContainerTools/config-sync/e2e/nomostest/testresourcegroup" "github.com/GoogleContainerTools/config-sync/e2e/nomostest/testwatcher" "github.com/GoogleContainerTools/config-sync/pkg/api/configsync" + "github.com/GoogleContainerTools/config-sync/pkg/api/kpt.dev/v1alpha1" "github.com/GoogleContainerTools/config-sync/pkg/core" "github.com/GoogleContainerTools/config-sync/pkg/core/k8sobjects" "github.com/GoogleContainerTools/config-sync/pkg/kinds" "github.com/GoogleContainerTools/config-sync/pkg/metadata" "github.com/GoogleContainerTools/config-sync/pkg/reconcilermanager" + "github.com/GoogleContainerTools/config-sync/pkg/resourcegroup" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status" ) @@ -250,6 +254,10 @@ func TestMutationIgnoredObjectPruned(t *testing.T) { func TestAnnotationDrift(t *testing.T) { nt := nomostest.New(t, nomostesting.DriftControl, ntopts.SyncWithGitSource(nomostest.DefaultRootSyncID, ntopts.Unstructured)) rootSyncGitRepo := nt.SyncSourceGitReadWriteRepository(nomostest.DefaultRootSyncID) + rgNN := types.NamespacedName{ + Name: nomostest.DefaultRootSyncID.Name, + Namespace: nomostest.DefaultRootSyncID.Namespace, + } nt.T.Log("Adding namespace to Git") namespace := k8sobjects.NamespaceObject("bookstore", @@ -287,9 +295,77 @@ func TestAnnotationDrift(t *testing.T) { nsObj2 := k8sobjects.NamespaceObject("new-ns") nt.Must(rootSyncGitRepo.Add("acme/ns2.yaml", nsObj2)) nt.Must(rootSyncGitRepo.CommitAndPush("add another namespace")) - nt.Must(nt.WatchForAllSyncs()) - secondCommitHash := rootSyncGitRepo.MustHash(nt.T) + nt.Must(nt.WatchForAllSyncs(nomostest.SkipAllResourceGroupChecks())) + // Explicitly verify ResourceGroup status. The mutation-ignored object should + // have "Skipped" actuation status + shortCommit := resourcegroup.TruncateSourceHash(rootSyncGitRepo.MustHash(nt.T)) + expectedStatus := testresourcegroup.EmptyStatus() + expectedStatus.ObservedGeneration = 6 + expectedStatus.ResourceStatuses = []v1alpha1.ResourceStatus{ + { + ObjMetadata: v1alpha1.ObjMetadata{ + Name: "safety-config-management-system-root-sync", + GroupKind: v1alpha1.GroupKind{ + Kind: "Namespace", + }, + }, + Status: v1alpha1.Current, + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSucceeded, + Reconcile: v1alpha1.ReconcileSucceeded, + SourceHash: shortCommit, + Conditions: nil, + }, + { + ObjMetadata: v1alpha1.ObjMetadata{ + Name: "new-ns", + GroupKind: v1alpha1.GroupKind{ + Kind: "Namespace", + }, + }, + Status: v1alpha1.Current, + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSucceeded, + Reconcile: v1alpha1.ReconcileSucceeded, + SourceHash: shortCommit, + Conditions: nil, + }, + { + ObjMetadata: v1alpha1.ObjMetadata{ + Name: "safety-config-management-system-root-sync", + GroupKind: v1alpha1.GroupKind{ + Kind: "ClusterRole", + Group: "rbac.authorization.k8s.io", + }, + }, + Status: v1alpha1.Current, + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSucceeded, + Reconcile: v1alpha1.ReconcileSucceeded, + SourceHash: shortCommit, + Conditions: nil, + }, + { + ObjMetadata: v1alpha1.ObjMetadata{ + Name: "bookstore", + GroupKind: v1alpha1.GroupKind{ + Kind: "Namespace", + }, + }, + Status: v1alpha1.Current, + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSkipped, // ActuationSkipped for ignore-mutation object + Reconcile: v1alpha1.ReconcileSucceeded, + SourceHash: shortCommit, + Conditions: nil, + }, + } + nt.Must(nt.Watcher.WatchObject(kinds.ResourceGroup(), rgNN.Name, rgNN.Namespace, + testwatcher.WatchPredicates( + testpredicates.ResourceGroupStatusEquals(expectedStatus), + ))) + secondCommitHash := rootSyncGitRepo.MustHash(nt.T) nt.Must(nt.Watcher.WatchObject(kinds.Namespace(), nsObj.Name, "", testwatcher.WatchPredicates( testpredicates.HasAnnotation(metadata.SyncTokenAnnotationKey, secondCommitHash), diff --git a/go.mod b/go.mod index f362f4f349..d952ba311b 100644 --- a/go.mod +++ b/go.mod @@ -52,8 +52,8 @@ require ( k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b k8s.io/kubectl v0.34.1 k8s.io/kubernetes v1.34.1 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/cli-utils v0.37.3-0.20250410211241-63a8e151c476 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + sigs.k8s.io/cli-utils v0.37.3-0.20251021150641-5895ad6c17dd sigs.k8s.io/controller-runtime v0.22.3 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231023142458-b9f29826ee83 sigs.k8s.io/controller-tools v0.18.0 @@ -127,7 +127,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/onsi/gomega v1.38.0 // indirect + github.com/onsi/gomega v1.38.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/otiai10/copy v1.14.0 // indirect diff --git a/go.sum b/go.sum index 68418a49b3..69ca2e54ec 100644 --- a/go.sum +++ b/go.sum @@ -321,10 +321,10 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= -github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= -github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= +github.com/onsi/ginkgo/v2 v2.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw= +github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/open-policy-agent/cert-controller v0.14.0 h1:TPc19BOHOs4tARruTT5o4bzir7Ed6FF+j3EXP/nmZBs= github.com/open-policy-agent/cert-controller v0.14.0/go.mod h1:UhE/FU54DnKo+Rt0Yf3r+oKjgy6kqSH8Vsjo+5bGrSo= github.com/open-policy-agent/frameworks/constraint v0.0.0-20241101234656-e78c8abd754a h1:gQtOJ50XFyL2Xh3lDD9zP4KQ2PY4mZKQ9hDcWc81Sp8= @@ -741,13 +741,13 @@ k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= k8s.io/kubernetes v1.34.1 h1:F3p8dtpv+i8zQoebZeK5zBqM1g9x1aIdnA5vthvcuUk= k8s.io/kubernetes v1.34.1/go.mod h1:iu+FhII+Oc/1gGWLJcer6wpyih441aNFHl7Pvm8yPto= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/cli-utils v0.37.3-0.20250410211241-63a8e151c476 h1:HjKF4Xfsh702Qx9J0Uu8QDywHA6aIKVoYFXIDTAEXZI= -sigs.k8s.io/cli-utils v0.37.3-0.20250410211241-63a8e151c476/go.mod h1:bM9dkBKOU5vmCHVg5yr3Jf6ooy4S0giTbeaskiHkZfQ= +sigs.k8s.io/cli-utils v0.37.3-0.20251021150641-5895ad6c17dd h1:QwwBjgDIYmNa4bf3AAbvsgkslFe0vF1Zgex1/ydtbDc= +sigs.k8s.io/cli-utils v0.37.3-0.20251021150641-5895ad6c17dd/go.mod h1:u5LTcoijf7f18rMNL7PVNyJzoGEriT+tS57ZSVG3nc4= sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231023142458-b9f29826ee83 h1:37G7lMdeXe0kogUnwCa1K+EIVYR5f4BqM0bqITyaDBI= diff --git a/pkg/applier/applier.go b/pkg/applier/applier.go index 52e8b2919e..f76c4e613d 100644 --- a/pkg/applier/applier.go +++ b/pkg/applier/applier.go @@ -207,7 +207,7 @@ func (s *supervisor) UpdateStatusMode(ctx context.Context) error { }) } -func (s *supervisor) processApplyEvent(ctx context.Context, e event.ApplyEvent, syncStats *stats.ApplyEventStats, objectStatusMap ObjectStatusMap, unknownTypeResources map[core.ID]struct{}, resourceMap map[core.ID]client.Object, declaredResources *declared.Resources) status.Error { +func (s *supervisor) processApplyEvent(ctx context.Context, e event.ApplyEvent, syncStats *stats.ApplyEventStats, objectStatusMap ObjectStatusMap, unknownTypeResources map[core.ID]struct{}, resourceMap map[core.ID]client.Object) status.Error { id := idFrom(e.Identifier) syncStats.Add(e.Status) @@ -231,15 +231,6 @@ func (s *supervisor) processApplyEvent(ctx context.Context, e event.ApplyEvent, case event.ApplyFailed: objectStatus.Actuation = actuation.ActuationFailed handleMetrics(ctx, "update", e.Error) - // If apply failed for an ignore-mutation object, delete it from the ignore cache. - // Normally the cached object should be updated by the remediator when it - // receives a watch event - This is a fallback to force a live lookup the - // next time the applier runs. - iObj, found := declaredResources.GetIgnored(id) - if found { - klog.Infof("Deleting object '%v' from the ignore cache (apply failed)", core.GKNN(iObj)) - declaredResources.DeleteIgnored(id) - } switch e.Error.(type) { case *applyerror.UnknownTypeError: unknownTypeResources[id] = struct{}{} @@ -317,11 +308,52 @@ func (s *supervisor) handleApplySkippedEvent(obj *unstructured.Unstructured, id return KptManagementConflictError(obj) } + var annotationErr *filter.AnnotationPreventedUpdateError + if errors.As(err, &annotationErr) { + // For applies this is desired behavior, not unexpected. The following logic + // re-applies just the CS metadata to ensure metadata does not drift. + klog.Info("Got AnnotationPreventedUpdateError") + if err := s.updateObjectMetadata(context.TODO(), obj); err != nil { + return SkipErrorForResource( + fmt.Errorf("updating Config Sync metadata for ignore mutation object: %w", err), + id, actuation.ActuationStrategyApply) + } + return nil + } + return SkipErrorForResource(err, id, actuation.ActuationStrategyApply) } +func (s *supervisor) updateObjectMetadata(ctx context.Context, obj *unstructured.Unstructured) error { + // Using PartialObjectMetadata optimizes the client calls (uses metadata client under the hood) + metaObj := &metav1.PartialObjectMetadata{} + metaObj.Name = obj.GetName() + metaObj.Namespace = obj.GetNamespace() + metaObj.SetGroupVersionKind(obj.GroupVersionKind()) + key := client.ObjectKey{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + } + if err := s.clientSet.Client.Get(ctx, key, metaObj); err != nil { + return err + } + + existing := metaObj.DeepCopy() + if metadata.UpdateConfigSyncMetadata(obj, metaObj) { + if err := s.clientSet.Client.Patch(ctx, metaObj, client.MergeFrom(existing), + client.FieldOwner(configsync.FieldManager)); err != nil { + return err + } + klog.Infof("Patched drifted CS metadata on %s", key.String()) + return nil + } + klog.Infof("Skip patching CS metadata on %s", key.String()) + return nil + +} + // processPruneEvent handles PruneEvents from the Applier -func (s *supervisor) processPruneEvent(ctx context.Context, e event.PruneEvent, syncStats *stats.PruneEventStats, objectStatusMap ObjectStatusMap, declaredResources *declared.Resources) status.Error { +func (s *supervisor) processPruneEvent(ctx context.Context, e event.PruneEvent, syncStats *stats.PruneEventStats, objectStatusMap ObjectStatusMap) status.Error { id := idFrom(e.Identifier) syncStats.Add(e.Status) @@ -340,13 +372,6 @@ func (s *supervisor) processPruneEvent(ctx context.Context, e event.PruneEvent, case event.PruneSuccessful: objectStatus.Actuation = actuation.ActuationSucceeded handleMetrics(ctx, "delete", e.Error) - - iObj, found := declaredResources.GetIgnored(id) - if found { - klog.V(3).Infof("Deleting object '%v' from the ignore cache", core.GKNN(iObj)) - declaredResources.DeleteIgnored(id) - } - return nil case event.PruneFailed: @@ -510,15 +535,6 @@ func (s *supervisor) applyInner(ctx context.Context, eventHandler func(Event), d objStatusMap := make(ObjectStatusMap) objs := declaredResources.DeclaredObjects() - if err := s.cacheIgnoreMutationObjects(ctx, declaredResources); err != nil { - sendErrorEvent(err, eventHandler) - return objStatusMap, syncStats - } - - if len(declaredResources.IgnoredObjects()) > 0 { - klog.Infof("%v mutation-ignored objects: %v", len(declaredResources.IgnoredObjects()), core.GKNNs(declaredResources.IgnoredObjects())) - } - // disabledObjs are objects for which the management are disabled // through annotation. enabledObjs, disabledObjs := partitionObjs(objs) @@ -535,10 +551,8 @@ func (s *supervisor) applyInner(ctx context.Context, eventHandler func(Event), d } } - objsToApply := handleIgnoredObjects(enabledObjs, declaredResources) - - klog.Infof("%v objects to be applied: %v", len(objsToApply), core.GKNNs(objsToApply)) - resources, err := toUnstructured(objsToApply) + klog.Infof("%v objects to be applied: %v", len(enabledObjs), core.GKNNs(enabledObjs)) + resources, err := toUnstructured(enabledObjs) if err != nil { sendErrorEvent(err, eventHandler) return objStatusMap, syncStats @@ -613,7 +627,7 @@ func (s *supervisor) applyInner(ctx context.Context, eventHandler func(Event), d } else { klog.V(1).Info(e.ApplyEvent) } - if err := s.processApplyEvent(ctx, e.ApplyEvent, syncStats.ApplyEvent, objStatusMap, unknownTypeResources, resourceMap, declaredResources); err != nil { + if err := s.processApplyEvent(ctx, e.ApplyEvent, syncStats.ApplyEvent, objStatusMap, unknownTypeResources, resourceMap); err != nil { sendErrorEvent(err, eventHandler) } case event.PruneType: @@ -622,7 +636,7 @@ func (s *supervisor) applyInner(ctx context.Context, eventHandler func(Event), d } else { klog.V(1).Info(e.PruneEvent) } - if err := s.processPruneEvent(ctx, e.PruneEvent, syncStats.PruneEvent, objStatusMap, declaredResources); err != nil { + if err := s.processPruneEvent(ctx, e.PruneEvent, syncStats.PruneEvent, objStatusMap); err != nil { sendErrorEvent(err, eventHandler) } default: @@ -836,37 +850,3 @@ func (s *supervisor) abandonObject(ctx context.Context, obj client.Object) error } return nil } - -// cacheIgnoreMutationObjects gets the current cluster state of any declared objects with the ignore mutation annotation and puts it in the Resources ignore objects cache -// Returns any errors that occur -func (s *supervisor) cacheIgnoreMutationObjects(ctx context.Context, declaredResources *declared.Resources) error { - var objsToUpdate []client.Object - declaredObjs := declaredResources.DeclaredObjects() - - for _, obj := range declaredObjs { - if obj.GetAnnotations()[metadata.LifecycleMutationAnnotation] == metadata.IgnoreMutation { - - if _, found := declaredResources.GetIgnored(core.IDOf(obj)); !found { - // Fetch the cluster state of the object if not already in the cache - uObj := &unstructured.Unstructured{} - uObj.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind()) - err := s.clientSet.Client.Get(ctx, client.ObjectKeyFromObject(obj), uObj) - - // Object doesn't exist on the cluster - if apierrors.IsNotFound(err) { - continue - } - - if err != nil { - return err - } - - objsToUpdate = append(objsToUpdate, uObj) - } - } - } - - declaredResources.UpdateIgnored(objsToUpdate...) - - return nil -} diff --git a/pkg/applier/applier_test.go b/pkg/applier/applier_test.go index 63407f51a7..6382fe049e 100644 --- a/pkg/applier/applier_test.go +++ b/pkg/applier/applier_test.go @@ -21,22 +21,16 @@ import ( "testing" "time" - "github.com/GoogleContainerTools/config-sync/pkg/api/configmanagement" "github.com/GoogleContainerTools/config-sync/pkg/api/configsync" "github.com/GoogleContainerTools/config-sync/pkg/api/configsync/v1beta1" "github.com/GoogleContainerTools/config-sync/pkg/api/kpt.dev/v1alpha1" "github.com/GoogleContainerTools/config-sync/pkg/applier/stats" - "github.com/GoogleContainerTools/config-sync/pkg/applyset" "github.com/GoogleContainerTools/config-sync/pkg/core" "github.com/GoogleContainerTools/config-sync/pkg/core/k8sobjects" "github.com/GoogleContainerTools/config-sync/pkg/declared" - "github.com/GoogleContainerTools/config-sync/pkg/diff/difftest" "github.com/GoogleContainerTools/config-sync/pkg/kinds" "github.com/GoogleContainerTools/config-sync/pkg/metadata" - "github.com/GoogleContainerTools/config-sync/pkg/remediator/queue" "github.com/GoogleContainerTools/config-sync/pkg/status" - "github.com/GoogleContainerTools/config-sync/pkg/syncer/reconcile" - "github.com/GoogleContainerTools/config-sync/pkg/syncer/syncertest" testingfake "github.com/GoogleContainerTools/config-sync/pkg/syncer/syncertest/fake" "github.com/GoogleContainerTools/config-sync/pkg/testing/testerrors" "github.com/stretchr/testify/assert" @@ -406,290 +400,6 @@ func TestApply(t *testing.T) { } } -func TestApplyMutationIgnoredObjects(t *testing.T) { - rootSyncName := "my-rs" - syncScope := declared.RootScope - applySetID := applyset.IDFromSync(rootSyncName, syncScope) - testGitCommit := "example-commit" - gitContextOutput := fmt.Sprintf(`{"repo":%q,"branch":%q,"rev":%q}`, - "example-repo", "example-branch", testGitCommit) - resourceManager := declared.ResourceManager(syncScope, rootSyncName) - - testcases := []struct { - name string - serverObjs []client.Object - declaredObjs []client.Object - cachedIgnoredObjs []client.Object - applyEvents []event.Event - expectedObjsToApply object.UnstructuredSet - expectedItemsInIgnoreCache []client.Object - }{ - { - name: "unmanaged object exists on the cluster", - serverObjs: []client.Object{ - k8sobjects.NamespaceObject("unmanaged-ns", - syncertest.ManagementDisabled, - core.Label("foo", "bar"), - ), - }, - declaredObjs: []client.Object{ - k8sobjects.NamespaceObject("unmanaged-ns", - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.ApplySetPartOfLabel, applySetID), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - difftest.ManagedBy(declared.RootScope, rootSyncName), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation), - }, - expectedObjsToApply: object.UnstructuredSet{ - asUnstructuredSanitizedObj(k8sobjects.NamespaceObject("unmanaged-ns", - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - core.Annotation(metadata.ResourceManagerKey, resourceManager), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation, - core.Generation(1), - core.UID("1"), - core.ResourceVersion("1"), - core.Label("foo", "bar"))), - }, - expectedItemsInIgnoreCache: []client.Object{ - asUnstructuredSanitizedObj( - k8sobjects.NamespaceObject("unmanaged-ns", - syncertest.ManagementDisabled, - core.Label("foo", "bar"), - core.Generation(1), - core.UID("1"), - core.ResourceVersion("1"))), - }, - }, - { - name: "managed object exists on the cluster without the ignore mutation annotation", - serverObjs: []client.Object{ - k8sobjects.NamespaceObject("managed-ns", - syncertest.ManagementEnabled, - core.Label("foo", "bar")), - }, - declaredObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("managed-ns"), - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.ApplySetPartOfLabel, applySetID), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - difftest.ManagedBy(declared.RootScope, rootSyncName), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation), - }, - expectedObjsToApply: object.UnstructuredSet{ - asUnstructuredSanitizedObj(k8sobjects.NamespaceObject("managed-ns", - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - core.Annotation(metadata.ResourceManagerKey, resourceManager), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation, - core.Generation(1), - core.UID("1"), - core.ResourceVersion("1"), - core.Label("foo", "bar"))), - }, - expectedItemsInIgnoreCache: []client.Object{ - asUnstructuredSanitizedObj(k8sobjects.NamespaceObject("managed-ns", - syncertest.ManagementDisabled, - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label("foo", "bar"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.ResourceIDKey, "_namespace_managed-ns"), - core.Generation(1), - core.UID("1"), - core.ResourceVersion("1")))}, - }, - { - name: "managed and mutation-ignored object was previously deleted", - serverObjs: []client.Object{ - k8sobjects.NamespaceObject("other-ns", - syncertest.ManagementEnabled, - core.Label("foo", "baz")), - }, - declaredObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("deleted-ns"), - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.ApplySetPartOfLabel, applySetID), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - difftest.ManagedBy(declared.RootScope, rootSyncName), - core.Label("foo", "bar"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation)}, - cachedIgnoredObjs: []client.Object{ - &queue.Deleted{ - Object: k8sobjects.UnstructuredObject(kinds.Namespace(), - core.Name("deleted-ns"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation)}}, - expectedItemsInIgnoreCache: []client.Object{ - &queue.Deleted{ - Object: k8sobjects.UnstructuredObject(kinds.Namespace(), - core.Name("deleted-ns"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation)}}, - expectedObjsToApply: object.UnstructuredSet{ - asUnstructuredSanitizedObj(k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("deleted-ns"), - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.ApplySetPartOfLabel, applySetID), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - difftest.ManagedBy(declared.RootScope, rootSyncName), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation, - core.Label("foo", "bar"))), - }, - }, - { - name: "mutation-ignored object doesn't currently exist on the cluster", - serverObjs: []client.Object{}, - declaredObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.ApplySetPartOfLabel, applySetID), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - difftest.ManagedBy(declared.RootScope, rootSyncName), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation)}, - cachedIgnoredObjs: []client.Object{}, - expectedItemsInIgnoreCache: nil, - expectedObjsToApply: object.UnstructuredSet{ - asUnstructuredSanitizedObj(k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.ApplySetPartOfLabel, applySetID), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - difftest.ManagedBy(declared.RootScope, rootSyncName), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation)), - }, - }, - { - name: "mutation-ignored object should be evicted from cache when apply fails", - serverObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", - syncertest.ManagementEnabled, - core.Label("foo", "baz")), - }, - declaredObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.ApplySetPartOfLabel, applySetID), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - difftest.ManagedBy(declared.RootScope, rootSyncName), - core.Label("foo", "bar"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation)}, - cachedIgnoredObjs: []client.Object{ - &queue.Deleted{ - Object: k8sobjects.UnstructuredObject(kinds.Namespace(), - core.Name("test-ns"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation)}}, - applyEvents: []event.Event{ - formApplyEvent(event.ApplyFailed, - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns")), - fmt.Errorf("test error")), - }, - // The object should be purged from the ignore cache due to ApplyFailed - expectedItemsInIgnoreCache: nil, - expectedObjsToApply: object.UnstructuredSet{ - asUnstructuredSanitizedObj(k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - core.Label(metadata.ManagedByKey, metadata.ManagedByValue), - core.Label(metadata.ApplySetPartOfLabel, applySetID), - core.Label(metadata.DeclaredVersionLabel, "v1"), - metadata.WithManagementMode(metadata.ManagementEnabled), - core.Annotation(metadata.GitContextKey, gitContextOutput), - core.Annotation(metadata.SyncTokenAnnotationKey, testGitCommit), - core.Annotation(metadata.OwningInventoryKey, InventoryID(rootSyncName, configmanagement.ControllerNamespace)), - difftest.ManagedBy(declared.RootScope, rootSyncName), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation, - core.Label("foo", "bar"))), - }, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - rsObj := &unstructured.Unstructured{} - rsObj.SetGroupVersionKind(kinds.RepoSyncV1Beta1()) - rsObj.SetNamespace(syncScope.SyncNamespace()) - rsObj.SetName(rootSyncName) - tc.serverObjs = append(tc.serverObjs, rsObj) - - syncScope := declared.Scope("test-namespace") - syncName := "rs" - fakeClient := testingfake.NewClient(t, core.Scheme, tc.serverObjs...) - fakeKptApplier := newFakeKptApplier(tc.applyEvents) - cs := &ClientSet{ - KptApplier: fakeKptApplier, - Client: fakeClient, - Mapper: fakeClient.RESTMapper(), - // TODO: Add tests to cover status mode - } - var errs status.MultiError - eventHandler := func(event Event) { - if errEvent, ok := event.(ErrorEvent); ok { - if errs == nil { - errs = errEvent.Error - } else { - errs = status.Append(errs, errEvent.Error) - } - } - } - - applier := NewSupervisor(cs, syncScope, syncName, 5*time.Minute) - - resources := &declared.Resources{} - _, err := resources.UpdateDeclared(context.Background(), tc.declaredObjs, "") - require.NoError(t, err) - resources.UpdateIgnored(tc.cachedIgnoredObjs...) - - applier.Apply(context.Background(), eventHandler, resources) - - testutil.AssertEqual(t, tc.expectedObjsToApply, fakeKptApplier.objsToApply) - testutil.AssertEqual(t, tc.expectedItemsInIgnoreCache, resources.IgnoredObjects()) - }) - } -} - func TestNewSupervisor(t *testing.T) { testCases := map[string]struct { scope declared.Scope @@ -977,11 +687,8 @@ func TestProcessApplyEvent(t *testing.T) { resourceMap[deploymentObjID] = deploymentObj resourceMap[testObj1ID] = testObj1 - resources := &declared.Resources{} - resources.UpdateIgnored(testObj1) - // process failed apply of deploymentObj - err := s.processApplyEvent(ctx, formApplyEvent(event.ApplyFailed, deploymentObj, fmt.Errorf("test error")).ApplyEvent, syncStats.ApplyEvent, objStatusMap, unknownTypeResources, resourceMap, resources) + err := s.processApplyEvent(ctx, formApplyEvent(event.ApplyFailed, deploymentObj, fmt.Errorf("test error")).ApplyEvent, syncStats.ApplyEvent, objStatusMap, unknownTypeResources, resourceMap) expectedError := ErrorForResourceWithResource(fmt.Errorf("test error"), deploymentObjID, deploymentObj) testutil.AssertEqual(t, expectedError, err, "expected processApplyEvent to error on apply %s", event.ApplyFailed) @@ -1000,10 +707,9 @@ func TestProcessApplyEvent(t *testing.T) { }}, } testutil.AssertEqual(t, expectedCSE, err.ToCSE(), "expected CSEs to match") - testutil.AssertEqual(t, 1, len(resources.IgnoredObjects())) // process successful apply of testObj1 - err = s.processApplyEvent(ctx, formApplyEvent(event.ApplySuccessful, testObj1, nil).ApplyEvent, syncStats.ApplyEvent, objStatusMap, unknownTypeResources, resourceMap, resources) + err = s.processApplyEvent(ctx, formApplyEvent(event.ApplySuccessful, testObj1, nil).ApplyEvent, syncStats.ApplyEvent, objStatusMap, unknownTypeResources, resourceMap) assert.Nil(t, err, "expected processApplyEvent NOT to error on apply %s", event.ApplySuccessful) expectedApplyStatus := stats.NewSyncStats() @@ -1022,10 +728,9 @@ func TestProcessApplyEvent(t *testing.T) { }, } testutil.AssertEqual(t, expectedObjStatusMap, objStatusMap, "expected object status to match") - testutil.AssertEqual(t, 1, len(resources.IgnoredObjects())) // process failed apply of testObj1 (ignore-mutation object) - err = s.processApplyEvent(ctx, formApplyEvent(event.ApplyFailed, testObj1, fmt.Errorf("test error")).ApplyEvent, syncStats.ApplyEvent, objStatusMap, unknownTypeResources, resourceMap, resources) + err = s.processApplyEvent(ctx, formApplyEvent(event.ApplyFailed, testObj1, fmt.Errorf("test error")).ApplyEvent, syncStats.ApplyEvent, objStatusMap, unknownTypeResources, resourceMap) expectedError = ErrorForResourceWithResource(fmt.Errorf("test error"), testObj1ID, testObj1) testutil.AssertEqual(t, expectedError, err, "expected processApplyEvent to error on apply %s", event.ApplyFailed) @@ -1053,8 +758,6 @@ For more information, see https://g.co/cloud/acm-errors#knv2009`, }}, } testutil.AssertEqual(t, expectedCSE, err.ToCSE(), "expected CSEs to match") - // The object should be removed from the IgnoredObjects cache when ApplyFailed - testutil.AssertEqual(t, 0, len(resources.IgnoredObjects())) // TODO: test handleMetrics on success // TODO: test unknownTypeResources on UnknownTypeError @@ -1078,14 +781,11 @@ func TestProcessPruneEvent(t *testing.T) { testObj2 := testObj.DeepCopy() core.SetAnnotation(testObj2, metadata.LifecycleMutationAnnotation, metadata.IgnoreMutation) - resources := &declared.Resources{} - resources.UpdateIgnored(testObj2) - - err := s.processPruneEvent(ctx, formPruneEvent(event.PruneFailed, deploymentObj, fmt.Errorf("test error")).PruneEvent, syncStats.PruneEvent, objStatusMap, resources) + err := s.processPruneEvent(ctx, formPruneEvent(event.PruneFailed, deploymentObj, fmt.Errorf("test error")).PruneEvent, syncStats.PruneEvent, objStatusMap) expectedError := PruneErrorForResource(fmt.Errorf("test error"), idFrom(deploymentID)) testerrors.AssertEqual(t, expectedError, err, "expected processPruneEvent to error on prune %s", event.PruneFailed) - err = s.processPruneEvent(ctx, formPruneEvent(event.PruneSuccessful, testObj, nil).PruneEvent, syncStats.PruneEvent, objStatusMap, resources) + err = s.processPruneEvent(ctx, formPruneEvent(event.PruneSuccessful, testObj, nil).PruneEvent, syncStats.PruneEvent, objStatusMap) assert.Nil(t, err, "expected processPruneEvent NOT to error on prune %s", event.PruneSuccessful) expectedApplyStatus := stats.NewSyncStats() @@ -1105,8 +805,6 @@ func TestProcessPruneEvent(t *testing.T) { } testutil.AssertEqual(t, expectedObjStatusMap, objStatusMap, "expected object status to match") - testutil.AssertEqual(t, 0, len(resources.IgnoredObjects())) - // TODO: test handleMetrics on success // TODO: test PruneErrorForResource on failed // TODO: test SpecialNamespaces on skip @@ -1225,10 +923,3 @@ func newTestObj(name string) *unstructured.Unstructured { Kind: "Test", }, core.Namespace("test-namespace"), core.Name(name), core.Annotation(metadata.SourcePathAnnotationKey, "foo/test.yaml")) } - -func asUnstructuredSanitizedObj(o client.Object) *unstructured.Unstructured { - core.Scheme.Default(o) - uObj, _ := reconcile.AsUnstructuredSanitized(o) - - return uObj -} diff --git a/pkg/applier/utils.go b/pkg/applier/utils.go index 80b6cc805b..45c07c678d 100644 --- a/pkg/applier/utils.go +++ b/pkg/applier/utils.go @@ -22,10 +22,8 @@ import ( "github.com/GoogleContainerTools/config-sync/pkg/api/kpt.dev/v1alpha1" "github.com/GoogleContainerTools/config-sync/pkg/core" - "github.com/GoogleContainerTools/config-sync/pkg/declared" "github.com/GoogleContainerTools/config-sync/pkg/kinds" "github.com/GoogleContainerTools/config-sync/pkg/metadata" - "github.com/GoogleContainerTools/config-sync/pkg/remediator/queue" "github.com/GoogleContainerTools/config-sync/pkg/status" syncerreconcile "github.com/GoogleContainerTools/config-sync/pkg/syncer/reconcile" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -49,27 +47,6 @@ func partitionObjs(objs []client.Object) ([]client.Object, []client.Object) { return enabled, disabled } -// handleIgnoredObjects gets the cached cluster state of all mutation-ignored objects that are declared and applies the CS metadata on top of them -// prior to sending them to the applier -// Returns all objects that will be applied -func handleIgnoredObjects(enabled []client.Object, resources *declared.Resources) []client.Object { - var allObjs []client.Object - - for _, dObj := range enabled { - cachedObj, found := resources.GetIgnored(core.IDOf(dObj)) - _, deleted := cachedObj.(*queue.Deleted) - - if found && !deleted { - metadata.UpdateConfigSyncMetadata(dObj, cachedObj) - allObjs = append(allObjs, cachedObj) - } else { - allObjs = append(allObjs, dObj) - } - } - - return allObjs -} - func toUnstructured(objs []client.Object) ([]*unstructured.Unstructured, status.MultiError) { var errs status.MultiError var unstructureds []*unstructured.Unstructured diff --git a/pkg/applier/utils_test.go b/pkg/applier/utils_test.go index 1886806c70..ababc70a09 100644 --- a/pkg/applier/utils_test.go +++ b/pkg/applier/utils_test.go @@ -19,15 +19,11 @@ import ( "github.com/GoogleContainerTools/config-sync/pkg/core" "github.com/GoogleContainerTools/config-sync/pkg/core/k8sobjects" - "github.com/GoogleContainerTools/config-sync/pkg/declared" - "github.com/GoogleContainerTools/config-sync/pkg/kinds" - "github.com/GoogleContainerTools/config-sync/pkg/remediator/queue" "github.com/GoogleContainerTools/config-sync/pkg/syncer/syncertest" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/cli-utils/pkg/object" - "sigs.k8s.io/cli-utils/pkg/testutil" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -185,128 +181,6 @@ func TestGetObjectSize(t *testing.T) { } } -func TestHandleIgnoredObjects(t *testing.T) { - testcases := []struct { - name string - declaredObjs []client.Object - ignoredObjs []client.Object - expectedObjs []client.Object - }{ - { - name: "all objects have the ignore mutation annotation and there's nothing in the cache", - declaredObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", syncertest.IgnoreMutationAnnotation), - }, - ignoredObjs: []client.Object{}, - expectedObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", syncertest.IgnoreMutationAnnotation), - }, - }, - { - name: "an existing but unmanaged object is declared with the ignore mutation annotation", - declaredObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", - syncertest.IgnoreMutationAnnotation, - syncertest.ManagementEnabled), - }, - ignoredObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - core.Annotation("foo", "bar")), - }, - expectedObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - core.Annotation("foo", "bar"), - syncertest.IgnoreMutationAnnotation, - syncertest.ManagementEnabled), - }, - }, - { - name: "a managed object is now declared with the ignore mutation annotation", - declaredObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation), - }, - ignoredObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - syncertest.ManagementEnabled)}, - expectedObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation), - }, - }, - { - name: "a managed object is now declared with the ignore mutation annotation and other spec changes", - declaredObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation, - core.Annotation("foo", "bar")), - }, - ignoredObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - syncertest.ManagementEnabled), - }, - expectedObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation), - }, - }, - { - name: "a mutation-ignored managed object that was previously deleted", - declaredObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation), - }, - ignoredObjs: []client.Object{ - &queue.Deleted{ - Object: k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation, - core.Annotation("foo", "bar"), - ), - }}, - expectedObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation), - }, - }, - { - name: "an object exists with the ignore mutation annotation but it is declared without it", - declaredObjs: []client.Object{ - k8sobjects.NamespaceObject("test-ns", - syncertest.ManagementEnabled), - }, - ignoredObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation, - core.Annotation("foo", "bar"), - ), - }, - expectedObjs: []client.Object{ - k8sobjects.UnstructuredObject(kinds.Namespace(), core.Name("test-ns"), - syncertest.ManagementEnabled, - syncertest.IgnoreMutationAnnotation, - core.Annotation("foo", "bar")), - }, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - resources := &declared.Resources{} - resources.UpdateIgnored(tc.ignoredObjs...) - allObjs := handleIgnoredObjects(tc.declaredObjs, resources) - testutil.AssertEqual(t, tc.expectedObjs, allObjs) - }) - } -} - // mustObjMetaFromObject constructs an ObjMetadata representing the Object. // // Fails the test if the GroupKind is not set and not registered in core.Scheme. diff --git a/pkg/declared/resources.go b/pkg/declared/resources.go index 43b1f92070..e7e9c8be70 100644 --- a/pkg/declared/resources.go +++ b/pkg/declared/resources.go @@ -21,7 +21,6 @@ import ( "github.com/GoogleContainerTools/config-sync/pkg/core" "github.com/GoogleContainerTools/config-sync/pkg/kinds" "github.com/GoogleContainerTools/config-sync/pkg/metrics" - "github.com/GoogleContainerTools/config-sync/pkg/remediator/queue" "github.com/GoogleContainerTools/config-sync/pkg/status" "github.com/GoogleContainerTools/config-sync/pkg/syncer/reconcile" "github.com/GoogleContainerTools/config-sync/pkg/util/clusterconfig" @@ -45,79 +44,12 @@ type Resources struct { // this reference; it should be treated as read-only from then on. declaredObjectsMap *orderedmap.OrderedMap[core.ID, *unstructured.Unstructured] - // mutationIgnoredObjectsMap is a map of object IDs to the cluster-state of mutation-ignored objects. - // The cluster-state is initialized by the applier and updated by the remediator. - mutationIgnoredObjectsMap *orderedmap.OrderedMap[core.ID, client.Object] - // commit of the source in which the resources were declared commit string // previousCommit is the preceding commit to the commit previousCommit string } -// UpdateIgnored performs an atomic update on the resource ignore mutation set. -func (r *Resources) UpdateIgnored(objs ...client.Object) { - r.mutex.Lock() - defer r.mutex.Unlock() - if r.mutationIgnoredObjectsMap == nil { - r.mutationIgnoredObjectsMap = orderedmap.NewOrderedMap[core.ID, client.Object]() - } - - for _, o := range objs { - if _, wasDeleted := o.(*queue.Deleted); wasDeleted { - r.mutationIgnoredObjectsMap.Set(core.IDOf(o), o) - } else { - u, _ := reconcile.AsUnstructuredSanitized(o) - r.mutationIgnoredObjectsMap.Set(core.IDOf(u), u) - } - } - -} - -// GetIgnored returns a copy of a declared object that has the ignore mutation annotation -func (r *Resources) GetIgnored(id core.ID) (client.Object, bool) { - r.mutex.RLock() - defer r.mutex.RUnlock() - if r.mutationIgnoredObjectsMap == nil || r.mutationIgnoredObjectsMap.Len() == 0 { - return nil, false - } - - o, found := r.mutationIgnoredObjectsMap.Get(id) - - if found { - oCopy := o.DeepCopyObject().(client.Object) - return oCopy, found - } - - return o, found -} - -// IgnoredObjects returns a slice with a copy of all ignore-mutation objects in the ignoredObjsMap -func (r *Resources) IgnoredObjects() []client.Object { - r.mutex.RLock() - defer r.mutex.RUnlock() - if r.mutationIgnoredObjectsMap == nil || r.mutationIgnoredObjectsMap.Len() == 0 { - return nil - } - - var objects []client.Object - for pair := r.mutationIgnoredObjectsMap.Front(); pair != nil; pair = pair.Next() { - objects = append(objects, pair.Value.DeepCopyObject().(client.Object)) - } - return objects -} - -// DeleteIgnored deletes an ignore-mutation object from the ignored cache -func (r *Resources) DeleteIgnored(id core.ID) bool { - r.mutex.Lock() - defer r.mutex.Unlock() - if r.mutationIgnoredObjectsMap == nil || r.mutationIgnoredObjectsMap.Len() == 0 { - return false - } - - return r.mutationIgnoredObjectsMap.Delete(id) -} - // UpdateDeclared performs an atomic update on the resource declaration set. func (r *Resources) UpdateDeclared(ctx context.Context, objects []client.Object, commit string) ([]client.Object, status.Error) { r.mutex.Lock() diff --git a/pkg/declared/resources_test.go b/pkg/declared/resources_test.go index bcb14624d2..323f1b8aaf 100644 --- a/pkg/declared/resources_test.go +++ b/pkg/declared/resources_test.go @@ -21,28 +21,21 @@ import ( "github.com/GoogleContainerTools/config-sync/pkg/core" "github.com/GoogleContainerTools/config-sync/pkg/core/k8sobjects" - "github.com/GoogleContainerTools/config-sync/pkg/kinds" - "github.com/GoogleContainerTools/config-sync/pkg/metadata" "github.com/GoogleContainerTools/config-sync/pkg/metrics" "github.com/GoogleContainerTools/config-sync/pkg/syncer/reconcile" - "github.com/GoogleContainerTools/config-sync/pkg/syncer/syncertest" "github.com/GoogleContainerTools/config-sync/pkg/testing/testmetrics" "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opencensus.io/stats/view" "go.opencensus.io/tag" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/cli-utils/pkg/testutil" "sigs.k8s.io/controller-runtime/pkg/client" ) var ( - obj1 = k8sobjects.CustomResourceDefinitionV1Beta1Object() - obj2 = k8sobjects.ResourceQuotaObject() - ignoredObj = createIgnoredObj() + obj1 = k8sobjects.CustomResourceDefinitionV1Beta1Object() + obj2 = k8sobjects.ResourceQuotaObject() testSet = []client.Object{obj1, obj2} nilSet = []client.Object{nil} @@ -236,79 +229,3 @@ func getIDs(objects []client.Object) []core.ID { } return IDs } - -func TestGetIgnored(t *testing.T) { - id := core.IDOf(ignoredObj) - dr := Resources{} - - o, found := dr.GetIgnored(id) - assert.Nil(t, o) - assert.False(t, found) - - dr.UpdateIgnored(ignoredObj) - o, found = dr.GetIgnored(id) - - expectedO := o.DeepCopyObject().(client.Object) - expectedO, _ = reconcile.AsUnstructuredSanitized(expectedO) - - assert.True(t, found) - testutil.AssertEqual(t, expectedO, o) -} - -func TestUpdateIgnored(t *testing.T) { - dr := Resources{} - id := core.IDOf(ignoredObj) - - dr.UpdateIgnored(ignoredObj) - o, found := dr.GetIgnored(id) - assert.True(t, found) - - o.SetName("new-name") - assert.NotEqual(t, "new-name", obj1.Name) -} - -func TestIgnoredObjects(t *testing.T) { - dr := Resources{} - - ignoredObjs := dr.IgnoredObjects() - assert.Nil(t, ignoredObjs) - - dr.UpdateIgnored(ignoredObj) - ignoredObjs = dr.IgnoredObjects() - - cachedIgnoredObj := asUnstructured(t, ignoredObj.DeepCopy()) - assert.Contains(t, ignoredObjs, cachedIgnoredObj) - - foundObj := ignoredObjs[0] - foundObj.SetName("foo") - foundObj = asUnstructured(t, foundObj) - - ignoredObjs = dr.IgnoredObjects() - - assert.NotContains(t, ignoredObjs, foundObj, "foundObj shouldn't have been modified in mutationIgnoredObjectsMap") -} - -func TestDeleteIgnored(t *testing.T) { - id := core.IDOf(ignoredObj) - dr := Resources{} - deleted := dr.DeleteIgnored(id) - ignored := dr.IgnoredObjects() - - assert.False(t, deleted) - assert.NotContains(t, ignored, ignoredObj) - - dr.UpdateIgnored(ignoredObj) - deleted = dr.DeleteIgnored(id) - assert.True(t, deleted) - assert.NotContains(t, ignored, ignoredObj) -} - -func createIgnoredObj() *unstructured.Unstructured { - o := k8sobjects.NamespaceObject("test-ns", syncertest.IgnoreMutationAnnotation) //&corev1.Namespace{TypeMeta: k8sobjects.ToTypeMeta(kinds.Namespace())} - o.SetManagedFields([]metav1.ManagedFieldsEntry{{Manager: "foo"}}) - core.SetAnnotation(o, metadata.LifecycleMutationAnnotation, metadata.IgnoreMutation) - - u, _ := kinds.ToUnstructured(o, core.Scheme) - return u - -} diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go index 11185b35ae..c1c725b96e 100644 --- a/pkg/metadata/metadata.go +++ b/pkg/metadata/metadata.go @@ -173,11 +173,12 @@ func RemoveConfigSyncMetadata(obj client.Object) bool { // UpdateConfigSyncMetadata applies the Config Sync metadata of fromObj // to toObj where toObj is modified in place. -func UpdateConfigSyncMetadata(fromObj client.Object, toObj client.Object) { +func UpdateConfigSyncMetadata(fromObj client.Object, toObj client.Object) bool { csAnnotations, csLabels := getConfigSyncMetadata(fromObj) - core.AddAnnotations(toObj, csAnnotations) - core.AddLabels(toObj, csLabels) + update1 := core.AddAnnotations(toObj, csAnnotations) + update2 := core.AddLabels(toObj, csLabels) + return update1 || update2 } // HasSameCSMetadata returns true if the given objects have the same Config Sync metadata. diff --git a/pkg/metadata/metadata_test.go b/pkg/metadata/metadata_test.go index eea94fd89e..c15575fe50 100644 --- a/pkg/metadata/metadata_test.go +++ b/pkg/metadata/metadata_test.go @@ -231,6 +231,7 @@ func TestUpdateConfigSyncMetadata(t *testing.T) { fromObj client.Object toObj client.Object expectedObj client.Object + updated bool }{ { name: "fromObj + toObj don't have CS metadata", @@ -243,18 +244,21 @@ func TestUpdateConfigSyncMetadata(t *testing.T) { fromObj: k8sobjects.NamespaceObject("test-ns", syncertest.IgnoreMutationAnnotation, syncertest.ManagementEnabled), toObj: k8sobjects.NamespaceObject("test-ns"), expectedObj: k8sobjects.NamespaceObject("test-ns", syncertest.IgnoreMutationAnnotation, syncertest.ManagementEnabled), + updated: true, }, { name: "fromObj doesn't have the ignore mutation annotation but toObj does", fromObj: k8sobjects.NamespaceObject("test-ns", syncertest.ManagementEnabled), toObj: k8sobjects.NamespaceObject("test-ns", syncertest.IgnoreMutationAnnotation), expectedObj: k8sobjects.NamespaceObject("test-ns", syncertest.ManagementEnabled, syncertest.IgnoreMutationAnnotation), + updated: true, }, { name: "fromObj doesn't have a non-CS annotation but toObj does", fromObj: k8sobjects.NamespaceObject("test-ns", syncertest.ManagementEnabled), toObj: k8sobjects.NamespaceObject("test-ns", syncertest.IgnoreMutationAnnotation, core.Annotation("foo", "bar")), expectedObj: k8sobjects.NamespaceObject("test-ns", syncertest.ManagementEnabled, syncertest.IgnoreMutationAnnotation, core.Annotation("foo", "bar")), + updated: true, }, } @@ -262,8 +266,9 @@ func TestUpdateConfigSyncMetadata(t *testing.T) { t.Run(tc.name, func(t *testing.T) { toObj, err := kinds.ObjectAsClientObject(tc.toObj.DeepCopyObject()) require.NoError(t, err) - metadata.UpdateConfigSyncMetadata(tc.fromObj, toObj) + updated := metadata.UpdateConfigSyncMetadata(tc.fromObj, toObj) testutil.AssertEqual(t, tc.expectedObj, toObj) + require.Equal(t, tc.updated, updated) }) } } diff --git a/pkg/remediator/watch/filteredwatcher.go b/pkg/remediator/watch/filteredwatcher.go index faa3572407..ee8aaf21cd 100644 --- a/pkg/remediator/watch/filteredwatcher.go +++ b/pkg/remediator/watch/filteredwatcher.go @@ -445,11 +445,6 @@ func (w *filteredWatcher) handle(ctx context.Context, event watch.Event) (string core.IDOf(object), object.GetGeneration()) } - // Update drifted objects in the Resources ignored cache - if _, found := w.resources.GetIgnored(core.IDOf(object)); found { - w.resources.UpdateIgnored(object) - klog.V(3).Infof("Updating object '%v' in the ignore mutation cache", core.GKNN(object)) - } w.queue.Add(object) return object.GetResourceVersion(), false, nil } diff --git a/pkg/remediator/watch/filteredwatcher_test.go b/pkg/remediator/watch/filteredwatcher_test.go index 6e499a13a3..e36662d85f 100644 --- a/pkg/remediator/watch/filteredwatcher_test.go +++ b/pkg/remediator/watch/filteredwatcher_test.go @@ -32,7 +32,6 @@ import ( "github.com/GoogleContainerTools/config-sync/pkg/syncer/syncertest" testfake "github.com/GoogleContainerTools/config-sync/pkg/syncer/syncertest/fake" "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -388,10 +387,6 @@ func TestFilteredWatcher(t *testing.T) { t.Fatalf("unexpected error %v", err) } - if tc.ignored != nil { - dr.UpdateIgnored(tc.ignored...) - } - watches := make(chan watch.Interface) // TODO: test startWatch errors q := queue.New("test") cfg := watcherConfig{ @@ -444,8 +439,6 @@ func TestFilteredWatcher(t *testing.T) { if diff := cmp.Diff(tc.want, got); diff != "" { t.Errorf("did not get desired object IDs: %v", diff) } - - assert.Equal(t, tc.expectedCachedIgnored, dr.IgnoredObjects()) }) } } diff --git a/pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller.go b/pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller.go index 5605822c01..2dfee77ab7 100644 --- a/pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller.go +++ b/pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller.go @@ -278,11 +278,13 @@ func (r *reconciler) computeStatus( log := r.Logger(ctx).WithValues("inventory.object", res) cachedStatus := r.resMap.GetStatus(res) + ignoreMutation := false // Add status to cache, if not present. switch { case cachedStatus != nil: log.V(4).Info("Resource object status found in the cache") + ignoreMutation = cachedStatus.IgnoreMutation if cachedStatus.Status == v1alpha1.NotFound { resStatus.Status = v1alpha1.NotFound } else { @@ -326,6 +328,8 @@ func (r *reconciler) computeStatus( r.resMap.SetStatus(res, cachedStatus) // Update the new resource status. setResStatus(id, &resStatus, cachedStatus) + // set ignore mutation variable + ignoreMutation = cachedStatus.IgnoreMutation } if resStatus.Status == v1alpha1.Failed || controllerstatus.IsCNRMResource(resStatus.Group) && resStatus.Status != v1alpha1.Current { @@ -340,16 +344,21 @@ func (r *reconciler) computeStatus( // Update the reconcile status based on the Strategy & Actuation // from the last apply attempt, and the newly computed kstatus. - if reconcile, err := UpdateReconcileStatusToReflectKstatus(resStatus); err != nil { + if reconcile, err := UpdateReconcileStatusToReflectKstatus(resStatus, ignoreMutation); err != nil { // Keep existing Reconcile status log.Error(err, "Resource object status unknown: failed to compute") } else { resStatus.Reconcile = reconcile } - // Update the status field to reflect source -> spec -> status, - // not just spec -> status. - resStatus.Status = UpdateStatusToReflectActuation(resStatus) + if ignoreMutation { + // If it's a mutation-ignored object, the status field only reflects spec -> status. + log.V(4).Info("Skipping actuation status update. Status will only reflect reconciliation.") + } else { + // Update the status field to reflect source -> spec -> status, + // not just spec -> status. + resStatus.Status = UpdateStatusToReflectActuation(resStatus) + } } log.V(5).Info("Resource object status computed", "status", resStatus) @@ -368,7 +377,7 @@ func (r *reconciler) computeStatus( // UpdateReconcileStatusToReflectKstatus uses the current Strategy and Actuation // status from the applier and the newly computed kstatus to compute the // Reconcile status. Returns an error if one of the inputs is invalid. -func UpdateReconcileStatusToReflectKstatus(status v1alpha1.ResourceStatus) (v1alpha1.Reconcile, error) { +func UpdateReconcileStatusToReflectKstatus(status v1alpha1.ResourceStatus, ignoreMutation bool) (v1alpha1.Reconcile, error) { switch status.Strategy { case v1alpha1.Apply: switch status.Actuation { @@ -377,6 +386,12 @@ func UpdateReconcileStatusToReflectKstatus(status v1alpha1.ResourceStatus) (v1al case v1alpha1.ActuationPending: return v1alpha1.ReconcilePending, nil case v1alpha1.ActuationSkipped: + if ignoreMutation { + // If the object is allowed to drift, it's expected that actuation is skipped. + // Compute the reconcile status to reflect the on-cluster object, even + // if it may not necessarily reflect the object in the source. + return computeReconcileStatusForSuccessfulApply(status.Status, status.Reconcile) + } return v1alpha1.ReconcileSkipped, nil case v1alpha1.ActuationFailed: return v1alpha1.ReconcileSkipped, nil diff --git a/pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller_test.go b/pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller_test.go index 0287679679..bd1133574b 100644 --- a/pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller_test.go +++ b/pkg/resourcegroup/controllers/resourcegroup/resourcegroup_controller_test.go @@ -482,10 +482,11 @@ func TestReconcileTimeout(t *testing.T) { func TestUpdateReconcileStatusToReflectKstatus(t *testing.T) { // Define test cases using a table-driven approach testCases := []struct { - name string - status v1alpha1.ResourceStatus - expected v1alpha1.Reconcile - expectedError error + name string + status v1alpha1.ResourceStatus + ignoreMutation bool + expected v1alpha1.Reconcile + expectedError error }{ // Apply Strategy tests { @@ -569,6 +570,67 @@ func TestUpdateReconcileStatusToReflectKstatus(t *testing.T) { }, expected: v1alpha1.ReconcileSkipped, }, + { + name: "Apply_Skipped_IgnoreMutation_Current", + status: v1alpha1.ResourceStatus{ + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSkipped, + Status: v1alpha1.Current, + }, + ignoreMutation: true, + expected: v1alpha1.ReconcileSucceeded, + }, + { + name: "Apply_Skipped_IgnoreMutation_InProgress", + status: v1alpha1.ResourceStatus{ + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSkipped, + Status: v1alpha1.InProgress, + }, + ignoreMutation: true, + expected: v1alpha1.ReconcilePending, + }, + { + name: "Apply_Skipped_IgnoreMutation_Failed", + status: v1alpha1.ResourceStatus{ + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSkipped, + Status: v1alpha1.Failed, + }, + ignoreMutation: true, + expected: v1alpha1.ReconcileFailed, + }, + { + name: "Apply_Skipped_IgnoreMutation_Terminating", + status: v1alpha1.ResourceStatus{ + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSkipped, + Status: v1alpha1.Terminating, + }, + ignoreMutation: true, + expected: v1alpha1.ReconcileFailed, + }, + { + name: "Apply_Skipped_IgnoreMutation_NotFound", + status: v1alpha1.ResourceStatus{ + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSkipped, + Status: v1alpha1.NotFound, + }, + ignoreMutation: true, + expected: v1alpha1.ReconcileFailed, + }, + { + name: "Apply_Skipped_IgnoreMutation_Unknown", + status: v1alpha1.ResourceStatus{ + Strategy: v1alpha1.Apply, + Actuation: v1alpha1.ActuationSkipped, + Status: v1alpha1.Unknown, + Reconcile: v1alpha1.ReconcilePending, // Simulate previous reconcile status + }, + ignoreMutation: true, + expected: v1alpha1.ReconcilePending, + }, { name: "Apply_Failed", status: v1alpha1.ResourceStatus{ @@ -692,7 +754,7 @@ func TestUpdateReconcileStatusToReflectKstatus(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Call the function under test - actual, err := UpdateReconcileStatusToReflectKstatus(tc.status) + actual, err := UpdateReconcileStatusToReflectKstatus(tc.status, tc.ignoreMutation) assert.Equal(t, tc.expected, actual) testerrors.AssertEqual(t, tc.expectedError, err) }) diff --git a/pkg/resourcegroup/controllers/resourcemap/resourcemap.go b/pkg/resourcegroup/controllers/resourcemap/resourcemap.go index 923c1e3fe3..698213effb 100644 --- a/pkg/resourcegroup/controllers/resourcemap/resourcemap.go +++ b/pkg/resourcegroup/controllers/resourcemap/resourcemap.go @@ -118,10 +118,11 @@ func newresourceGroupSet(groups []types.NamespacedName) *resourceGroupSet { // CachedStatus stores the status and condition for one resource. type CachedStatus struct { - Status v1alpha1.Status - Conditions []v1alpha1.Condition - SourceHash string - InventoryID string + Status v1alpha1.Status + Conditions []v1alpha1.Condition + SourceHash string + InventoryID string + IgnoreMutation bool } // ResourceMap maintains the following maps: diff --git a/pkg/resourcegroup/controllers/status/status.go b/pkg/resourcegroup/controllers/status/status.go index 48d28b85d2..261806394d 100644 --- a/pkg/resourcegroup/controllers/status/status.go +++ b/pkg/resourcegroup/controllers/status/status.go @@ -71,6 +71,7 @@ func ComputeStatus(obj *unstructured.Unstructured) *resourcemap.CachedStatus { // get the inventory ID. inv := getOwningInventory(obj.GetAnnotations()) resStatus.InventoryID = inv + resStatus.IgnoreMutation = isIgnoreMutationObject(obj.GetAnnotations()) return resStatus } @@ -129,3 +130,10 @@ func getOwningInventory(annotations map[string]string) string { } return annotations[metadata.OwningInventoryKey] } + +func isIgnoreMutationObject(annotations map[string]string) bool { + if len(annotations) == 0 { + return false + } + return annotations[metadata.LifecycleMutationAnnotation] == metadata.IgnoreMutation +} diff --git a/vendor/k8s.io/utils/net/multi_listen.go b/vendor/k8s.io/utils/net/multi_listen.go index 7cb7795bec..e5d508055d 100644 --- a/vendor/k8s.io/utils/net/multi_listen.go +++ b/vendor/k8s.io/utils/net/multi_listen.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "sync" + "sync/atomic" ) // connErrPair pairs conn and error which is returned by accept on sub-listeners. @@ -38,6 +39,7 @@ type multiListener struct { connCh chan connErrPair // stopCh communicates from parent to child listeners. stopCh chan struct{} + closed atomic.Bool } // compile time check to ensure *multiListener implements net.Listener @@ -150,10 +152,8 @@ func (ml *multiListener) Accept() (net.Conn, error) { // the go-routines to exit. func (ml *multiListener) Close() error { // Make sure this can be called repeatedly without explosions. - select { - case <-ml.stopCh: + if !ml.closed.CompareAndSwap(false, true) { return fmt.Errorf("use of closed network connection") - default: } // Tell all sub-listeners to stop. diff --git a/vendor/modules.txt b/vendor/modules.txt index 0e563f2eaa..43f6b8540a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -407,7 +407,7 @@ github.com/munnerz/goautoneg # github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f ## explicit github.com/mxk/go-flowrate/flowrate -# github.com/onsi/gomega v1.38.0 +# github.com/onsi/gomega v1.38.2 ## explicit; go 1.23.0 github.com/onsi/gomega/format # github.com/open-policy-agent/cert-controller v0.14.0 @@ -1534,7 +1534,7 @@ k8s.io/kubernetes/pkg/apis/rbac/v1 k8s.io/kubernetes/pkg/apis/rbac/v1beta1 k8s.io/kubernetes/pkg/features k8s.io/kubernetes/pkg/util/parsers -# k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +# k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d ## explicit; go 1.18 k8s.io/utils/buffer k8s.io/utils/clock @@ -1546,8 +1546,8 @@ k8s.io/utils/lru k8s.io/utils/net k8s.io/utils/ptr k8s.io/utils/trace -# sigs.k8s.io/cli-utils v0.37.3-0.20250410211241-63a8e151c476 -## explicit; go 1.23.0 +# sigs.k8s.io/cli-utils v0.37.3-0.20251021150641-5895ad6c17dd +## explicit; go 1.24.0 sigs.k8s.io/cli-utils/pkg/apis/actuation sigs.k8s.io/cli-utils/pkg/apply sigs.k8s.io/cli-utils/pkg/apply/cache diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/applier.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/applier.go index c78a601244..7fb84a4156 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/apply/applier.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/applier.go @@ -14,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" + "k8s.io/client-go/metadata" "k8s.io/klog/v2" "sigs.k8s.io/cli-utils/pkg/apis/actuation" "sigs.k8s.io/cli-utils/pkg/apply/cache" @@ -42,13 +43,14 @@ import ( // parameters and/or the set of resources that needs to be applied to the // cluster, different sets of tasks might be needed. type Applier struct { - pruner *prune.Pruner - statusWatcher watcher.StatusWatcher - invClient inventory.Client - client dynamic.Interface - openAPIGetter discovery.OpenAPISchemaInterface - mapper meta.RESTMapper - infoHelper info.Helper + pruner *prune.Pruner + statusWatcher watcher.StatusWatcher + invClient inventory.Client + client dynamic.Interface + metadataClient metadata.Interface + openAPIGetter discovery.OpenAPISchemaInterface + mapper meta.RESTMapper + infoHelper info.Helper } // prepareObjects returns the set of objects to apply and to prune or @@ -128,11 +130,16 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.Info, objects objec // Build list of apply validation filters. applyFilters := []filter.ValidationFilter{ filter.InventoryPolicyApplyFilter{ - Client: a.client, + Client: a.metadataClient, Mapper: a.mapper, Inv: invInfo, InvPolicy: options.InventoryPolicy, }, + // consider consolidating these two filters to minimize repeated Get calls + filter.PreventUpdateFilter{ + Client: a.metadataClient, + Mapper: a.mapper, + }, filter.DependencyFilter{ TaskContext: taskContext, ActuationStrategy: actuation.ActuationStrategyApply, @@ -311,8 +318,8 @@ func handleError(eventChannel chan event.Event, err error) { // for the passed non cluster-scoped localObjs, plus the namespace // of the passed inventory object. This is used to skip deleting // namespaces which have currently applied objects in them. -func localNamespaces(localInv inventory.Info, localObjs []object.ObjMetadata) sets.String { // nolint:staticcheck - namespaces := sets.NewString() +func localNamespaces(localInv inventory.Info, localObjs []object.ObjMetadata) sets.Set[string] { // nolint:staticcheck + namespaces := sets.New[string]() for _, obj := range localObjs { if obj.Namespace != "" { namespaces.Insert(obj.Namespace) diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/applier_builder.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/applier_builder.go index e964d110f8..0c4634d421 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/apply/applier_builder.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/applier_builder.go @@ -8,6 +8,7 @@ import ( "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" + "k8s.io/client-go/metadata" "k8s.io/client-go/rest" "k8s.io/kubectl/pkg/cmd/util" "sigs.k8s.io/cli-utils/pkg/apply/info" @@ -38,12 +39,13 @@ func (b *ApplierBuilder) Build() (*Applier, error) { Client: bx.client, Mapper: bx.mapper, }, - statusWatcher: bx.statusWatcher, - invClient: bx.invClient, - client: bx.client, - openAPIGetter: bx.discoClient, - mapper: bx.mapper, - infoHelper: info.NewHelper(bx.mapper, bx.unstructuredClientForMapping), + statusWatcher: bx.statusWatcher, + invClient: bx.invClient, + client: bx.client, + metadataClient: bx.metadataClient, + openAPIGetter: bx.discoClient, + mapper: bx.mapper, + infoHelper: info.NewHelper(bx.mapper, bx.unstructuredClientForMapping), }, nil } @@ -62,6 +64,11 @@ func (b *ApplierBuilder) WithDynamicClient(client dynamic.Interface) *ApplierBui return b } +func (b *ApplierBuilder) WithMetadataClient(client metadata.Interface) *ApplierBuilder { + b.metadataClient = client + return b +} + func (b *ApplierBuilder) WithDiscoveryClient(discoClient discovery.CachedDiscoveryInterface) *ApplierBuilder { b.discoClient = discoClient return b diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/builder.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/builder.go index 75602e74f0..83f8c451ac 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/apply/builder.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/builder.go @@ -11,6 +11,7 @@ import ( "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" + "k8s.io/client-go/metadata" "k8s.io/client-go/rest" "k8s.io/kubectl/pkg/cmd/util" "sigs.k8s.io/cli-utils/pkg/inventory" @@ -21,6 +22,7 @@ type commonBuilder struct { // factory is only used to retrieve things that have not been provided explicitly. factory util.Factory invClient inventory.Client + metadataClient metadata.Interface client dynamic.Interface discoClient discovery.CachedDiscoveryInterface mapper meta.RESTMapper @@ -72,6 +74,12 @@ func (cb *commonBuilder) finalize() (*commonBuilder, error) { return nil, fmt.Errorf("error getting rest config: %v", err) } } + if cx.metadataClient == nil { + cx.metadataClient, err = metadata.NewForConfig(cx.restConfig) + if err != nil { + return nil, fmt.Errorf("error getting metadata client: %v", err) + } + } if cx.unstructuredClientForMapping == nil { if cx.factory == nil { return nil, fmt.Errorf("a factory must be provided or all other options: %v", err) diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/current-uids-filter.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/current-uids-filter.go index edc1bbd24e..045d8f12c5 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/current-uids-filter.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/current-uids-filter.go @@ -16,7 +16,7 @@ import ( // if an object should not be pruned (deleted) because it has recently // been applied. type CurrentUIDFilter struct { - CurrentUIDs sets.String // nolint:staticcheck + CurrentUIDs sets.Set[types.UID] // nolint:staticcheck } // Name returns a filter identifier for logging. @@ -28,7 +28,7 @@ func (cuf CurrentUIDFilter) Name() string { // should be skipped. func (cuf CurrentUIDFilter) Filter(_ context.Context, obj *unstructured.Unstructured) error { uid := obj.GetUID() - if cuf.CurrentUIDs.Has(string(uid)) { + if cuf.CurrentUIDs.Has(uid) { return &ApplyPreventedDeletionError{UID: uid} } return nil diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/inventory-policy-apply-filter.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/inventory-policy-apply-filter.go index b7cef7029b..ffb029b482 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/inventory-policy-apply-filter.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/inventory-policy-apply-filter.go @@ -11,7 +11,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/dynamic" + "k8s.io/client-go/metadata" "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/object" ) @@ -20,7 +20,7 @@ import ( // if an object should be applied based on the cluster object's inventory id, // the id for the inventory object, and the inventory policy. type InventoryPolicyApplyFilter struct { - Client dynamic.Interface + Client metadata.Interface Mapper meta.RESTMapper Inv inventory.Info InvPolicy inventory.Policy @@ -55,7 +55,7 @@ func (ipaf InventoryPolicyApplyFilter) Filter(ctx context.Context, obj *unstruct } // getObject retrieves the passed object from the cluster, or an error if one occurred. -func (ipaf InventoryPolicyApplyFilter) getObject(ctx context.Context, id object.ObjMetadata) (*unstructured.Unstructured, error) { +func (ipaf InventoryPolicyApplyFilter) getObject(ctx context.Context, id object.ObjMetadata) (*metav1.PartialObjectMetadata, error) { mapping, err := ipaf.Mapper.RESTMapping(id.GroupKind) if err != nil { return nil, err diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/local-namespaces-filter.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/local-namespaces-filter.go index ccfedd3b83..2b2629683c 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/local-namespaces-filter.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/local-namespaces-filter.go @@ -21,7 +21,7 @@ var ( // that are currently in use. Used to ensure we do not delete // namespaces with currently applied objects in them. type LocalNamespacesFilter struct { - LocalNamespaces sets.String // nolint:staticcheck + LocalNamespaces sets.Set[string] // nolint:staticcheck } // Name returns a filter identifier for logging. diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/prevent-update-filter.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/prevent-update-filter.go new file mode 100644 index 0000000000..9a4d88eed0 --- /dev/null +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/filter/prevent-update-filter.go @@ -0,0 +1,83 @@ +// Copyright 2025 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package filter + +import ( + "context" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/metadata" + "sigs.k8s.io/cli-utils/pkg/common" + "sigs.k8s.io/cli-utils/pkg/object" +) + +// PreventUpdateFilter implements ValidationFilter interface to determine +// if an object should not be updated because of an "ignore mutation" annotation. +type PreventUpdateFilter struct { + Client metadata.Interface + Mapper meta.RESTMapper +} + +const PreventUpdateFilterName = "PreventUpdateFilter" + +// Name returns the preferred name for the filter. Usually +// used for logging. +func (puf PreventUpdateFilter) Name() string { + return PreventUpdateFilterName +} + +// Filter returns a AnnotationPreventedUpdateError if the object apply +// should be skipped. +func (puf PreventUpdateFilter) Filter(ctx context.Context, obj *unstructured.Unstructured) error { + a := obj.GetAnnotations() + if val, ok := a[common.LifecycleMutationAnnotation]; ok && val == common.IgnoreMutation { + _, err := puf.getObject(ctx, object.UnstructuredToObjMetadata(obj)) + if apierrors.IsNotFound(err) { // object NotFound - apply + return nil + } else if err != nil { // unexpected error - fatal + return NewFatalError(fmt.Errorf("failed to get current object from cluster: %w", err)) + } + // Object exists - skip apply + return &AnnotationPreventedUpdateError{ + Annotation: common.LifecycleMutationAnnotation, + Value: common.IgnoreMutation, + } + } + return nil +} + +// getObject retrieves the passed object from the cluster, or an error if one occurred. +func (puf PreventUpdateFilter) getObject(ctx context.Context, id object.ObjMetadata) (*metav1.PartialObjectMetadata, error) { + mapping, err := puf.Mapper.RESTMapping(id.GroupKind) + if err != nil { + return nil, err + } + namespacedClient := puf.Client.Resource(mapping.Resource).Namespace(id.Namespace) + return namespacedClient.Get(ctx, id.Name, metav1.GetOptions{}) +} + +type AnnotationPreventedUpdateError struct { + Annotation string + Value string +} + +func (e *AnnotationPreventedUpdateError) Error() string { + return fmt.Sprintf("annotation prevents apply (%q: %q)", e.Annotation, e.Value) +} + +func (e *AnnotationPreventedUpdateError) Is(err error) bool { + if err == nil { + return false + } + tErr, ok := err.(*AnnotationPreventedUpdateError) + if !ok { + return false + } + return e.Annotation == tErr.Annotation && + e.Value == tErr.Value +} diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/mutator/apply_time_mutator.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/mutator/apply_time_mutator.go index 2481517627..30e59d6b93 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/apply/mutator/apply_time_mutator.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/mutator/apply_time_mutator.go @@ -112,7 +112,7 @@ func (atm *ApplyTimeMutator) Mutate(ctx context.Context, obj *unstructured.Unstr return mutated, reason, fmt.Errorf("source field (%s) not present in source object (%s)", sub.SourcePath, sourceRef) } - var newValue interface{} + var newValue any if sub.Token == "" { // token not specified, replace the entire target value with the source value newValue = sourceValue @@ -244,7 +244,7 @@ func computeStatus(obj *unstructured.Unstructured) cache.ResourceStatus { } } -func readFieldValue(obj *unstructured.Unstructured, path string) (interface{}, bool, error) { +func readFieldValue(obj *unstructured.Unstructured, path string) (any, bool, error) { if path == "" { return nil, false, errors.New("empty path expression") } @@ -259,7 +259,7 @@ func readFieldValue(obj *unstructured.Unstructured, path string) (interface{}, b return values[0], true, nil } -func writeFieldValue(obj *unstructured.Unstructured, path string, value interface{}) error { +func writeFieldValue(obj *unstructured.Unstructured, path string, value any) error { if path == "" { return errors.New("empty path expression") } @@ -276,7 +276,7 @@ func writeFieldValue(obj *unstructured.Unstructured, path string, value interfac // valueToString converts an interface{} to a string, formatting as json for // maps, lists. Designed to handle yaml/json/krm primitives. -func valueToString(value interface{}) (string, error) { +func valueToString(value any) (string, error) { var valueString string switch valueTyped := value.(type) { case string: diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/apply/prune/prune.go b/vendor/sigs.k8s.io/cli-utils/pkg/apply/prune/prune.go index 45af0ae332..38a907a1dd 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/apply/prune/prune.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/apply/prune/prune.go @@ -103,7 +103,7 @@ func (p *Pruner) Prune( // UID will change if the object is deleted and re-created. uid := obj.GetUID() if uid == "" { - err := object.NotFound([]interface{}{"metadata", "uid"}, "") + err := object.NotFound([]any{"metadata", "uid"}, "") if klog.V(4).Enabled() { // only log event emitted errors if the verbosity > 4 klog.Errorf("prune uid lookup errored (object: %s): %v", id, err) diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/common/common.go b/vendor/sigs.k8s.io/cli-utils/pkg/common/common.go index 9df43bbb37..9fe105c763 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/common/common.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/common/common.go @@ -41,6 +41,14 @@ const ( // PreventDeletion is the value used with LifecycleDeletionAnnotation // to prevent deleting a resource. PreventDeletion = "detach" + + // LifecycleMutationAnnotation is the lifecycle annotation key for mutation operation. + LifecycleMutationAnnotation = "client.lifecycle.config.k8s.io/mutation" + + // IgnoreMutation is the value used with LifecycleMutationAnnotation to + // prevent mutating a resource. That is, if the resource exists on the cluster + // then the applier will make no attempt to modify it. + IgnoreMutation = "ignore" ) // RandomStr returns an eight-digit (with leading zeros) string of a diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/inventory/manager.go b/vendor/sigs.k8s.io/cli-utils/pkg/inventory/manager.go index 9c75aa73db..fd7f0b3fb3 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/inventory/manager.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/inventory/manager.go @@ -117,13 +117,13 @@ func (tc *Manager) AppliedResourceUID(id object.ObjMetadata) (types.UID, bool) { // AppliedResourceUIDs returns a set with the UIDs of all the // successfully applied resources. -func (tc *Manager) AppliedResourceUIDs() sets.String { // nolint:staticcheck - uids := sets.NewString() +func (tc *Manager) AppliedResourceUIDs() sets.Set[types.UID] { // nolint:staticcheck + uids := sets.New[types.UID]() for _, objStatus := range tc.inventory.ObjectStatuses { if objStatus.Strategy == actuation.ActuationStrategyApply && objStatus.Actuation == actuation.ActuationSucceeded { if objStatus.UID != "" { - uids.Insert(string(objStatus.UID)) + uids.Insert(objStatus.UID) } } } diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/inventory/policy.go b/vendor/sigs.k8s.io/cli-utils/pkg/inventory/policy.go index cae3d02a9c..157cb1cd64 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/inventory/policy.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/inventory/policy.go @@ -83,7 +83,11 @@ const ( NoMatch ) -func IDMatch(inv Info, obj *unstructured.Unstructured) IDMatchStatus { +type Annotated interface { + GetAnnotations() map[string]string +} + +func IDMatch(inv Info, obj Annotated) IDMatchStatus { annotations := obj.GetAnnotations() value, found := annotations[OwningInventoryKey] if !found { @@ -95,7 +99,7 @@ func IDMatch(inv Info, obj *unstructured.Unstructured) IDMatchStatus { return NoMatch } -func CanApply(inv Info, obj *unstructured.Unstructured, policy Policy) (bool, error) { +func CanApply(inv Info, obj Annotated, policy Policy) (bool, error) { matchStatus := IDMatch(inv, obj) switch matchStatus { case Empty: diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/jsonpath/jsonpath.go b/vendor/sigs.k8s.io/cli-utils/pkg/jsonpath/jsonpath.go index 8dc77cd28c..854711674a 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/jsonpath/jsonpath.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/jsonpath/jsonpath.go @@ -20,7 +20,7 @@ import ( // Returns the node values that were found (zero or more), or an error. // For details about the JSONPath expression language, see: // https://goessner.net/articles/JsonPath/ -func Get(obj map[string]interface{}, expression string) ([]interface{}, error) { +func Get(obj map[string]any, expression string) ([]any, error) { // format input object as json for input into jsonpath library jsonBytes, err := json.Marshal(obj) if err != nil { @@ -41,7 +41,7 @@ func Get(obj map[string]interface{}, expression string) ([]interface{}, error) { return nil, fmt.Errorf("failed to evaluate jsonpath expression (%s): %w", expression, err) } - result := make([]interface{}, len(nodes)) + result := make([]any, len(nodes)) // get value of all matching nodes for i, node := range nodes { @@ -54,7 +54,7 @@ func Get(obj map[string]interface{}, expression string) ([]interface{}, error) { klog.V(7).Infof("jsonpath.Get output as json:\n%s", jsonBytes) // parse json back into a Go primitive - var value interface{} + var value any err = yaml.Unmarshal(jsonBytes, &value) if err != nil { return nil, fmt.Errorf("failed to unmarshal jsonpath result: %w", err) @@ -69,7 +69,7 @@ func Get(obj map[string]interface{}, expression string) ([]interface{}, error) { // Returns the number of matching nodes that were updated, or an error. // For details about the JSONPath expression language, see: // https://goessner.net/articles/JsonPath/ -func Set(obj map[string]interface{}, expression string, value interface{}) (int, error) { +func Set(obj map[string]any, expression string, value any) (int, error) { // format input object as json for input into jsonpath library jsonBytes, err := json.Marshal(obj) if err != nil { @@ -105,14 +105,14 @@ func Set(obj map[string]interface{}, expression string, value interface{}) (int, err = node.SetNumeric(float64(typedValue)) case float64: err = node.SetNumeric(typedValue) - case []interface{}: + case []any: var arrayValue []*ajson.Node arrayValue, err = toArrayOfNodes(typedValue) if err != nil { break } err = node.SetArray(arrayValue) - case map[string]interface{}: + case map[string]any: var mapValue map[string]*ajson.Node mapValue, err = toMapOfNodes(typedValue) if err != nil { @@ -148,7 +148,7 @@ func Set(obj map[string]interface{}, expression string, value interface{}) (int, return len(nodes), nil } -func toArrayOfNodes(obj []interface{}) ([]*ajson.Node, error) { +func toArrayOfNodes(obj []any) ([]*ajson.Node, error) { out := make([]*ajson.Node, len(obj)) for index, value := range obj { // format input object as json for input into jsonpath library @@ -167,7 +167,7 @@ func toArrayOfNodes(obj []interface{}) ([]*ajson.Node, error) { return out, nil } -func toMapOfNodes(obj map[string]interface{}) (map[string]*ajson.Node, error) { +func toMapOfNodes(obj map[string]any) (map[string]*ajson.Node, error) { out := make(map[string]*ajson.Node, len(obj)) for key, value := range obj { // format input object as json for input into jsonpath library diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/polling/statusreaders/common.go b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/polling/statusreaders/common.go index c780940d37..83c803fafe 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/polling/statusreaders/common.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/polling/statusreaders/common.go @@ -167,7 +167,7 @@ func errResourceToResourceStatus(err error, resource *unstructured.Unstructured, // If the error is from the context, we don't attach that to the ResourceStatus, // but just return it directly so the caller can decide how to handle this // situation. - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || isRateLimiterContextDeadlineExceeded(err) { return nil, err } identifier := object.UnstructuredToObjMetadata(resource) @@ -193,7 +193,7 @@ func errIdentifierToResourceStatus(err error, identifier object.ObjMetadata) (*e // If the error is from the context, we don't attach that to the ResourceStatus, // but just return it directly so the caller can decide how to handle this // situation. - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || isRateLimiterContextDeadlineExceeded(err) { return nil, err } if apierrors.IsNotFound(err) { @@ -209,3 +209,20 @@ func errIdentifierToResourceStatus(err error, identifier object.ObjMetadata) (*e Error: err, }, nil } + +// isRateLimiterContextDeadlineExceeded checks if the error is a rate limiter "would exceed context deadline" error +// this allows us to treat it the same way as the context.Canceled and context.DeadlineExceeded errors +// instead of attaching the error to the ResourceStatus, caller can decide how to handle this +func isRateLimiterContextDeadlineExceeded(err error) bool { + for { + next := errors.Unwrap(err) + if next == nil { + break + } + err = next + } + + // there's no dedicated error type for this, hence we check the error message + // https://cs.opensource.google/go/x/time/+/refs/tags/v0.10.0:rate/rate.go;l=276 + return err != nil && err.Error() == "rate: Wait(n=1) would exceed context deadline" +} diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/core.go b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/core.go index 4cd6704e40..b7de595a74 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/core.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/core.go @@ -35,7 +35,7 @@ var legacyTypes = map[string]GetConditionsFn{ "batch/CronJob": alwaysReady, "ConfigMap": alwaysReady, "batch/Job": jobConditions, - "apiextensions.k8s.io/CustomResourceDefinition": crdConditions, + "apiextensions.k8s.io/CustomResourceDefinition": CRDConditions, } const ( @@ -466,14 +466,14 @@ func podConditions(u *unstructured.Unstructured) (*Result, error) { } } -func getCrashLoopingContainers(obj map[string]interface{}) ([]string, bool, error) { +func getCrashLoopingContainers(obj map[string]any) ([]string, bool, error) { var containerNames []string css, found, err := unstructured.NestedSlice(obj, "status", "containerStatuses") if !found || err != nil { return containerNames, found, err } for _, item := range css { - cs := item.(map[string]interface{}) + cs := item.(map[string]any) n, found := cs["name"] if !found { continue @@ -483,13 +483,13 @@ func getCrashLoopingContainers(obj map[string]interface{}) ([]string, bool, erro if !found { continue } - state := s.(map[string]interface{}) + state := s.(map[string]any) ws, found := state["waiting"] if !found { continue } - waitingState := ws.(map[string]interface{}) + waitingState := ws.(map[string]any) r, found := waitingState["reason"] if !found { @@ -539,6 +539,7 @@ func jobConditions(u *unstructured.Unstructured) (*Result, error) { active := GetIntField(obj, ".status.active", 0) failed := GetIntField(obj, ".status.failed", 0) starttime := GetStringField(obj, ".status.startTime", "") + suspended := GetBoolField(obj, ".spec.suspend", false) // Conditions // https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24 @@ -562,6 +563,15 @@ func jobConditions(u *unstructured.Unstructured) (*Result, error) { return newFailedStatus("JobFailed", fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions)), nil } + // Jobs with spec.suspend=true and a Suspended status, should not be treated as in-progress. + case "Suspended": + if c.Status == corev1.ConditionTrue && suspended { + return &Result{ + Status: CurrentStatus, + Message: "Job is suspended", + Conditions: []Condition{}, + }, nil + } } } @@ -598,7 +608,7 @@ func serviceConditions(u *unstructured.Unstructured) (*Result, error) { }, nil } -func crdConditions(u *unstructured.Unstructured) (*Result, error) { +func CRDConditions(u *unstructured.Unstructured) (*Result, error) { obj := u.UnstructuredContent() objc, err := GetObjectWithConditions(obj) diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/status.go b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/status.go index a8f2e28ded..82d47585c7 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/status.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/status.go @@ -6,6 +6,7 @@ package status import ( "errors" "fmt" + "slices" "time" corev1 "k8s.io/api/core/v1" @@ -48,14 +49,12 @@ func (s Status) String() string { return string(s) } -// StatusFromString turns a string into a Status. Will panic if the provided string is +// FromStringOrDie turns a string into a Status. Will panic if the provided string is // not a valid status. func FromStringOrDie(text string) Status { s := Status(text) - for _, r := range Statuses { - if s == r { - return s - } + if slices.Contains(Statuses, s) { + return s } panic(fmt.Errorf("string has invalid status: %s", s)) } @@ -196,7 +195,7 @@ func Augment(u *unstructured.Unstructured) error { } if !found { - conditions = make([]interface{}, 0) + conditions = make([]any, 0) } currentTime := time.Now().UTC().Format(time.RFC3339) @@ -204,7 +203,7 @@ func Augment(u *unstructured.Unstructured) error { for _, resCondition := range res.Conditions { present := false for _, c := range conditions { - condition, ok := c.(map[string]interface{}) + condition, ok := c.(map[string]any) if !ok { return errors.New("condition does not have the expected structure") } @@ -228,7 +227,7 @@ func Augment(u *unstructured.Unstructured) error { } } if !present { - conditions = append(conditions, map[string]interface{}{ + conditions = append(conditions, map[string]any{ "lastTransitionTime": currentTime, "lastUpdateTime": currentTime, "message": resCondition.Message, diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/util.go b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/util.go index b22152cc71..35635a548d 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/util.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/status/util.go @@ -76,7 +76,7 @@ type BasicCondition struct { } // GetObjectWithConditions return typed object -func GetObjectWithConditions(in map[string]interface{}) (*ObjWithConditions, error) { +func GetObjectWithConditions(in map[string]any) (*ObjWithConditions, error) { var out = new(ObjWithConditions) err := runtime.DefaultUnstructuredConverter.FromUnstructured(in, out) if err != nil { @@ -100,7 +100,7 @@ func getConditionWithStatus(conditions []BasicCondition, conditionType string, s } // GetStringField return field as string defaulting to value if not found -func GetStringField(obj map[string]interface{}, fieldPath string, defaultValue string) string { +func GetStringField(obj map[string]any, fieldPath string, defaultValue string) string { var rv = defaultValue fields := strings.Split(fieldPath, ".") @@ -119,8 +119,10 @@ func GetStringField(obj map[string]interface{}, fieldPath string, defaultValue s return rv } -// GetIntField return field as string defaulting to value if not found -func GetIntField(obj map[string]interface{}, fieldPath string, defaultValue int) int { +// GetIntField return field as int defaulting to value if not found +func GetIntField(obj map[string]any, fieldPath string, defaultValue int) int { + var rv = defaultValue + fields := strings.Split(fieldPath, ".") if fields[0] == "" { fields = fields[1:] @@ -128,7 +130,7 @@ func GetIntField(obj map[string]interface{}, fieldPath string, defaultValue int) val, found, err := apiunstructured.NestedFieldNoCopy(obj, fields...) if !found || err != nil { - return defaultValue + return rv } switch v := val.(type) { @@ -139,5 +141,25 @@ func GetIntField(obj map[string]interface{}, fieldPath string, defaultValue int) case int64: return int(v) } - return defaultValue + return rv +} + +// GetBoolField return field as boolean defaulting to value if not found +func GetBoolField(obj map[string]any, fieldPath string, defaultValue bool) bool { + var rv = defaultValue + + fields := strings.Split(fieldPath, ".") + if fields[0] == "" { + fields = fields[1:] + } + + val, found, err := apiunstructured.NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return rv + } + + if v, ok := val.(bool); ok { + return v + } + return rv } diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/dynamic_informer_factory.go b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/dynamic_informer_factory.go index 1e0c95390b..5a98402d0f 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/dynamic_informer_factory.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/dynamic_informer_factory.go @@ -35,13 +35,12 @@ func NewDynamicInformerFactory(client dynamic.Interface, resyncPeriod time.Durat } } -func (f *DynamicInformerFactory) NewInformer(ctx context.Context, mapping *meta.RESTMapping, namespace string) cache.SharedIndexInformer { +func (f *DynamicInformerFactory) NewInformer(mapping *meta.RESTMapping, namespace string) cache.SharedIndexInformer { // Unstructured example output need `"apiVersion"` and `"kind"` set. example := &unstructured.Unstructured{} example.SetGroupVersionKind(mapping.GroupVersionKind) return cache.NewSharedIndexInformer( NewFilteredListWatchFromDynamicClient( - ctx, f.Client, mapping.Resource, namespace, @@ -70,7 +69,6 @@ type Filters struct { // NewFilteredListWatchFromDynamicClient creates a new ListWatch from the // specified client, resource, namespace, and optional filters. func NewFilteredListWatchFromDynamicClient( - ctx context.Context, client dynamic.Interface, resource schema.GroupVersionResource, namespace string, @@ -108,7 +106,7 @@ func NewFilteredListWatchFromDynamicClient( } return nil } - return NewModifiedListWatchFromDynamicClient(ctx, client, resource, namespace, optionsModifier) + return NewModifiedListWatchFromDynamicClient(client, resource, namespace, optionsModifier) } // NewModifiedListWatchFromDynamicClient creates a new ListWatch from the @@ -117,30 +115,27 @@ func NewFilteredListWatchFromDynamicClient( // ListOptions. Provide customized modifier function to apply modification to // ListOptions with field selectors, label selectors, or any other desired options. func NewModifiedListWatchFromDynamicClient( - ctx context.Context, client dynamic.Interface, resource schema.GroupVersionResource, namespace string, optionsModifier func(*metav1.ListOptions) error, ) *cache.ListWatch { - listFunc := func(options metav1.ListOptions) (runtime.Object, error) { - if err := optionsModifier(&options); err != nil { - return nil, fmt.Errorf("modifying list options: %w", err) - } - return client.Resource(resource). - Namespace(namespace). - List(ctx, options) - } - watchFunc := func(options metav1.ListOptions) (watch.Interface, error) { - options.Watch = true - if err := optionsModifier(&options); err != nil { - return nil, fmt.Errorf("modifying watch options: %w", err) - } - return client.Resource(resource). - Namespace(namespace). - Watch(ctx, options) + res := client.Resource(resource).Namespace(namespace) + return &cache.ListWatch{ + ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + if err := optionsModifier(&options); err != nil { + return nil, fmt.Errorf("modifying list options: %w", err) + } + return res.List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + options.Watch = true + if err := optionsModifier(&options); err != nil { + return nil, fmt.Errorf("modifying watch options: %w", err) + } + return res.Watch(ctx, options) + }, } - return &cache.ListWatch{ListFunc: listFunc, WatchFunc: watchFunc} } func andLabelSelectors(selectors ...labels.Selector) labels.Selector { diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/object_status_reporter.go b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/object_status_reporter.go index 5ce33b4c33..d69f337491 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/object_status_reporter.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/object_status_reporter.go @@ -327,7 +327,7 @@ func (w *ObjectStatusReporter) startInformerNow( return err } - informer := w.InformerFactory.NewInformer(ctx, mapping, gkn.Namespace) + informer := w.InformerFactory.NewInformer(mapping, gkn.Namespace) w.informerRefs[gkn].SetInformer(informer) @@ -361,7 +361,7 @@ func (w *ObjectStatusReporter) startInformerNow( // Informer will be stopped when the context is cancelled. go func() { klog.V(3).Infof("Watch starting: %v", gkn) - informer.Run(ctx.Done()) + informer.RunWithContext(ctx) klog.V(3).Infof("Watch stopped: %v", gkn) // Signal to the caller there will be no more events for this GroupKind. close(eventCh) @@ -435,7 +435,7 @@ func (w *ObjectStatusReporter) eventHandler( ) cache.ResourceEventHandler { var handler cache.ResourceEventHandlerFuncs - handler.AddFunc = func(iobj interface{}) { + handler.AddFunc = func(iobj any) { // Bail early if the context is cancelled, to avoid unnecessary work. if ctx.Err() != nil { return @@ -484,7 +484,7 @@ func (w *ObjectStatusReporter) eventHandler( } } - handler.UpdateFunc = func(_, iobj interface{}) { + handler.UpdateFunc = func(_, iobj any) { // Bail early if the context is cancelled, to avoid unnecessary work. if ctx.Err() != nil { return @@ -533,7 +533,7 @@ func (w *ObjectStatusReporter) eventHandler( } } - handler.DeleteFunc = func(iobj interface{}) { + handler.DeleteFunc = func(iobj any) { // Bail early if the context is cancelled, to avoid unnecessary work. if ctx.Err() != nil { return diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/unschedulable.go b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/unschedulable.go index 5d8f9a0a6f..0b37eb480d 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/unschedulable.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/unschedulable.go @@ -4,6 +4,8 @@ package watcher import ( + "slices" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" @@ -28,12 +30,7 @@ func isObjectUnschedulable(rs *event.ResourceStatus) bool { return true } // recurse through generated resources - for _, subRS := range rs.GeneratedResources { - if isObjectUnschedulable(subRS) { - return true - } - } - return false + return slices.ContainsFunc(rs.GeneratedResources, isObjectUnschedulable) } // isPodUnschedulable returns true if the object is a pod and is unschedulable @@ -50,12 +47,12 @@ func isPodUnschedulable(obj *unstructured.Unstructured) bool { if err != nil || !found { return false } - cnds, ok := icnds.([]interface{}) + cnds, ok := icnds.([]any) if !ok { return false } for _, icnd := range cnds { - cnd, ok := icnd.(map[string]interface{}) + cnd, ok := icnd.(map[string]any) if !ok { return false } diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/object/field.go b/vendor/sigs.k8s.io/cli-utils/pkg/object/field.go index 7d530b4f98..0e212cfd6f 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/object/field.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/object/field.go @@ -13,8 +13,8 @@ import ( // NestedField gets a value from a KRM map, if it exists, otherwise nil. // Fields can be string (map key) or int (array index). -func NestedField(obj map[string]interface{}, fields ...interface{}) (interface{}, bool, error) { - var val interface{} = obj +func NestedField(obj map[string]any, fields ...any) (any, bool, error) { + var val any = obj for i, field := range fields { if val == nil { @@ -22,7 +22,7 @@ func NestedField(obj map[string]interface{}, fields ...interface{}) (interface{} } switch typedField := field.(type) { case string: - if m, ok := val.(map[string]interface{}); ok { + if m, ok := val.(map[string]any); ok { val, ok = m[typedField] if !ok { // not in map @@ -32,7 +32,7 @@ func NestedField(obj map[string]interface{}, fields ...interface{}) (interface{} return nil, false, InvalidType(fields[:i+1], val, "map[string]interface{}") } case int: - if s, ok := val.([]interface{}); ok { + if s, ok := val.([]any); ok { if typedField >= len(s) { // index out of range return nil, false, nil @@ -50,14 +50,14 @@ func NestedField(obj map[string]interface{}, fields ...interface{}) (interface{} // InvalidType returns a *Error indicating "invalid value type". This is used // to report malformed values (e.g. found int, expected string). -func InvalidType(fieldPath []interface{}, value interface{}, validTypes string) *field.Error { +func InvalidType(fieldPath []any, value any, validTypes string) *field.Error { return Invalid(fieldPath, value, fmt.Sprintf("found type %T, expected %s", value, validTypes)) } // Invalid returns a *Error indicating "invalid value". This is used // to report malformed values (e.g. failed regex match, too long, out of bounds). -func Invalid(fieldPath []interface{}, value interface{}, detail string) *field.Error { +func Invalid(fieldPath []any, value any, detail string) *field.Error { return &field.Error{ Type: field.ErrorTypeInvalid, Field: FieldPath(fieldPath), @@ -68,7 +68,7 @@ func Invalid(fieldPath []interface{}, value interface{}, detail string) *field.E // NotFound returns a *Error indicating "value not found". This is // used to report failure to find a requested value (e.g. looking up an ID). -func NotFound(fieldPath []interface{}, value interface{}) *field.Error { +func NotFound(fieldPath []any, value any) *field.Error { return &field.Error{ Type: field.ErrorTypeNotFound, Field: FieldPath(fieldPath), @@ -83,7 +83,7 @@ func NotFound(fieldPath []interface{}, value interface{}) *field.Error { // Complex strings will be wrapped with square brackets and double quotes. // Integers will be wrapped with square brackets. // All other types will be formatted best-effort within square brackets. -func FieldPath(fieldPath []interface{}) string { +func FieldPath(fieldPath []any) string { var sb strings.Builder for _, field := range fieldPath { switch typedField := field.(type) { diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/object/graph/graph.go b/vendor/sigs.k8s.io/cli-utils/pkg/object/graph/graph.go index d3c16a4f7b..05df2ff12e 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/object/graph/graph.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/object/graph/graph.go @@ -7,6 +7,7 @@ package graph import ( + "slices" "sort" "sigs.k8s.io/cli-utils/pkg/object" @@ -101,12 +102,7 @@ func (g *Graph) isAdjacent(from object.ObjMetadata, to object.ObjMetadata) bool return false } // Iterate through adjacency list to see if "to" vertex is adjacent. - for _, vertex := range g.edges[from] { - if vertex == to { - return true - } - } - return false + return slices.Contains(g.edges[from], to) } // Size returns the number of vertices in the graph. diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/object/objmetadata_set.go b/vendor/sigs.k8s.io/cli-utils/pkg/object/objmetadata_set.go index 31a8b473fa..9da66b9ab1 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/object/objmetadata_set.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/object/objmetadata_set.go @@ -6,6 +6,7 @@ package object import ( "hash/fnv" + "slices" "sort" "strconv" ) @@ -54,12 +55,7 @@ func (setA ObjMetadataSet) Equal(setB ObjMetadataSet) bool { // Contains checks if the provided ObjMetadata exists in the set. func (setA ObjMetadataSet) Contains(id ObjMetadata) bool { - for _, om := range setA { - if om == id { - return true - } - } - return false + return slices.Contains(setA, id) } // Remove the object from the set and return the updated set. @@ -75,13 +71,7 @@ func (setA ObjMetadataSet) Remove(obj ObjMetadata) ObjMetadataSet { // Intersection returns the set of unique objects in both set A and set B. func (setA ObjMetadataSet) Intersection(setB ObjMetadataSet) ObjMetadataSet { - var maxlen int - if len(setA) > len(setB) { - maxlen = len(setA) - } else { - maxlen = len(setB) - } - mapI := make(map[ObjMetadata]struct{}, maxlen) + mapI := make(map[ObjMetadata]struct{}, max(len(setA), len(setB))) mapB := setB.ToMap() for _, a := range setA { if _, ok := mapB[a]; ok { diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/object/unstructured.go b/vendor/sigs.k8s.io/cli-utils/pkg/object/unstructured.go index 57a16d064d..6eb2c89efa 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/object/unstructured.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/object/unstructured.go @@ -132,14 +132,14 @@ func LookupResourceScope(u *unstructured.Unstructured, crds []*unstructured.Unst return nil, err } if !found || group == "" { - return nil, NotFound([]interface{}{"spec", "group"}, group) + return nil, NotFound([]any{"spec", "group"}, group) } kind, found, err := NestedField(crd.Object, "spec", "names", "kind") if err != nil { return nil, err } if !found || kind == "" { - return nil, NotFound([]interface{}{"spec", "kind"}, group) + return nil, NotFound([]any{"spec", "kind"}, group) } if gvk.Kind != kind || gvk.Group != group { continue @@ -163,7 +163,7 @@ func LookupResourceScope(u *unstructured.Unstructured, crds []*unstructured.Unst case "Cluster": return meta.RESTScopeRoot, nil default: - return nil, Invalid([]interface{}{"spec", "scope"}, scopeName, + return nil, Invalid([]any{"spec", "scope"}, scopeName, "expected Namespaced or Cluster") } } @@ -178,14 +178,14 @@ func crdDefinesVersion(crd *unstructured.Unstructured, version string) (bool, er return false, err } if !found { - return false, NotFound([]interface{}{"spec", "versions"}, versions) + return false, NotFound([]any{"spec", "versions"}, versions) } - versionsSlice, ok := versions.([]interface{}) + versionsSlice, ok := versions.([]any) if !ok { - return false, InvalidType([]interface{}{"spec", "versions"}, versions, "[]interface{}") + return false, InvalidType([]any{"spec", "versions"}, versions, "[]interface{}") } if len(versionsSlice) == 0 { - return false, Invalid([]interface{}{"spec", "versions"}, versionsSlice, "must not be empty") + return false, Invalid([]any{"spec", "versions"}, versionsSlice, "must not be empty") } for i := range versionsSlice { name, found, err := NestedField(crd.Object, "spec", "versions", i, "name") @@ -193,7 +193,7 @@ func crdDefinesVersion(crd *unstructured.Unstructured, version string) (bool, er return false, err } if !found { - return false, NotFound([]interface{}{"spec", "versions", i, "name"}, name) + return false, NotFound([]any{"spec", "versions", i, "name"}, name) } if name == version { return true, nil diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/testutil/asserter.go b/vendor/sigs.k8s.io/cli-utils/pkg/testutil/asserter.go index 597d8e82a9..7bb1936881 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/testutil/asserter.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/testutil/asserter.go @@ -30,7 +30,7 @@ var DefaultAsserter = NewAsserter(cmpopts.EquateErrors()) // EqualMatcher returns a new EqualMatcher with the Asserter's options and the // specified expected value. -func (a *Asserter) EqualMatcher(expected interface{}) *EqualMatcher { +func (a *Asserter) EqualMatcher(expected any) *EqualMatcher { return &EqualMatcher{ Expected: expected, Options: a.Options, @@ -39,7 +39,7 @@ func (a *Asserter) EqualMatcher(expected interface{}) *EqualMatcher { // Equal fails the test if the actual value does not deeply equal the // expected value. Prints a diff on failure. -func (a *Asserter) Equal(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { +func (a *Asserter) Equal(t *testing.T, expected, actual any, msgAndArgs ...any) { t.Helper() // print the caller's file:line, instead of this func, on failure matcher := a.EqualMatcher(expected) match, err := matcher.Match(actual) @@ -54,14 +54,14 @@ func (a *Asserter) Equal(t *testing.T, expected, actual interface{}, msgAndArgs // AssertEqual fails the test if the actual value does not deeply equal the // expected value. Prints a diff on failure. -func AssertEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { +func AssertEqual(t *testing.T, expected, actual any, msgAndArgs ...any) { t.Helper() // print the caller's file:line, instead of this func, on failure DefaultAsserter.Equal(t, expected, actual, msgAndArgs...) } // NotEqual fails the test if the actual value deeply equals the // expected value. Prints a diff on failure. -func (a *Asserter) NotEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { +func (a *Asserter) NotEqual(t *testing.T, expected, actual any, msgAndArgs ...any) { t.Helper() // print the caller's file:line, instead of this func, on failure matcher := a.EqualMatcher(expected) match, err := matcher.Match(actual) @@ -76,7 +76,7 @@ func (a *Asserter) NotEqual(t *testing.T, expected, actual interface{}, msgAndAr // AssertNotEqual fails the test if the actual value deeply equals the // expected value. Prints a diff on failure. -func AssertNotEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { +func AssertNotEqual(t *testing.T, expected, actual any, msgAndArgs ...any) { t.Helper() // print the caller's file:line, instead of this func, on failure DefaultAsserter.NotEqual(t, expected, actual, msgAndArgs...) } diff --git a/vendor/sigs.k8s.io/cli-utils/pkg/testutil/matcher.go b/vendor/sigs.k8s.io/cli-utils/pkg/testutil/matcher.go index c9d3ca0f82..96bd74d62c 100644 --- a/vendor/sigs.k8s.io/cli-utils/pkg/testutil/matcher.go +++ b/vendor/sigs.k8s.io/cli-utils/pkg/testutil/matcher.go @@ -18,18 +18,18 @@ import ( // // Example Usage: // Expect(receivedEvents).To(testutil.Equal(expectedEvents)) -func Equal(expected interface{}) *EqualMatcher { +func Equal(expected any) *EqualMatcher { return DefaultAsserter.EqualMatcher(expected) } type EqualMatcher struct { - Expected interface{} + Expected any Options cmp.Options explanation error } -func (cm *EqualMatcher) Match(actual interface{}) (bool, error) { +func (cm *EqualMatcher) Match(actual any) (bool, error) { match := cmp.Equal(cm.Expected, actual, cm.Options...) if !match { cm.explanation = errors.New(cmp.Diff(cm.Expected, actual, cm.Options...)) @@ -37,12 +37,12 @@ func (cm *EqualMatcher) Match(actual interface{}) (bool, error) { return match, nil } -func (cm *EqualMatcher) FailureMessage(actual interface{}) string { +func (cm *EqualMatcher) FailureMessage(actual any) string { return "\n" + format.Message(actual, "to deeply equal", cm.Expected) + "\nDiff (- Expected, + Actual):\n" + indent(cm.explanation.Error(), 1) } -func (cm *EqualMatcher) NegatedFailureMessage(actual interface{}) string { +func (cm *EqualMatcher) NegatedFailureMessage(actual any) string { return "\n" + format.Message(actual, "not to deeply equal", cm.Expected) + "\nDiff (- Expected, + Actual):\n" + indent(cm.explanation.Error(), 1) }