diff --git a/pkg/util/helper/work.go b/pkg/util/helper/work.go index 987a3dc8e709..9fe32047eda9 100644 --- a/pkg/util/helper/work.go +++ b/pkg/util/helper/work.go @@ -21,6 +21,8 @@ import ( "fmt" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" @@ -80,7 +82,7 @@ func CreateOrUpdateWork(ctx context.Context, client client.Client, workMeta meta runtimeObject := work.DeepCopy() var operationResult controllerutil.OperationResult err = retry.RetryOnConflict(retry.DefaultRetry, func() (err error) { - operationResult, err = controllerutil.CreateOrUpdate(ctx, client, runtimeObject, func() error { + operationResult, err = createOrUpdateWork(ctx, client, runtimeObject, func() error { if !runtimeObject.DeletionTimestamp.IsZero() { return fmt.Errorf("work %s/%s is being deleted", runtimeObject.GetNamespace(), runtimeObject.GetName()) } @@ -109,6 +111,63 @@ func CreateOrUpdateWork(ctx context.Context, client client.Client, workMeta meta return nil } +// createOrUpdateWork is a fork of sigs.k8s.io/controller-runtime/pkg/controller/controllerutil.CreateOrUpdate(). +// We modified it to avoid performing deep-equality checks on workloads represented as byte slices. +func createOrUpdateWork(ctx context.Context, c client.Client, obj *workv1alpha1.Work, f controllerutil.MutateFn) (controllerutil.OperationResult, error) { + key := client.ObjectKeyFromObject(obj) + if err := c.Get(ctx, key, obj); err != nil { + if !apierrors.IsNotFound(err) { + return controllerutil.OperationResultNone, err + } + if err := mutate(f, key, obj); err != nil { + return controllerutil.OperationResultNone, err + } + if err := c.Create(ctx, obj); err != nil { + return controllerutil.OperationResultNone, err + } + return controllerutil.OperationResultCreated, nil + } + + existing := obj.DeepCopy() + if err := mutate(f, key, obj); err != nil { + return controllerutil.OperationResultNone, err + } + + existingWorkloads, err := unmarshalWorkManifests(existing) + if err != nil { + return controllerutil.OperationResultNone, err + } + desiredWorkloads, err := unmarshalWorkManifests(obj) + if err != nil { + return controllerutil.OperationResultNone, err + } + workloadEqual := equality.Semantic.DeepEqual(existingWorkloads, desiredWorkloads) + + existing.Spec.Workload.Manifests = nil + objCopy := obj.DeepCopy() + objCopy.Spec.Workload.Manifests = nil + if workloadEqual && equality.Semantic.DeepEqual(existing, objCopy) { + return controllerutil.OperationResultNone, nil + } + + if err := c.Update(ctx, obj); err != nil { + return controllerutil.OperationResultNone, err + } + return controllerutil.OperationResultUpdated, nil +} + +func unmarshalWorkManifests(work *workv1alpha1.Work) ([]*unstructured.Unstructured, error) { + ret := make([]*unstructured.Unstructured, 0, len(work.Spec.Workload.Manifests)) + for _, manifest := range work.Spec.Workload.Manifests { + obj := &unstructured.Unstructured{} + if err := obj.UnmarshalJSON(manifest.Raw); err != nil { + return nil, err + } + ret = append(ret, obj) + } + return ret, nil +} + // GetWorksByLabelsSet gets WorkList by matching labels.Set. func GetWorksByLabelsSet(ctx context.Context, c client.Client, ls labels.Set) (*workv1alpha1.WorkList, error) { workList := &workv1alpha1.WorkList{}