From 538480c31c3e3b5a808476c9af26952d0dc47698 Mon Sep 17 00:00:00 2001 From: raphael Date: Thu, 16 Jul 2026 22:19:45 +0100 Subject: [PATCH] fix: TestStatusWaitForDelete flakes due to WaitForDelete informer initialization race Signed-off-by: raphael --- pkg/kube/statuswait.go | 46 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 12c3e95a6..c34599884 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -33,7 +33,9 @@ import ( "github.com/fluxcd/cli-utils/pkg/kstatus/watcher" "github.com/fluxcd/cli-utils/pkg/object" appsv1 "k8s.io/api/apps/v1" + 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/dynamic" watchtools "k8s.io/client-go/tools/watch" @@ -136,16 +138,55 @@ func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Dur } func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceList, sw watcher.StatusWatcher) error { + if err := ctx.Err(); err != nil { + return err + } cancelCtx, cancel := context.WithCancel(ctx) defer cancel() resources := []object.ObjMetadata{} for _, resource := range resourceList { + gvk := resource.Object.GetObjectKind().GroupVersionKind() + mapping, err := w.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return err + } + var name, namespace string + if u, ok := resource.Object.(*unstructured.Unstructured); ok { + name = u.GetName() + namespace = u.GetNamespace() + } else { + accessor, err := meta.Accessor(resource.Object) + if err != nil { + return err + } + name = accessor.GetName() + namespace = accessor.GetNamespace() + } + var resourceClient dynamic.ResourceInterface + if namespace != "" { + resourceClient = w.client.Resource(mapping.Resource).Namespace(namespace) + } else { + resourceClient = w.client.Resource(mapping.Resource) + } + _, err = resourceClient.Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + continue + } + return err + } + obj, err := object.RuntimeToObjMeta(resource.Object) if err != nil { return err } resources = append(resources, obj) } + + if len(resources) == 0 { + return nil + } + eventCh := sw.Watch(cancelCtx, resources, watcher.Options{ RESTScopeStrategy: watcher.RESTScopeNamespace, }) @@ -241,11 +282,6 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl if rs == nil { continue } - // If a resource is already deleted before waiting has started, it will show as unknown. - // This check ensures we don't wait forever for a resource that is already deleted. - if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus { - continue - } // Failed is a terminal state. This check ensures we don't wait forever for a resource // that has already failed, as intervention is required to resolve the failure. if rs.Status == status.FailedStatus && desired == status.CurrentStatus {