Skip to content
Merged
Show file tree
Hide file tree
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
58 changes: 58 additions & 0 deletions pkg/reconciler/customresourcesgate/cache.go
Original file line number Diff line number Diff line change
@@ -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
}
251 changes: 251 additions & 0 deletions pkg/reconciler/customresourcesgate/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
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"
"github.com/google/go-cmp/cmp/cmpopts"
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, cmpopts.EquateErrors()); 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)
}
})
}
}
Loading