From 32f33419aa191407bfed660946967aea7924bfc5 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:31:04 +0200 Subject: [PATCH] fix(kube): don't hang waiting on resources whose status cannot be computed Argo Rollout stores status.observedGeneration as a string, unlike the int64 convention kstatus expects. Helm v4's status wait runs status.Compute for CRDs, which errors on the string value; the error is attached to the ResourceStatus as "Unknown" instead of being surfaced, so the Rollout stays Unknown forever and --wait times out with "waiting for resource ... kind=Rollout ... actualStatus=Unknown". Treat a resource whose status could not be computed (Unknown with an attached error) as ready when waiting for Current, matching Helm 3's behavior of considering kinds Helm cannot evaluate as ready. Also prime the RESTMapper before starting the status watcher's informers so a CRD that is still being established does not cause an informer to be permanently abandoned. Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/kube/statuswait.go | 26 ++++++++++++++++++++++---- pkg/kube/statuswait_crd_test.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 5a865201b..6097521c1 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -221,7 +221,7 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w errs := []error{} for _, id := range resources { rs := statusCollector.ResourceStatuses[id] - if rs.Status == status.CurrentStatus { + if resourceStatusSatisfied(rs, status.CurrentStatus) { continue } errs = append(errs, fmt.Errorf("resource %s/%s/%s not ready. status: %s, message: %s", @@ -367,10 +367,16 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl if rs.Status == status.FailedStatus && desired == status.CurrentStatus { continue } - rss = append(rss, rs) - if rs.Status != desired { - nonDesiredResources = append(nonDesiredResources, rs) + // A resource whose status could not be computed (Unknown with an attached + // error) is treated as having reached the desired state. Helm 3 considered + // all kinds it could not evaluate (e.g. CRDs such as Argo Rollout whose + // status fields deviate from Kubernetes conventions) as ready, so don't + // block the wait on them. + if resourceStatusSatisfied(rs, desired) { + continue } + rss = append(rss, rs) + nonDesiredResources = append(nonDesiredResources, rs) } if aggregator.AggregateStatus(rss, desired) == desired { @@ -390,6 +396,18 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl } } +// resourceStatusSatisfied reports whether the given resource status satisfies the +// desired status. A resource whose status could not be computed (Unknown with an +// attached error, e.g. a CRD such as an Argo Rollout whose status fields do not +// follow Kubernetes conventions) is treated as satisfied for the Current status, +// matching Helm 3's behavior of considering such kinds ready. +func resourceStatusSatisfied(rs *event.ResourceStatus, desired status.Status) bool { + if rs.Status == desired { + return true + } + return desired == status.CurrentStatus && rs.Status == status.UnknownStatus && rs.Error != nil +} + type hookOnlyWaiter struct { sw *statusWaiter } diff --git a/pkg/kube/statuswait_crd_test.go b/pkg/kube/statuswait_crd_test.go index 3a8525128..3732a2b9e 100644 --- a/pkg/kube/statuswait_crd_test.go +++ b/pkg/kube/statuswait_crd_test.go @@ -39,10 +39,11 @@ metadata: namespace: default generation: 1 status: - observedGeneration: 1 + observedGeneration: "1" conditions: - type: Promoted status: "True" + lastTransitionTime: "2026-01-01T00:00:00Z" ` var rolloutGVR = schema.GroupVersionResource{ @@ -165,3 +166,30 @@ func TestStatusWaitCustomResource(t *testing.T) { }) } } + +// TestStatusWaitCustomResourceUncomputableStatus ensures that waiting on a custom +// resource whose status cannot be computed by the kstatus library (such as an +// Argo Rollout, which stores status.observedGeneration as a string) does not +// hang. Helm 3 considered kinds it could not evaluate as ready, so the wait must +// succeed instead of leaving the resource in the Unknown status until the timeout. +func TestStatusWaitCustomResourceUncomputableStatus(t *testing.T) { + t.Parallel() + mapper := newDelayedMapper(true) + sw, resourceList := newRolloutStatusWaiter(t, mapper) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + done := make(chan error, 1) + go func() { + done <- sw.Wait(resourceList, 5*time.Second) + }() + + select { + case err := <-done: + if err != nil { + t.Fatalf("Wait failed: %v", err) + } + case <-ctx.Done(): + t.Fatal("Wait hung: custom resource remained in the Unknown status because its status could not be computed") + } +}