From c178b0d9eda0b9f51b9f52483b4ede42ce845328 Mon Sep 17 00:00:00 2001 From: Jakub Jaruszewski Date: Mon, 15 Jun 2026 12:14:11 +0200 Subject: [PATCH] Only retry on resourcequota transient conflict Signed-off-by: Jakub Jaruszewski --- pkg/kube/client.go | 23 ++++++++++++++++++++++- pkg/kube/client_test.go | 18 ++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index c5bdb8b81..7c98a653d 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -323,8 +323,9 @@ func (c *Client) makeCreateApplyFunc(serverSideApply, forceConflicts, dryRun boo slog.String("name", target.Name), slog.String("gvk", target.Mapping.GroupVersionKind.String())) - return retry.RetryOnConflict( + return retry.OnError( retry.DefaultRetry, + isQuotaConflict, func() error { err := patchResourceServerSide(target, dryRun, forceConflicts, fieldValidationDirective) if err != nil { @@ -954,6 +955,26 @@ func isIncompatibleServerError(err error) bool { return err.(*apierrors.StatusError).Status().Code == http.StatusUnsupportedMediaType } +// isQuotaConflict checks if the error is a conflict error specifically caused by +// a ResourceQuota. This is used to determine if a retry should be attempted, +// since quota conflicts are typically transient and can be resolved by retrying. +func isQuotaConflict(err error) bool { + if !apierrors.IsConflict(err) { + return false + } + + // Check the error message for the specific ResourceQuota conflict pattern. + // The error message from the ResourceQuota admission controller contains: + // "Operation cannot be fulfilled on resourcequotas" and "the object has been modified" + errMsg := err.Error() + if strings.Contains(errMsg, "Operation cannot be fulfilled on resourcequotas") && + strings.Contains(errMsg, "the object has been modified") { + return true + } + + return false +} + // getManagedFieldsManager returns the manager string. If one was set it will be returned. // Otherwise, one is calculated based on the name of the binary. func getManagedFieldsManager() string { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index f04be354a..6a96c915c 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -282,6 +282,24 @@ func TestCreate(t *testing.T) { return actions }(), }, + "Create fail: managed fields conflict (server-side apply)": { + Pods: newPodList("seal"), + ServerSideApply: true, + Callback: func(t *testing.T, _ testCase, _ []RequestResponseAction, req *http.Request) (*http.Response, error) { + t.Helper() + + // Return a generic 409 conflict (not quota-related) + // This simulates a managed fields conflict + return &http.Response{ + StatusCode: http.StatusConflict, + Request: req, + }, nil + }, + ExpectedErrorContains: "the server reported a conflict", + ExpectedActions: []string{ + "/namespaces/default/pods/seal:PATCH", + }, + }, } c := newTestClient(t)