diff --git a/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml b/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml index da26f48..c5b8dfd 100644 --- a/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml +++ b/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml @@ -778,6 +778,35 @@ spec: Version is the API version of the related resource. This can be left blank to automatically use the preferred version. type: string + watch: + description: |- + Watch configures how the agent identifies the owning primary object when a related + resource with origin: kcp changes. When set, the agent sets up a watch on the related + resource type and uses the configured rule to enqueue the correct primary object. + Without this field, changes to origin:kcp related resources do not trigger reconciliation. + properties: + byLabel: + additionalProperties: + type: string + description: |- + ByLabel configures the watch handler to list primary objects matching a label selector + derived from the changed object. Each map key is a label key on the primary object; + each value is a Go template expression evaluated with the changed object available as + .watchObject (with fields .name, .namespace, .labels). + type: object + byOwner: + description: |- + ByOwner configures the watch handler to inspect the OwnerReferences of the changed + object. When an OwnerReference with the given Kind is found, the referenced owner + is enqueued as the primary object. + properties: + kind: + description: Kind is the Kind to look for in the OwnerReferences of the changed related object. + type: string + required: + - kind + type: object + type: object required: - identifier - object diff --git a/hack/tools.checksums b/hack/tools.checksums index 759a74b..66dfa6f 100644 --- a/hack/tools.checksums +++ b/hack/tools.checksums @@ -1,4 +1,5 @@ boilerplate|GOARCH=amd64;GOOS=linux|6f05fc3be207ae2ed99e125509a08df677cb007e197e16607c654a434b91d47f +boilerplate|GOARCH=arm64;GOOS=darwin|3ac82c58f440ac8461746674e39311ba332d6d960966a060dd3be734b1111522 boilerplate|GOARCH=arm64;GOOS=linux|70253486ed7a803a35a9abb2bab4db2f1f7748d5266bf7a1c2ee298fda2b208a etcd|GOARCH=amd64;GOOS=linux|435d74510f3216bab1932fb6d7a6b5fe8245301143fcd25f7e65dfb7dcf8904a etcd|GOARCH=arm64;GOOS=linux|cc8c645e5a8df0f35f2a5c51d9b9383037eef0cf0167c52e648457b3971a7a09 @@ -11,4 +12,5 @@ kube-apiserver|GOARCH=arm64;GOOS=linux|6ade6c2646e2c01fde1095407452afc2b65e89d6d kubectl|GOARCH=amd64;GOOS=linux|9591f3d75e1581f3f7392e6ad119aab2f28ae7d6c6e083dc5d22469667f27253 kubectl|GOARCH=arm64;GOOS=linux|95df604e914941f3172a93fa8feeb1a1a50f4011dfbe0c01e01b660afc8f9b85 yq|GOARCH=amd64;GOOS=linux|0c2b24e645b57d8e7c0566d18643a6d4f5580feeea3878127354a46f2a1e4598 +yq|GOARCH=arm64;GOOS=darwin|164e10e5f7df62990e4f3823205e7ea42ba5660523a428df07c7386c0b62e3d9 yq|GOARCH=arm64;GOOS=linux|9477ac3cc447b6c083986129e35af8122eb2b938fe55c9c3e40436fb966e5813 diff --git a/internal/controller/sync/controller.go b/internal/controller/sync/controller.go index 509d8f4..d342235 100644 --- a/internal/controller/sync/controller.go +++ b/internal/controller/sync/controller.go @@ -39,9 +39,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/cluster" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -161,6 +164,73 @@ func Create( return nil, fmt.Errorf("failed to setup local-side watch: %w", err) } + // Watch origin:kcp related resources so that changes to them trigger reconciliation + // of the owning primary object. Only related resources with a Watch config are covered. + watchedGVKs := sets.New[schema.GroupVersionKind]() + for _, relRes := range pubRes.Spec.Related { + if relRes.Origin != syncagentv1alpha1.RelatedResourceOriginKcp || relRes.Watch == nil { + continue + } + + gvr := schema.GroupVersionResource{ + Group: relRes.Group, + Version: relRes.Version, + Resource: relRes.Resource, + } + + // Use the local REST mapper to determine the Kind. + gvk, err := localManager.GetRESTMapper().KindFor(gvr) + if err != nil { + log.Warnw("Failed to determine Kind for origin:kcp related resource, skipping watch", "gvr", gvr, "error", err) + continue + } + + // Deduplicate: only set up one watch per GVK. + if watchedGVKs.Has(gvk) { + continue + } + watchedGVKs.Insert(gvk) + + relatedDummy := &unstructured.Unstructured{} + relatedDummy.SetGroupVersionKind(gvk) + + var enqueueForRelated mchandler.TypedEventHandlerFunc[*unstructured.Unstructured, mcreconcile.Request] + + switch { + case relRes.Watch.ByOwner != nil: + ownerKind := relRes.Watch.ByOwner.Kind + enqueueForRelated = func(clusterName string, _ cluster.Cluster) handler.TypedEventHandler[*unstructured.Unstructured, mcreconcile.Request] { + return &byOwnerEventHandler{ + clusterName: clusterName, + ownerKind: ownerKind, + } + } + + case relRes.Watch.ByLabel != nil: + labelTemplates := relRes.Watch.ByLabel + primaryDummy := remoteDummy.DeepCopy() + enqueueForRelated = func(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[*unstructured.Unstructured, mcreconcile.Request] { + return &byLabelEventHandler{ + clusterName: clusterName, + client: cl.GetClient(), + primaryDummy: primaryDummy, + labelTemplates: labelTemplates, + log: log, + } + } + + default: + log.Warnw("origin:kcp related resource has Watch set but neither byOwner nor byLabel configured, skipping", "gvk", gvk) + continue + } + + if err := c.MultiClusterWatch(mcsource.TypedKind(relatedDummy, enqueueForRelated)); err != nil { + return nil, fmt.Errorf("failed to setup watch for origin:kcp related resource %v: %w", gvk, err) + } + + log.Infow("Set up watch for origin:kcp related resource", "gvk", gvk) + } + log.Info("Done setting up unmanaged controller.") return c, nil diff --git a/internal/controller/sync/related_handlers.go b/internal/controller/sync/related_handlers.go new file mode 100644 index 0000000..843a20f --- /dev/null +++ b/internal/controller/sync/related_handlers.go @@ -0,0 +1,144 @@ +/* +Copyright 2026 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sync + +import ( + "context" + + "go.uber.org/zap" + + "github.com/kcp-dev/api-syncagent/internal/sync/templating" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + mcreconcile "sigs.k8s.io/multicluster-runtime/pkg/reconcile" +) + +// byOwnerEventHandler enqueues the primary object by inspecting the OwnerReferences +// of the changed related object and finding one with the configured Kind. +type byOwnerEventHandler struct { + clusterName string + ownerKind string +} + +func (h *byOwnerEventHandler) Create(_ context.Context, evt event.TypedCreateEvent[*unstructured.Unstructured], q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + h.enqueue(evt.Object, q) +} + +func (h *byOwnerEventHandler) Update(_ context.Context, evt event.TypedUpdateEvent[*unstructured.Unstructured], q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + h.enqueue(evt.ObjectNew, q) +} + +func (h *byOwnerEventHandler) Delete(_ context.Context, evt event.TypedDeleteEvent[*unstructured.Unstructured], q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + h.enqueue(evt.Object, q) +} + +func (h *byOwnerEventHandler) Generic(_ context.Context, evt event.TypedGenericEvent[*unstructured.Unstructured], q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + h.enqueue(evt.Object, q) +} + +func (h *byOwnerEventHandler) enqueue(obj *unstructured.Unstructured, q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + for _, ref := range obj.GetOwnerReferences() { + if ref.Kind == h.ownerKind { + q.Add(mcreconcile.Request{ + ClusterName: h.clusterName, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: ref.Name, + }, + }, + }) + return + } + } +} + +// byLabelEventHandler enqueues primary objects by evaluating label templates against +// the changed related object and listing primaries matching the resulting label selector. +type byLabelEventHandler struct { + clusterName string + client ctrlruntimeclient.Client + primaryDummy *unstructured.Unstructured + labelTemplates map[string]string + log *zap.SugaredLogger +} + +func (h *byLabelEventHandler) Create(ctx context.Context, evt event.TypedCreateEvent[*unstructured.Unstructured], q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + h.enqueue(ctx, evt.Object, q) +} + +func (h *byLabelEventHandler) Update(ctx context.Context, evt event.TypedUpdateEvent[*unstructured.Unstructured], q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + h.enqueue(ctx, evt.ObjectNew, q) +} + +func (h *byLabelEventHandler) Delete(ctx context.Context, evt event.TypedDeleteEvent[*unstructured.Unstructured], q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + h.enqueue(ctx, evt.Object, q) +} + +func (h *byLabelEventHandler) Generic(ctx context.Context, evt event.TypedGenericEvent[*unstructured.Unstructured], q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + h.enqueue(ctx, evt.Object, q) +} + +func (h *byLabelEventHandler) enqueue(ctx context.Context, obj *unstructured.Unstructured, q workqueue.TypedRateLimitingInterface[mcreconcile.Request]) { + // Build the template context using the changed related object. + data := map[string]any{ + "watchObject": map[string]any{ + "name": obj.GetName(), + "namespace": obj.GetNamespace(), + "labels": obj.GetLabels(), + }, + } + + // Evaluate each label template to build the selector. + matchingLabels := ctrlruntimeclient.MatchingLabels{} + for key, tpl := range h.labelTemplates { + value, err := templating.Render(tpl, data) + if err != nil { + h.log.Warnw("Failed to evaluate byLabel template", "key", key, "template", tpl, "error", err) + return + } + matchingLabels[key] = value + } + + // List primary objects matching the derived label selector. + primaryList := &unstructured.UnstructuredList{} + primaryList.SetAPIVersion(h.primaryDummy.GetAPIVersion()) + primaryList.SetKind(h.primaryDummy.GetKind() + "List") + + if err := h.client.List(ctx, primaryList, matchingLabels); err != nil { + h.log.Warnw("Failed to list primary objects for byLabel watch", "selector", matchingLabels, "error", err) + return + } + + for i := range primaryList.Items { + primary := &primaryList.Items[i] + q.Add(mcreconcile.Request{ + ClusterName: h.clusterName, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: primary.GetNamespace(), + Name: primary.GetName(), + }, + }, + }) + } +} diff --git a/sdk/apis/syncagent/v1alpha1/published_resource.go b/sdk/apis/syncagent/v1alpha1/published_resource.go index 94c80ea..71eed55 100644 --- a/sdk/apis/syncagent/v1alpha1/published_resource.go +++ b/sdk/apis/syncagent/v1alpha1/published_resource.go @@ -256,6 +256,36 @@ type RelatedResourceSpec struct { // Mutation configures optional transformation rules for the related resource. // Status mutations are only performed when the related resource originates in kcp. Mutation *ResourceMutationSpec `json:"mutation,omitempty"` + + // Watch configures how the agent identifies the owning primary object when a related + // resource with origin: kcp changes. When set, the agent sets up a watch on the related + // resource type and uses the configured rule to enqueue the correct primary object. + // Without this field, changes to origin:kcp related resources do not trigger reconciliation. + Watch *RelatedResourceWatch `json:"watch,omitempty"` +} + +// RelatedResourceWatch configures how the watch handler maps a changed related resource +// back to its owning primary object. +// Exactly one of ByOwner or ByLabel must be set. +type RelatedResourceWatch struct { + // ByOwner configures the watch handler to inspect the OwnerReferences of the changed + // object. When an OwnerReference with the given Kind is found, the referenced owner + // is enqueued as the primary object. + // +optional + ByOwner *RelatedResourceWatchByOwner `json:"byOwner,omitempty"` + + // ByLabel configures the watch handler to list primary objects matching a label selector + // derived from the changed object. Each map key is a label key on the primary object; + // each value is a Go template expression evaluated with the changed object available as + // .watchObject (with fields .name, .namespace, .labels). + // +optional + ByLabel map[string]string `json:"byLabel,omitempty"` +} + +// RelatedResourceWatchByOwner configures reverse lookup via OwnerReferences. +type RelatedResourceWatchByOwner struct { + // Kind is the Kind to look for in the OwnerReferences of the changed related object. + Kind string `json:"kind"` } // RelatedResourceProjection describes how the source GVK of a related resource (i.e. diff --git a/sdk/apis/syncagent/v1alpha1/zz_generated.deepcopy.go b/sdk/apis/syncagent/v1alpha1/zz_generated.deepcopy.go index 61724a3..a69d656 100644 --- a/sdk/apis/syncagent/v1alpha1/zz_generated.deepcopy.go +++ b/sdk/apis/syncagent/v1alpha1/zz_generated.deepcopy.go @@ -304,6 +304,11 @@ func (in *RelatedResourceSpec) DeepCopyInto(out *RelatedResourceSpec) { *out = new(ResourceMutationSpec) (*in).DeepCopyInto(*out) } + if in.Watch != nil { + in, out := &in.Watch, &out.Watch + *out = new(RelatedResourceWatch) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelatedResourceSpec. @@ -316,6 +321,48 @@ func (in *RelatedResourceSpec) DeepCopy() *RelatedResourceSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RelatedResourceWatch) DeepCopyInto(out *RelatedResourceWatch) { + *out = *in + if in.ByOwner != nil { + in, out := &in.ByOwner, &out.ByOwner + *out = new(RelatedResourceWatchByOwner) + **out = **in + } + if in.ByLabel != nil { + in, out := &in.ByLabel, &out.ByLabel + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelatedResourceWatch. +func (in *RelatedResourceWatch) DeepCopy() *RelatedResourceWatch { + if in == nil { + return nil + } + out := new(RelatedResourceWatch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RelatedResourceWatchByOwner) DeepCopyInto(out *RelatedResourceWatchByOwner) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RelatedResourceWatchByOwner. +func (in *RelatedResourceWatchByOwner) DeepCopy() *RelatedResourceWatchByOwner { + if in == nil { + return nil + } + out := new(RelatedResourceWatchByOwner) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceCELMutation) DeepCopyInto(out *ResourceCELMutation) { *out = *in diff --git a/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcespec.go b/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcespec.go index 5496c40..68e1ef7 100644 --- a/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcespec.go +++ b/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcespec.go @@ -35,6 +35,7 @@ type RelatedResourceSpecApplyConfiguration struct { Projection *RelatedResourceProjectionApplyConfiguration `json:"projection,omitempty"` Object *RelatedResourceObjectApplyConfiguration `json:"object,omitempty"` Mutation *ResourceMutationSpecApplyConfiguration `json:"mutation,omitempty"` + Watch *RelatedResourceWatchApplyConfiguration `json:"watch,omitempty"` } // RelatedResourceSpecApplyConfiguration constructs a declarative configuration of the RelatedResourceSpec type for use with @@ -122,3 +123,11 @@ func (b *RelatedResourceSpecApplyConfiguration) WithMutation(value *ResourceMuta b.Mutation = value return b } + +// WithWatch sets the Watch field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Watch field is set to the value of the last call. +func (b *RelatedResourceSpecApplyConfiguration) WithWatch(value *RelatedResourceWatchApplyConfiguration) *RelatedResourceSpecApplyConfiguration { + b.Watch = value + return b +} diff --git a/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcewatch.go b/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcewatch.go new file mode 100644 index 0000000..443a515 --- /dev/null +++ b/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcewatch.go @@ -0,0 +1,54 @@ +/* +Copyright The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen-v0.33. DO NOT EDIT. + +package v1alpha1 + +// RelatedResourceWatchApplyConfiguration represents a declarative configuration of the RelatedResourceWatch type for use +// with apply. +type RelatedResourceWatchApplyConfiguration struct { + ByOwner *RelatedResourceWatchByOwnerApplyConfiguration `json:"byOwner,omitempty"` + ByLabel map[string]string `json:"byLabel,omitempty"` +} + +// RelatedResourceWatchApplyConfiguration constructs a declarative configuration of the RelatedResourceWatch type for use with +// apply. +func RelatedResourceWatch() *RelatedResourceWatchApplyConfiguration { + return &RelatedResourceWatchApplyConfiguration{} +} + +// WithByOwner sets the ByOwner field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ByOwner field is set to the value of the last call. +func (b *RelatedResourceWatchApplyConfiguration) WithByOwner(value *RelatedResourceWatchByOwnerApplyConfiguration) *RelatedResourceWatchApplyConfiguration { + b.ByOwner = value + return b +} + +// WithByLabel puts the entries into the ByLabel field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the ByLabel field, +// overwriting an existing map entries in ByLabel field with the same key. +func (b *RelatedResourceWatchApplyConfiguration) WithByLabel(entries map[string]string) *RelatedResourceWatchApplyConfiguration { + if b.ByLabel == nil && len(entries) > 0 { + b.ByLabel = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ByLabel[k] = v + } + return b +} diff --git a/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcewatchbyowner.go b/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcewatchbyowner.go new file mode 100644 index 0000000..7700892 --- /dev/null +++ b/sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcewatchbyowner.go @@ -0,0 +1,39 @@ +/* +Copyright The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen-v0.33. DO NOT EDIT. + +package v1alpha1 + +// RelatedResourceWatchByOwnerApplyConfiguration represents a declarative configuration of the RelatedResourceWatchByOwner type for use +// with apply. +type RelatedResourceWatchByOwnerApplyConfiguration struct { + Kind *string `json:"kind,omitempty"` +} + +// RelatedResourceWatchByOwnerApplyConfiguration constructs a declarative configuration of the RelatedResourceWatchByOwner type for use with +// apply. +func RelatedResourceWatchByOwner() *RelatedResourceWatchByOwnerApplyConfiguration { + return &RelatedResourceWatchByOwnerApplyConfiguration{} +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *RelatedResourceWatchByOwnerApplyConfiguration) WithKind(value string) *RelatedResourceWatchByOwnerApplyConfiguration { + b.Kind = &value + return b +} diff --git a/sdk/applyconfiguration/utils.go b/sdk/applyconfiguration/utils.go index 57601ad..55b573a 100644 --- a/sdk/applyconfiguration/utils.go +++ b/sdk/applyconfiguration/utils.go @@ -55,6 +55,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &syncagentv1alpha1.RelatedResourceSelectorRewriteApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("RelatedResourceSpec"): return &syncagentv1alpha1.RelatedResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RelatedResourceWatch"): + return &syncagentv1alpha1.RelatedResourceWatchApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RelatedResourceWatchByOwner"): + return &syncagentv1alpha1.RelatedResourceWatchByOwnerApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ResourceCELMutation"): return &syncagentv1alpha1.ResourceCELMutationApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ResourceDeleteMutation"):