From dff77b73eff758dcc705122c1341692dd597f2e6 Mon Sep 17 00:00:00 2001 From: Daniel Franz Date: Fri, 7 Nov 2025 15:45:26 +0900 Subject: [PATCH 1/2] Removes read-only or otherwise ill-advised fields from the manifests applied by revisions. Assisted-by: Gemini+Claude Signed-off-by: Daniel Franz --- .../operator-controller/applier/boxcutter.go | 32 ++++++++++- .../applier/boxcutter_test.go | 55 ++++++++++++++++--- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/internal/operator-controller/applier/boxcutter.go b/internal/operator-controller/applier/boxcutter.go index 32279bdf69..5fd4662479 100644 --- a/internal/operator-controller/applier/boxcutter.go +++ b/internal/operator-controller/applier/boxcutter.go @@ -64,7 +64,7 @@ func (r *SimpleRevisionGenerator) GenerateRevisionFromHelmRelease( maps.Copy(labels, existingLabels) maps.Copy(labels, objectLabels) obj.SetLabels(labels) - obj.SetOwnerReferences(nil) // reset OwnerReferences for migration. + sanitize(&obj) // Memory optimization: strip large annotations // Note: ApplyStripTransform never returns an error in practice @@ -123,6 +123,7 @@ func (r *SimpleRevisionGenerator) GenerateRevision( return nil, err } + sanitize(&unstr) objs = append(objs, ocv1.ClusterExtensionRevisionObject{ Object: unstr, }) @@ -135,6 +136,35 @@ func (r *SimpleRevisionGenerator) GenerateRevision( return r.buildClusterExtensionRevision(objs, ext, revisionAnnotations), nil } +func sanitize(obj *unstructured.Unstructured) { + m := obj.Object + + // Remove status (top-level) + delete(m, "status") + + // Remove common metadata fields set by the API server + if metaRaw, ok := m["metadata"]; ok { + if meta, ok := metaRaw.(map[string]any); ok { + delete(meta, "finalizers") + delete(meta, "ownerReferences") + delete(meta, "creationTimestamp") + delete(meta, "uid") + delete(meta, "resourceVersion") + delete(meta, "generation") + delete(meta, "managedFields") + delete(meta, "deletionTimestamp") + delete(meta, "deletionGracePeriodSeconds") + + // remove kubectl last-applied annotation + if annRaw, ok := meta["annotations"]; ok { + if ann, ok := annRaw.(map[string]any); ok { + delete(ann, "kubectl.kubernetes.io/last-applied-configuration") + } + } + } + } +} + func (r *SimpleRevisionGenerator) buildClusterExtensionRevision( objects []ocv1.ClusterExtensionRevisionObject, ext *ocv1.ClusterExtension, diff --git a/internal/operator-controller/applier/boxcutter_test.go b/internal/operator-controller/applier/boxcutter_test.go index 9da1ddb4a0..8214ffba53 100644 --- a/internal/operator-controller/applier/boxcutter_test.go +++ b/internal/operator-controller/applier/boxcutter_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io/fs" + "strings" "testing" "testing/fstest" @@ -36,8 +37,29 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) g := &applier.SimpleRevisionGenerator{} helmRelease := &release.Release{ - Name: "test-123", - Manifest: `{"apiVersion":"v1","kind":"ConfigMap"}` + "\n" + `{"apiVersion":"v1","kind":"Secret"}` + "\n", + Name: "test-123", + Manifest: strings.Join(strings.Fields(` + { + "apiVersion":"v1", + "kind":"ConfigMap", + "metadata":{ + "finalizers":["test"], + "ownerReferences":[{"kind":"TestOwner"}], + "creationTimestamp":{"time":"0"}, + "uid":"1a2b3c4d", + "resourceVersion":"12345", + "generation":123, + "managedFields":[{"manager":"test-manager"}], + "deletionTimestamp":{"time":"0"}, + "deletionGracePeriodSeconds":30, + "annotations":{ + "do-not-delete-me":"please", + "kubectl.kubernetes.io/last-applied-configuration":"delete me" + }, + }, "status": { + "replicas": 3, + } + }`), "") + "\n" + `{"apiVersion":"v1","kind":"Secret"}` + "\n", Labels: map[string]string{ labels.BundleNameKey: "my-bundle", labels.PackageNameKey: "my-package", @@ -84,6 +106,9 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "do-not-delete-me": "please", + }, "labels": map[string]interface{}{ "my-label": "my-value", }, @@ -125,6 +150,23 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "test-deployment", + // Fields to be sanitized + Finalizers: []string{"test"}, + OwnerReferences: []metav1.OwnerReference{{Kind: "TestOwner"}}, + CreationTimestamp: metav1.Time{Time: metav1.Now().Time}, + UID: "1a2b3c4d", + ResourceVersion: "12345", + Generation: 123, + ManagedFields: []metav1.ManagedFieldsEntry{{Manager: "test-manager"}}, + DeletionTimestamp: &metav1.Time{Time: metav1.Now().Time}, + DeletionGracePeriodSeconds: func(i int64) *int64 { return &i }(30), + Annotations: map[string]string{ + // User-set annotations should not be touched + "do-not-delete-me": "please", + "kubectl.kubernetes.io/last-applied-configuration": "delete me", + }, + }, Status: appsv1.DeploymentStatus{ + Replicas: 3, }, }, }, nil @@ -149,8 +191,6 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { require.Equal(t, map[string]string{ controllers.ClusterExtensionRevisionOwnerLabel: "test-extension", }, rev.Labels) - t.Log("by checking there are no annotations") - require.Empty(t, rev.Annotations) t.Log("by checking the revision number is 0") require.Equal(t, int64(0), rev.Spec.Revision) t.Log("by checking the rendered objects are present in the correct phases") @@ -167,9 +207,6 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { "name": "test-service", }, "spec": map[string]interface{}{}, - "status": map[string]interface{}{ - "loadBalancer": map[string]interface{}{}, - }, }, }, }, @@ -180,6 +217,9 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { "kind": "Deployment", "metadata": map[string]interface{}{ "name": "test-deployment", + "annotations": map[string]interface{}{ + "do-not-delete-me": "please", + }, }, "spec": map[string]interface{}{ "selector": nil, @@ -191,7 +231,6 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { }, "strategy": map[string]interface{}{}, }, - "status": map[string]interface{}{}, }, }, }, From 4e4949043705647ffa4f31dd538fe7027dd82e73 Mon Sep 17 00:00:00 2001 From: Daniel Franz Date: Mon, 10 Nov 2025 16:32:43 +0900 Subject: [PATCH 2/2] Cleanup, add logging Signed-off-by: Daniel Franz --- .../operator-controller/applier/boxcutter.go | 77 +++++++++++-------- .../applier/boxcutter_test.go | 54 ++++++------- 2 files changed, 72 insertions(+), 59 deletions(-) diff --git a/internal/operator-controller/applier/boxcutter.go b/internal/operator-controller/applier/boxcutter.go index 5fd4662479..1914b80e8f 100644 --- a/internal/operator-controller/applier/boxcutter.go +++ b/internal/operator-controller/applier/boxcutter.go @@ -20,6 +20,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/yaml" helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" @@ -35,8 +36,9 @@ const ( ) type ClusterExtensionRevisionGenerator interface { - GenerateRevision(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) + GenerateRevision(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) GenerateRevisionFromHelmRelease( + ctx context.Context, helmRelease *release.Release, ext *ocv1.ClusterExtension, objectLabels map[string]string, ) (*ocv1.ClusterExtensionRevision, error) @@ -48,6 +50,7 @@ type SimpleRevisionGenerator struct { } func (r *SimpleRevisionGenerator) GenerateRevisionFromHelmRelease( + ctx context.Context, helmRelease *release.Release, ext *ocv1.ClusterExtension, objectLabels map[string]string, ) (*ocv1.ClusterExtensionRevision, error) { @@ -64,11 +67,11 @@ func (r *SimpleRevisionGenerator) GenerateRevisionFromHelmRelease( maps.Copy(labels, existingLabels) maps.Copy(labels, objectLabels) obj.SetLabels(labels) - sanitize(&obj) // Memory optimization: strip large annotations // Note: ApplyStripTransform never returns an error in practice _ = cache.ApplyStripAnnotationsTransform(&obj) + sanitizedUnstructured(ctx, &obj) objs = append(objs, ocv1.ClusterExtensionRevisionObject{ Object: obj, @@ -88,6 +91,7 @@ func (r *SimpleRevisionGenerator) GenerateRevisionFromHelmRelease( } func (r *SimpleRevisionGenerator) GenerateRevision( + ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string, ) (*ocv1.ClusterExtensionRevision, error) { @@ -122,8 +126,8 @@ func (r *SimpleRevisionGenerator) GenerateRevision( if err := cache.ApplyStripAnnotationsTransform(&unstr); err != nil { return nil, err } + sanitizedUnstructured(ctx, &unstr) - sanitize(&unstr) objs = append(objs, ocv1.ClusterExtensionRevisionObject{ Object: unstr, }) @@ -136,33 +140,46 @@ func (r *SimpleRevisionGenerator) GenerateRevision( return r.buildClusterExtensionRevision(objs, ext, revisionAnnotations), nil } -func sanitize(obj *unstructured.Unstructured) { - m := obj.Object - - // Remove status (top-level) - delete(m, "status") - - // Remove common metadata fields set by the API server - if metaRaw, ok := m["metadata"]; ok { - if meta, ok := metaRaw.(map[string]any); ok { - delete(meta, "finalizers") - delete(meta, "ownerReferences") - delete(meta, "creationTimestamp") - delete(meta, "uid") - delete(meta, "resourceVersion") - delete(meta, "generation") - delete(meta, "managedFields") - delete(meta, "deletionTimestamp") - delete(meta, "deletionGracePeriodSeconds") - - // remove kubectl last-applied annotation - if annRaw, ok := meta["annotations"]; ok { - if ann, ok := annRaw.(map[string]any); ok { - delete(ann, "kubectl.kubernetes.io/last-applied-configuration") - } - } +// sanitizedUnstructured takes an unstructured obj, removes status if present, and returns a sanitized copy containing only the allowed metadata entries set below. +// If any unallowed entries are removed, a warning will be logged. +func sanitizedUnstructured(ctx context.Context, unstr *unstructured.Unstructured) { + l := log.FromContext(ctx) + obj := unstr.Object + + // remove status + if _, ok := obj["status"]; ok { + l.Info("warning: extraneous status removed from manifest") + delete(obj, "status") + } + + var allowedMetadata = []string{ + "annotations", + "labels", + "name", + "namespace", + } + + var metadata map[string]any + if metaRaw, ok := obj["metadata"]; ok { + metadata, ok = metaRaw.(map[string]any) + if !ok { + return } + } else { + return + } + + metadataSanitized := map[string]any{} + for _, key := range allowedMetadata { + if val, ok := metadata[key]; ok { + metadataSanitized[key] = val + } + } + + if len(metadataSanitized) != len(metadata) { + l.Info("warning: extraneous values removed from manifest metadata", "allowed metadata", allowedMetadata) } + obj["metadata"] = metadataSanitized } func (r *SimpleRevisionGenerator) buildClusterExtensionRevision( @@ -220,7 +237,7 @@ func (m *BoxcutterStorageMigrator) Migrate(ctx context.Context, ext *ocv1.Cluste return err } - rev, err := m.RevisionGenerator.GenerateRevisionFromHelmRelease(helmRelease, ext, objectLabels) + rev, err := m.RevisionGenerator.GenerateRevisionFromHelmRelease(ctx, helmRelease, ext, objectLabels) if err != nil { return err } @@ -266,7 +283,7 @@ func (bc *Boxcutter) createOrUpdate(ctx context.Context, obj client.Object) erro func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (bool, string, error) { // Generate desired revision - desiredRevision, err := bc.RevisionGenerator.GenerateRevision(contentFS, ext, objectLabels, revisionAnnotations) + desiredRevision, err := bc.RevisionGenerator.GenerateRevision(ctx, contentFS, ext, objectLabels, revisionAnnotations) if err != nil { return false, "", err } diff --git a/internal/operator-controller/applier/boxcutter_test.go b/internal/operator-controller/applier/boxcutter_test.go index 8214ffba53..ad30bf2c13 100644 --- a/internal/operator-controller/applier/boxcutter_test.go +++ b/internal/operator-controller/applier/boxcutter_test.go @@ -52,10 +52,6 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) "managedFields":[{"manager":"test-manager"}], "deletionTimestamp":{"time":"0"}, "deletionGracePeriodSeconds":30, - "annotations":{ - "do-not-delete-me":"please", - "kubectl.kubernetes.io/last-applied-configuration":"delete me" - }, }, "status": { "replicas": 3, } @@ -78,7 +74,7 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) "my-label": "my-value", } - rev, err := g.GenerateRevisionFromHelmRelease(helmRelease, ext, objectLabels) + rev, err := g.GenerateRevisionFromHelmRelease(t.Context(), helmRelease, ext, objectLabels) require.NoError(t, err) assert.Equal(t, &ocv1.ClusterExtensionRevision{ @@ -106,9 +102,6 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]interface{}{ - "annotations": map[string]interface{}{ - "do-not-delete-me": "please", - }, "labels": map[string]interface{}{ "my-label": "my-value", }, @@ -149,7 +142,10 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { }, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-deployment", + Name: "test-deployment", + Namespace: "test-ns", + Labels: map[string]string{"my-label": "my-label-value"}, + Annotations: map[string]string{"my-annotation": "my-annotation-value"}, // Fields to be sanitized Finalizers: []string{"test"}, OwnerReferences: []metav1.OwnerReference{{Kind: "TestOwner"}}, @@ -160,11 +156,6 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{{Manager: "test-manager"}}, DeletionTimestamp: &metav1.Time{Time: metav1.Now().Time}, DeletionGracePeriodSeconds: func(i int64) *int64 { return &i }(30), - Annotations: map[string]string{ - // User-set annotations should not be touched - "do-not-delete-me": "please", - "kubectl.kubernetes.io/last-applied-configuration": "delete me", - }, }, Status: appsv1.DeploymentStatus{ Replicas: 3, }, @@ -184,7 +175,7 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { }, } - rev, err := b.GenerateRevision(fstest.MapFS{}, ext, map[string]string{}, map[string]string{}) + rev, err := b.GenerateRevision(t.Context(), fstest.MapFS{}, ext, map[string]string{}, map[string]string{}) require.NoError(t, err) t.Log("by checking the olm.operatorframework.io/owner label is set to the name of the ClusterExtension") @@ -216,9 +207,13 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{ - "name": "test-deployment", + "name": "test-deployment", + "namespace": "test-ns", + "labels": map[string]interface{}{ + "my-label": "my-label-value", + }, "annotations": map[string]interface{}{ - "do-not-delete-me": "please", + "my-annotation": "my-annotation-value", }, }, "spec": map[string]interface{}{ @@ -259,7 +254,7 @@ func Test_SimpleRevisionGenerator_Renderer_Integration(t *testing.T) { ManifestProvider: r, } - _, err := b.GenerateRevision(bundleFS, ext, map[string]string{}, map[string]string{}) + _, err := b.GenerateRevision(t.Context(), bundleFS, ext, map[string]string{}, map[string]string{}) require.NoError(t, err) } @@ -297,7 +292,7 @@ func Test_SimpleRevisionGenerator_AppliesObjectLabelsAndRevisionAnnotations(t *t "other": "value", } - rev, err := b.GenerateRevision(fstest.MapFS{}, &ocv1.ClusterExtension{}, map[string]string{ + rev, err := b.GenerateRevision(t.Context(), fstest.MapFS{}, &ocv1.ClusterExtension{}, map[string]string{ "some": "value", }, revAnnotations) require.NoError(t, err) @@ -325,7 +320,7 @@ func Test_SimpleRevisionGenerator_Failure(t *testing.T) { ManifestProvider: r, } - rev, err := b.GenerateRevision(fstest.MapFS{}, &ocv1.ClusterExtension{}, map[string]string{}, map[string]string{}) + rev, err := b.GenerateRevision(t.Context(), fstest.MapFS{}, &ocv1.ClusterExtension{}, map[string]string{}, map[string]string{}) require.Nil(t, rev) t.Log("by checking rendering errors are propagated") require.Error(t, err) @@ -402,7 +397,7 @@ func TestBoxcutter_Apply(t *testing.T) { { name: "first revision", mockBuilder: &mockBundleRevisionBuilder{ - makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + makeRevisionFunc: func(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { return &ocv1.ClusterExtensionRevision{ ObjectMeta: metav1.ObjectMeta{ Annotations: revisionAnnotations, @@ -450,7 +445,7 @@ func TestBoxcutter_Apply(t *testing.T) { { name: "no change, revision exists", mockBuilder: &mockBundleRevisionBuilder{ - makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + makeRevisionFunc: func(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { return &ocv1.ClusterExtensionRevision{ ObjectMeta: metav1.ObjectMeta{ Annotations: revisionAnnotations, @@ -496,7 +491,7 @@ func TestBoxcutter_Apply(t *testing.T) { { name: "new revision created when objects in new revision are different", mockBuilder: &mockBundleRevisionBuilder{ - makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + makeRevisionFunc: func(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { return &ocv1.ClusterExtensionRevision{ ObjectMeta: metav1.ObjectMeta{ Annotations: revisionAnnotations, @@ -557,7 +552,7 @@ func TestBoxcutter_Apply(t *testing.T) { { name: "error from GenerateRevision", mockBuilder: &mockBundleRevisionBuilder{ - makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + makeRevisionFunc: func(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { return nil, errors.New("render boom") }, }, @@ -573,7 +568,7 @@ func TestBoxcutter_Apply(t *testing.T) { { name: "keep at most 5 past revisions", mockBuilder: &mockBundleRevisionBuilder{ - makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + makeRevisionFunc: func(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { return &ocv1.ClusterExtensionRevision{ ObjectMeta: metav1.ObjectMeta{ Annotations: revisionAnnotations, @@ -675,7 +670,7 @@ func TestBoxcutter_Apply(t *testing.T) { { name: "keep active revisions when they are out of limit", mockBuilder: &mockBundleRevisionBuilder{ - makeRevisionFunc: func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + makeRevisionFunc: func(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { return &ocv1.ClusterExtensionRevision{ ObjectMeta: metav1.ObjectMeta{ Annotations: revisionAnnotations, @@ -933,14 +928,15 @@ func TestBoxcutterStorageMigrator(t *testing.T) { // mockBundleRevisionBuilder is a mock implementation of the ClusterExtensionRevisionGenerator for testing. type mockBundleRevisionBuilder struct { - makeRevisionFunc func(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotation map[string]string) (*ocv1.ClusterExtensionRevision, error) + makeRevisionFunc func(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotation map[string]string) (*ocv1.ClusterExtensionRevision, error) } -func (m *mockBundleRevisionBuilder) GenerateRevision(bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { - return m.makeRevisionFunc(bundleFS, ext, objectLabels, revisionAnnotations) +func (m *mockBundleRevisionBuilder) GenerateRevision(ctx context.Context, bundleFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (*ocv1.ClusterExtensionRevision, error) { + return m.makeRevisionFunc(ctx, bundleFS, ext, objectLabels, revisionAnnotations) } func (m *mockBundleRevisionBuilder) GenerateRevisionFromHelmRelease( + ctx context.Context, helmRelease *release.Release, ext *ocv1.ClusterExtension, objectLabels map[string]string, ) (*ocv1.ClusterExtensionRevision, error) {