diff --git a/go.mod b/go.mod index 24442bead..2b8fbb2d9 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/spf13/cobra v1.1.3 github.com/spf13/pflag v1.0.5 golang.org/x/tools v0.1.2 + gomodules.xyz/jsonpatch/v2 v2.2.0 google.golang.org/grpc v1.38.0 k8s.io/api v0.22.2 k8s.io/apiextensions-apiserver v0.22.2 diff --git a/pkg/crdexplorer/crdexplorer.go b/pkg/crdexplorer/crdexplorer.go index 82f24bc1a..0ff1c55ec 100644 --- a/pkg/crdexplorer/crdexplorer.go +++ b/pkg/crdexplorer/crdexplorer.go @@ -94,8 +94,8 @@ func (i *customResourceExplorerImpl) GetReplicas(object *unstructured.Unstructur } // Retain returns the objects that based on the "desired" object but with values retained from the "observed" object. -func (i *customResourceExplorerImpl) Retain(desired *unstructured.Unstructured, cluster *unstructured.Unstructured) (retained *unstructured.Unstructured, err error) { +func (i *customResourceExplorerImpl) Retain(desired *unstructured.Unstructured, observed *unstructured.Unstructured) (retained *unstructured.Unstructured, err error) { // TODO(RainbowMango): consult to the dynamic webhooks first. - return i.defaultExplorer.Retain(desired, cluster) + return i.defaultExplorer.Retain(desired, observed) } diff --git a/pkg/crdexplorer/customizedexplorer/webhook/attributes.go b/pkg/crdexplorer/customizedexplorer/webhook/attributes.go index c766e2cda..2357083a8 100644 --- a/pkg/crdexplorer/customizedexplorer/webhook/attributes.go +++ b/pkg/crdexplorer/customizedexplorer/webhook/attributes.go @@ -13,6 +13,7 @@ import ( type RequestAttributes struct { Operation configv1alpha1.OperationType Object *unstructured.Unstructured + ObservedObj *unstructured.Unstructured ReplicasSet int32 AggregatedStatus []workv1alpha1.AggregatedStatusItem } diff --git a/pkg/crdexplorer/customizedexplorer/webhook/explorereview.go b/pkg/crdexplorer/customizedexplorer/webhook/explorereview.go index 7a10d455a..2c2d896e8 100644 --- a/pkg/crdexplorer/customizedexplorer/webhook/explorereview.go +++ b/pkg/crdexplorer/customizedexplorer/webhook/explorereview.go @@ -29,7 +29,7 @@ func CreateExploreReview(versions []string, attributes *RequestAttributes) (uid // CreateV1alpha1ExploreReview creates an ExploreReview for the provided RequestAttributes. func CreateV1alpha1ExploreReview(uid types.UID, attributes *RequestAttributes) *configv1alpha1.ExploreReview { - return &configv1alpha1.ExploreReview{ + review := &configv1alpha1.ExploreReview{ Request: &configv1alpha1.ExploreRequest{ UID: uid, Kind: metav1.GroupVersionKind{ @@ -47,6 +47,11 @@ func CreateV1alpha1ExploreReview(uid types.UID, attributes *RequestAttributes) * AggregatedStatus: attributes.AggregatedStatus, }, } + + if attributes.ObservedObj != nil { + review.Request.ObservedObject = &runtime.RawExtension{Object: attributes.ObservedObj.DeepCopyObject()} + } + return review } // VerifyExploreReview checks the validity of the provided explore review object, and returns ResponseAttributes, diff --git a/pkg/webhook/explorer/decode.go b/pkg/webhook/explorer/decode.go index 101968518..df69ade01 100644 --- a/pkg/webhook/explorer/decode.go +++ b/pkg/webhook/explorer/decode.go @@ -23,16 +23,26 @@ func NewDecoder(scheme *runtime.Scheme) *Decoder { } // Decode decodes the inlined object in the ExploreRequest into the passed-in runtime.Object. +// If you want to decode the ObservedObject in the ExploreRequest, use DecodeRaw. // It errors out if req.Object.Raw is empty i.e. containing 0 raw bytes. func (d *Decoder) Decode(req Request, into runtime.Object) error { if len(req.Object.Raw) == 0 { return fmt.Errorf("there is no context to decode") } + return d.DecodeRaw(req.Object, into) +} + +// DecodeRaw decodes a RawExtension object into the passed-in runtime.Object. +// It errors out if rawObj is empty i.e. containing 0 raw bytes. +func (d *Decoder) DecodeRaw(rawObj runtime.RawExtension, into runtime.Object) error { + if len(rawObj.Raw) == 0 { + return fmt.Errorf("there is no content to decode") + } if unstructuredInto, isUnstructured := into.(*unstructured.Unstructured); isUnstructured { // unmarshal into unstructured's underlying object to avoid calling the decoder - return json.Unmarshal(req.Object.Raw, &unstructuredInto.Object) + return json.Unmarshal(rawObj.Raw, &unstructuredInto.Object) } deserializer := d.codecs.UniversalDeserializer() - return runtime.DecodeInto(deserializer, req.Object.Raw, into) + return runtime.DecodeInto(deserializer, rawObj.Raw, into) } diff --git a/pkg/webhook/explorer/response.go b/pkg/webhook/explorer/response.go index 16043893a..bf67fa91b 100644 --- a/pkg/webhook/explorer/response.go +++ b/pkg/webhook/explorer/response.go @@ -1,8 +1,11 @@ package explorer import ( + "encoding/json" "net/http" + "gomodules.xyz/jsonpatch/v2" + configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" ) @@ -40,3 +43,29 @@ func ValidationResponse(successful bool, msg string) Response { }, } } + +// PatchResponseFromRaw takes 2 byte arrays and returns a new response with patch. +func PatchResponseFromRaw(original, current []byte) Response { + patches, err := jsonpatch.CreatePatch(original, current) + if err != nil { + return Errored(http.StatusInternalServerError, err) + } + + if len(patches) == 0 { + return Succeeded("") + } + + patch, err := json.Marshal(patches) + if err != nil { + return Errored(http.StatusInternalServerError, err) + } + + patchType := configv1alpha1.PatchTypeJSONPatch + return Response{ + ExploreResponse: configv1alpha1.ExploreResponse{ + Successful: true, + Patch: patch, + PatchType: &patchType, + }, + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 65276aff9..c14c1c961 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -429,6 +429,7 @@ golang.org/x/tools/internal/imports golang.org/x/xerrors golang.org/x/xerrors/internal # gomodules.xyz/jsonpatch/v2 v2.2.0 +## explicit gomodules.xyz/jsonpatch/v2 # google.golang.org/appengine v1.6.7 google.golang.org/appengine/internal