diff --git a/internal/operator-controller/applier/boxcutter.go b/internal/operator-controller/applier/boxcutter.go index cb4da7e53..8f2aa28f0 100644 --- a/internal/operator-controller/applier/boxcutter.go +++ b/internal/operator-controller/applier/boxcutter.go @@ -68,13 +68,24 @@ func (r *SimpleRevisionGenerator) GenerateRevisionFromHelmRelease( if err := yaml.Unmarshal([]byte(doc), &obj); err != nil { return nil, err } - obj.SetLabels(mergeLabelMaps(obj.GetLabels(), objectLabels)) + obj.SetLabels(mergeStringMaps(obj.GetLabels(), objectLabels)) // Memory optimization: strip large annotations // Note: ApplyStripTransform never returns an error in practice _ = cache.ApplyStripAnnotationsTransform(&obj) sanitizedUnstructured(ctx, &obj) + annotationUpdates := map[string]string{} + if v := helmRelease.Labels[labels.BundleVersionKey]; v != "" { + annotationUpdates[labels.BundleVersionKey] = v + } + if v := helmRelease.Labels[labels.PackageNameKey]; v != "" { + annotationUpdates[labels.PackageNameKey] = v + } + if len(annotationUpdates) > 0 { + obj.SetAnnotations(mergeStringMaps(obj.GetAnnotations(), annotationUpdates)) + } + objs = append(objs, *ocv1ac.ClusterExtensionRevisionObject(). WithObject(obj)) } @@ -126,7 +137,7 @@ func (r *SimpleRevisionGenerator) GenerateRevision( // objectLabels objs := make([]ocv1ac.ClusterExtensionRevisionObjectApplyConfiguration, 0, len(plain)) for _, obj := range plain { - obj.SetLabels(mergeLabelMaps(obj.GetLabels(), objectLabels)) + obj.SetLabels(mergeStringMaps(obj.GetLabels(), objectLabels)) gvk, err := apiutil.GVKForObject(obj, r.Scheme) if err != nil { @@ -146,6 +157,17 @@ func (r *SimpleRevisionGenerator) GenerateRevision( } sanitizedUnstructured(ctx, &unstr) + annotationUpdates := map[string]string{} + if v := revisionAnnotations[labels.BundleVersionKey]; v != "" { + annotationUpdates[labels.BundleVersionKey] = v + } + if v := revisionAnnotations[labels.PackageNameKey]; v != "" { + annotationUpdates[labels.PackageNameKey] = v + } + if len(annotationUpdates) > 0 { + unstr.SetAnnotations(mergeStringMaps(unstr.GetAnnotations(), annotationUpdates)) + } + objs = append(objs, *ocv1ac.ClusterExtensionRevisionObject(). WithObject(unstr)) } @@ -671,9 +693,9 @@ func revisionManagementPerms(rev *ocv1ac.ClusterExtensionRevisionApplyConfigurat } } -func mergeLabelMaps(m1, m2 map[string]string) map[string]string { - mergedLabels := make(map[string]string, len(m1)+len(m2)) - maps.Copy(mergedLabels, m1) - maps.Copy(mergedLabels, m2) - return mergedLabels +func mergeStringMaps(m1, m2 map[string]string) map[string]string { + merged := make(map[string]string, len(m1)+len(m2)) + maps.Copy(merged, m1) + maps.Copy(merged, m2) + return merged } diff --git a/internal/operator-controller/applier/boxcutter_test.go b/internal/operator-controller/applier/boxcutter_test.go index 4f8461250..f1bc56904 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", + }, }, }, }), @@ -199,7 +207,10 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { }, } - rev, err := b.GenerateRevision(t.Context(), dummyBundle, ext, map[string]string{}, map[string]string{}) + rev, err := b.GenerateRevision(t.Context(), dummyBundle, ext, map[string]string{}, map[string]string{ + labels.BundleVersionKey: "1.0.0", + labels.PackageNameKey: "test-package", + }) require.NoError(t, err) t.Log("by checking the olm.operatorframework.io/owner-name and owner-kind labels are set") @@ -223,6 +234,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": "1.0.0", + "olm.operatorframework.io/package-name": "test-package", + }, }, "spec": map[string]interface{}{}, }, @@ -244,6 +259,8 @@ func Test_SimpleRevisionGenerator_GenerateRevision(t *testing.T) { }, "annotations": map[string]interface{}{ "my-annotation": "my-annotation-value", + "olm.operatorframework.io/bundle-version": "1.0.0", + "olm.operatorframework.io/package-name": "test-package", }, }, "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