Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(backport release-0.8): fix(kubeclient): retry patch on conflict #2516

Merged
merged 1 commit into from
Sep 9, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 38 additions & 31 deletions internal/kubeclient/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
}

Expand Down
Loading