From 43d5ef8fa9878f1b8f5753e2e59998913001e865 Mon Sep 17 00:00:00 2001 From: rafal-jan Date: Mon, 20 Jul 2026 22:26:50 +0200 Subject: [PATCH 1/2] feat: add TransformStripCRDSchema for reduced cache memory usage Signed-off-by: rafal-jan --- pkg/reconciler/customresourcesgate/cache.go | 58 ++++ .../customresourcesgate/cache_test.go | 250 ++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 pkg/reconciler/customresourcesgate/cache.go create mode 100644 pkg/reconciler/customresourcesgate/cache_test.go diff --git a/pkg/reconciler/customresourcesgate/cache.go b/pkg/reconciler/customresourcesgate/cache.go new file mode 100644 index 000000000..9c583902f --- /dev/null +++ b/pkg/reconciler/customresourcesgate/cache.go @@ -0,0 +1,58 @@ +/* +Copyright 2025 The Crossplane 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 customresourcesgate + +import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" +) + +// TransformStripCRDSchema is a cache.TransformFunc that removes heavy fields +// from CustomResourceDefinition objects before they are stored in the informer +// cache. It strips: +// - Spec.Versions[].Schema (OpenAPI v3 validation schemas) +// - ObjectMeta.ManagedFields +// - The "kubectl.kubernetes.io/last-applied-configuration" annotation +// +// This significantly reduces memory usage in clusters with many CRDs. The CRD +// gate reconciler only needs basic metadata (group, kind, version names, served +// status) and status conditions to function correctly. +// +// Usage: +// +// cache.Options{ +// ByObject: map[client.Object]cache.ByObject{ +// &apiextensionsv1.CustomResourceDefinition{}: { +// Transform: customresourcesgate.TransformStripCRDSchema, +// }, +// }, +// } +func TransformStripCRDSchema(obj any) (any, error) { + crd, ok := obj.(*apiextensionsv1.CustomResourceDefinition) + if !ok { + return obj, nil + } + + for i := range crd.Spec.Versions { + crd.Spec.Versions[i].Schema = nil + } + + crd.ManagedFields = nil + + delete(crd.Annotations, "kubectl.kubernetes.io/last-applied-configuration") + + return crd, nil +} diff --git a/pkg/reconciler/customresourcesgate/cache_test.go b/pkg/reconciler/customresourcesgate/cache_test.go new file mode 100644 index 000000000..ff9c3e59e --- /dev/null +++ b/pkg/reconciler/customresourcesgate/cache_test.go @@ -0,0 +1,250 @@ +/* +Copyright 2025 The Crossplane 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 customresourcesgate + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestTransformStripCRDSchema(t *testing.T) { + type args struct { + obj any + } + + type want struct { + obj any + err error + } + + cases := map[string]struct { + reason string + args args + want want + }{ + "StripsSchemaManagedFieldsAndAnnotation": { + reason: "Should strip OpenAPI schemas, ManagedFields, and last-applied-configuration annotation", + args: args{ + obj: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testresources.example.com", + Annotations: map[string]string{ + "kubectl.kubernetes.io/last-applied-configuration": `{"very":"large","json":"blob"}`, + "other-annotation": "keep-me", + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: "kubectl", Operation: metav1.ManagedFieldsOperationApply}, + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + Properties: map[string]apiextensionsv1.JSONSchemaProps{ + "spec": {Type: "object"}, + }, + }, + }, + }, + }, + }, + Status: apiextensionsv1.CustomResourceDefinitionStatus{ + Conditions: []apiextensionsv1.CustomResourceDefinitionCondition{ + { + Type: apiextensionsv1.Established, + Status: apiextensionsv1.ConditionTrue, + }, + }, + }, + }, + }, + want: want{ + obj: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testresources.example.com", + Annotations: map[string]string{ + "other-annotation": "keep-me", + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Schema: nil, + }, + }, + }, + Status: apiextensionsv1.CustomResourceDefinitionStatus{ + Conditions: []apiextensionsv1.CustomResourceDefinitionCondition{ + { + Type: apiextensionsv1.Established, + Status: apiextensionsv1.ConditionTrue, + }, + }, + }, + }, + }, + }, + "MultipleVersions": { + reason: "Should strip schemas from all versions", + args: args{ + obj: &apiextensionsv1.CustomResourceDefinition{ + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{Type: "object"}, + }, + }, + { + Name: "v1beta1", + Served: false, + Schema: &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{Type: "object"}, + }, + }, + }, + }, + }, + }, + want: want{ + obj: &apiextensionsv1.CustomResourceDefinition{ + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + {Name: "v1", Served: true, Schema: nil}, + {Name: "v1beta1", Served: false, Schema: nil}, + }, + }, + }, + }, + }, + "NoVersions": { + reason: "Should handle CRD with no versions without panicking", + args: args{ + obj: &apiextensionsv1.CustomResourceDefinition{ + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + }, + }, + }, + want: want{ + obj: &apiextensionsv1.CustomResourceDefinition{ + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + }, + }, + }, + }, + "NilAnnotations": { + reason: "Should handle CRD with nil annotations map without panicking", + args: args{ + obj: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test.example.com", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + }, + }, + }, + want: want{ + obj: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test.example.com", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + }, + }, + }, + }, + "NoLastAppliedAnnotation": { + reason: "Should leave other annotations intact when last-applied-configuration is absent", + args: args{ + obj: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "custom-annotation": "value", + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + }, + }, + }, + want: want{ + obj: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "custom-annotation": "value", + }, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{Kind: "TestResource"}, + }, + }, + }, + }, + "NonCRDObject": { + reason: "Should return non-CRD objects unchanged", + args: args{ + obj: "not-a-crd", + }, + want: want{ + obj: "not-a-crd", + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got, err := TransformStripCRDSchema(tc.args.obj) + + if diff := cmp.Diff(tc.want.err, err); diff != "" { + t.Errorf("%s\nTransformStripCRDSchema(...): -want error, +got error:\n%s", tc.reason, diff) + } + + if diff := cmp.Diff(tc.want.obj, got); diff != "" { + t.Errorf("%s\nTransformStripCRDSchema(...): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} From ddc11c920b20d7b75065afbd110d9fd957c788ba Mon Sep 17 00:00:00 2001 From: rafal-jan Date: Mon, 20 Jul 2026 23:47:42 +0200 Subject: [PATCH 2/2] test(customresourcesgate): equate errors in TransformStripCRDSchema error comparison Signed-off-by: rafal-jan --- pkg/reconciler/customresourcesgate/cache_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/reconciler/customresourcesgate/cache_test.go b/pkg/reconciler/customresourcesgate/cache_test.go index ff9c3e59e..e0e44cbfd 100644 --- a/pkg/reconciler/customresourcesgate/cache_test.go +++ b/pkg/reconciler/customresourcesgate/cache_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -238,7 +239,7 @@ func TestTransformStripCRDSchema(t *testing.T) { t.Run(name, func(t *testing.T) { got, err := TransformStripCRDSchema(tc.args.obj) - if diff := cmp.Diff(tc.want.err, err); diff != "" { + if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" { t.Errorf("%s\nTransformStripCRDSchema(...): -want error, +got error:\n%s", tc.reason, diff) }