diff --git a/internal/kubeclient/patch.go b/internal/kubeclient/patch.go index 6793e2697..5ef3381a4 100644 --- a/internal/kubeclient/patch.go +++ b/internal/kubeclient/patch.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -83,41 +84,47 @@ type UnstructuredPatchFn func(src, dest unstructured.Unstructured) error func PatchUnstructured(ctx context.Context, c client.Client, obj ObjectWithKind, modify UnstructuredPatchFn) error { destObj := unstructured.Unstructured{} destObj.SetGroupVersionKind(obj.GroupVersionKind()) - if err := c.Get(ctx, client.ObjectKeyFromObject(obj), &destObj); err != nil { - return fmt.Errorf( - "unable to get unstructured object for %s %q in namespace %q: %w", - destObj.GroupVersionKind().Kind, obj.GetName(), obj.GetNamespace(), err, - ) - } - - // Create a patch for the unstructured object. - // - // As we expect the object to be modified by the callback, while it may - // also simultaneously be modified by other clients (e.g. someone updating - // the object via `kubectl`), we use an optimistic lock to ensure that we - // only apply the patch if the object has not been modified since we - // fetched it. - patch := client.MergeFromWithOptions(destObj.DeepCopy(), client.MergeFromWithOptimisticLock{}) - - // Convert the typed object to an unstructured object. - srcObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) - if err != nil { - return fmt.Errorf("could not convert typed source object to unstructured object: %w", err) - } - srcApp := unstructured.Unstructured{Object: srcObj} - // Apply modifications to the unstructured object. - if err = modify(srcApp, destObj); err != nil { - return fmt.Errorf("failed to apply modifications to unstructured object: %w", err) - } - - // Issue the patch to the unstructured object. - if err = c.Patch(ctx, &destObj, patch); err != nil { - return fmt.Errorf("failed to patch the object: %w", err) + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := c.Get(ctx, client.ObjectKeyFromObject(obj), &destObj); err != nil { + return fmt.Errorf( + "unable to get unstructured object for %s %q in namespace %q: %w", + destObj.GroupVersionKind().Kind, obj.GetName(), obj.GetNamespace(), err, + ) + } + + // Create a patch for the unstructured object. + // + // As we expect the object to be modified by the callback, while it may + // also simultaneously be modified by other clients (e.g. someone updating + // the object via `kubectl`), we use an optimistic lock to ensure that we + // only apply the patch if the object has not been modified since we + // fetched it. + patch := client.MergeFromWithOptions(destObj.DeepCopy(), client.MergeFromWithOptimisticLock{}) + + // Convert the typed object to an unstructured object. + srcObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return fmt.Errorf("could not convert typed source object to unstructured object: %w", err) + } + srcApp := unstructured.Unstructured{Object: srcObj} + + // Apply modifications to the unstructured object. + if err = modify(srcApp, destObj); err != nil { + return fmt.Errorf("failed to apply modifications to unstructured object: %w", err) + } + + // Issue the patch to the unstructured object. + if err = c.Patch(ctx, &destObj, patch); err != nil { + return fmt.Errorf("failed to patch the object: %w", err) + } + return nil + }); err != nil { + return err } // Convert the unstructured object back to the typed object. - if err = runtime.DefaultUnstructuredConverter.FromUnstructured(destObj.Object, obj); err != nil { + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(destObj.Object, obj); err != nil { return fmt.Errorf("error converting unstructured object to typed object: %w", err) }