From 8eb6722432f4f73da3aa55df93d1cf56c6a6b6a9 Mon Sep 17 00:00:00 2001 From: Predrag Knezevic Date: Fri, 20 Mar 2026 14:47:24 +0100 Subject: [PATCH] Add bundle-version and package-name annotations to CER phase objects When upgrading between bundle versions that produce identical Kubernetes manifests, the installed version status was not updated because CER phases were identical across versions, causing in-place patches instead of new revision creation. Propagate bundle-version and package-name annotations onto each rendered object within CER phases so that different bundle versions always produce distinct phases, triggering new revision creation via phase immutability. As a side benefit, every applied bundle resource now carries two annotations that immediately tell an observer which package and version it belongs to. Co-Authored-By: Claude Opus 4.6 --- .../operator-controller/applier/boxcutter.go | 10 +++++++ .../applier/boxcutter_test.go | 14 +++++++++ test/e2e/features/update.feature | 30 +++++++++++++++++++ .../test-catalog/v1/configs/catalog.yaml | 14 +++++++++ 4 files changed, 68 insertions(+) diff --git a/internal/operator-controller/applier/boxcutter.go b/internal/operator-controller/applier/boxcutter.go index cb4da7e53..608cb6fee 100644 --- a/internal/operator-controller/applier/boxcutter.go +++ b/internal/operator-controller/applier/boxcutter.go @@ -75,6 +75,11 @@ func (r *SimpleRevisionGenerator) GenerateRevisionFromHelmRelease( _ = cache.ApplyStripAnnotationsTransform(&obj) sanitizedUnstructured(ctx, &obj) + obj.SetAnnotations(mergeLabelMaps(obj.GetAnnotations(), map[string]string{ + labels.BundleVersionKey: helmRelease.Labels[labels.BundleVersionKey], + labels.PackageNameKey: helmRelease.Labels[labels.PackageNameKey], + })) + objs = append(objs, *ocv1ac.ClusterExtensionRevisionObject(). WithObject(obj)) } @@ -146,6 +151,11 @@ func (r *SimpleRevisionGenerator) GenerateRevision( } sanitizedUnstructured(ctx, &unstr) + unstr.SetAnnotations(mergeLabelMaps(unstr.GetAnnotations(), map[string]string{ + labels.BundleVersionKey: revisionAnnotations[labels.BundleVersionKey], + labels.PackageNameKey: revisionAnnotations[labels.PackageNameKey], + })) + objs = append(objs, *ocv1ac.ClusterExtensionRevisionObject(). WithObject(unstr)) } diff --git a/internal/operator-controller/applier/boxcutter_test.go b/internal/operator-controller/applier/boxcutter_test.go index 4f8461250..44cd7f7f6 100644 --- a/internal/operator-controller/applier/boxcutter_test.go +++ b/internal/operator-controller/applier/boxcutter_test.go @@ -128,6 +128,10 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) "labels": map[string]interface{}{ "my-label": "my-value", }, + "annotations": map[string]interface{}{ + "olm.operatorframework.io/bundle-version": "1.2.0", + "olm.operatorframework.io/package-name": "my-package", + }, }, }, }), @@ -140,6 +144,10 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) "labels": map[string]interface{}{ "my-label": "my-value", }, + "annotations": map[string]interface{}{ + "olm.operatorframework.io/bundle-version": "1.2.0", + "olm.operatorframework.io/package-name": "my-package", + }, }, }, }), @@ -223,6 +231,10 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { "kind": "Service", "metadata": map[string]interface{}{ "name": "test-service", + "annotations": map[string]interface{}{ + "olm.operatorframework.io/bundle-version": "", + "olm.operatorframework.io/package-name": "", + }, }, "spec": map[string]interface{}{}, }, @@ -244,6 +256,8 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { }, "annotations": map[string]interface{}{ "my-annotation": "my-annotation-value", + "olm.operatorframework.io/bundle-version": "", + "olm.operatorframework.io/package-name": "", }, }, "spec": map[string]interface{}{ diff --git a/test/e2e/features/update.feature b/test/e2e/features/update.feature index e1b4becca..590ecf126 100644 --- a/test/e2e/features/update.feature +++ b/test/e2e/features/update.feature @@ -180,6 +180,36 @@ Feature: Update ClusterExtension When ClusterCatalog "test" image version "v2" is also tagged as "latest" Then bundle "test-operator.1.3.0" is installed in version "1.3.0" + @BoxcutterRuntime + Scenario: Update to a version with identical bundle content creates a new revision + Given ClusterExtension is applied + """ + apiVersion: olm.operatorframework.io/v1 + kind: ClusterExtension + metadata: + name: ${NAME} + spec: + namespace: ${TEST_NAMESPACE} + serviceAccount: + name: olm-sa + source: + sourceType: Catalog + catalog: + packageName: test + selector: + matchLabels: + "olm.operatorframework.io/metadata.name": test-catalog + version: 1.0.0 + upgradeConstraintPolicy: SelfCertified + """ + And ClusterExtension is rolled out + And ClusterExtension is available + And bundle "test-operator.1.0.0" is installed in version "1.0.0" + When ClusterExtension is updated to version "1.0.4" + Then ClusterExtension is rolled out + And ClusterExtension is available + And bundle "test-operator.1.0.4" is installed in version "1.0.4" + @BoxcutterRuntime Scenario: Each update creates a new revision and resources not present in the new revision are removed from the cluster Given ClusterExtension is applied diff --git a/testdata/images/catalogs/test-catalog/v1/configs/catalog.yaml b/testdata/images/catalogs/test-catalog/v1/configs/catalog.yaml index 012afbe83..8272dd68c 100644 --- a/testdata/images/catalogs/test-catalog/v1/configs/catalog.yaml +++ b/testdata/images/catalogs/test-catalog/v1/configs/catalog.yaml @@ -17,6 +17,7 @@ entries: - name: test-operator.1.0.0 - name: test-operator.1.0.1 replaces: test-operator.1.0.0 + - name: test-operator.1.0.4 - name: test-operator.1.2.0 replaces: test-operator.1.0.1 --- @@ -40,6 +41,19 @@ properties: packageName: test version: 1.0.1 --- +# Bundle with identical rendered content as v1.0.0 (same image). +# Used to test that upgrading between versions with identical manifests +# correctly updates the installed version status (OCPBUGS-78311). +schema: olm.bundle +name: test-operator.1.0.4 +package: test +image: docker-registry.operator-controller-e2e.svc.cluster.local:5000/bundles/registry-v1/test-operator:v1.0.0 +properties: + - type: olm.package + value: + packageName: test + version: 1.0.4 +--- # Bundle with a wrong image ref causing image pull failure schema: olm.bundle name: test-operator.1.0.2