-
Notifications
You must be signed in to change notification settings - Fork 73
✨ Support explicit pkg.Release field with build metadata fallback for bundle comparison #2543
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ import ( | |
| "github.com/operator-framework/operator-registry/alpha/property" | ||
|
|
||
| "github.com/operator-framework/operator-controller/internal/operator-controller/catalogmetadata/compare" | ||
| "github.com/operator-framework/operator-controller/internal/operator-controller/features" | ||
| ) | ||
|
|
||
| func TestNewVersionRange(t *testing.T) { | ||
|
|
@@ -138,13 +139,13 @@ func TestByVersionAndRelease(t *testing.T) { | |
| t.Run("all bundles valid", func(t *testing.T) { | ||
| toSort := []declcfg.Bundle{b3_1, b2, b3_2, b1} | ||
| slices.SortStableFunc(toSort, compare.ByVersionAndRelease) | ||
| assert.Equal(t, []declcfg.Bundle{b1, b3_2, b3_1, b2}, toSort) | ||
| assert.Equal(t, []declcfg.Bundle{b1, b3_2, b3_1, b2}, toSort, "should sort descending: 1.0.0 > 1.0.0-alpha+2 > 1.0.0-alpha+1 > 0.0.1") | ||
| }) | ||
|
|
||
| t.Run("some bundles are missing version", func(t *testing.T) { | ||
| toSort := []declcfg.Bundle{b3_1, b4noVersion, b2, b3_2, b5empty, b1} | ||
| slices.SortStableFunc(toSort, compare.ByVersionAndRelease) | ||
| assert.Equal(t, []declcfg.Bundle{b1, b3_2, b3_1, b2, b4noVersion, b5empty}, toSort) | ||
| assert.Equal(t, []declcfg.Bundle{b1, b3_2, b3_1, b2, b4noVersion, b5empty}, toSort, "bundles with invalid/missing versions should sort last") | ||
| }) | ||
| } | ||
|
|
||
|
|
@@ -161,10 +162,57 @@ func TestByDeprecationFunc(t *testing.T) { | |
| c := declcfg.Bundle{Name: "c"} | ||
| d := declcfg.Bundle{Name: "d"} | ||
|
|
||
| assert.Equal(t, 0, byDeprecation(a, b)) | ||
| assert.Equal(t, 0, byDeprecation(b, a)) | ||
| assert.Equal(t, 1, byDeprecation(a, c)) | ||
| assert.Equal(t, -1, byDeprecation(c, a)) | ||
| assert.Equal(t, 0, byDeprecation(c, d)) | ||
| assert.Equal(t, 0, byDeprecation(d, c)) | ||
| assert.Equal(t, 0, byDeprecation(a, b), "both deprecated bundles are equal") | ||
| assert.Equal(t, 0, byDeprecation(b, a), "both deprecated bundles are equal") | ||
| assert.Equal(t, 1, byDeprecation(a, c), "deprecated bundle 'a' should sort after non-deprecated 'c'") | ||
| assert.Equal(t, -1, byDeprecation(c, a), "non-deprecated bundle 'c' should sort before deprecated 'a'") | ||
| assert.Equal(t, 0, byDeprecation(c, d), "both non-deprecated bundles are equal") | ||
| assert.Equal(t, 0, byDeprecation(d, c), "both non-deprecated bundles are equal") | ||
| } | ||
|
|
||
| // TestByVersionAndRelease_WithCompositeVersionComparison tests the feature-gated hybrid comparison. | ||
| // This test detects the current feature gate state and validates the correct behavior path. | ||
| // When CompositeVersionComparison is enabled (experimental deployments), it uses Bundle.Compare() | ||
| // with build metadata fallback. When disabled (standard deployments), it uses legacy build metadata only. | ||
| func TestByVersionAndRelease_WithCompositeVersionComparison(t *testing.T) { | ||
| // Registry+v1 bundles: same version, different build metadata | ||
| b1 := declcfg.Bundle{ | ||
| Name: "package1.v1.0.0+1", | ||
| Properties: []property.Property{ | ||
| {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "package1", "version": "1.0.0+1"}`)}, | ||
| }, | ||
| } | ||
| b2 := declcfg.Bundle{ | ||
| Name: "package1.v1.0.0+2", | ||
| Properties: []property.Property{ | ||
| {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "package1", "version": "1.0.0+2"}`)}, | ||
| }, | ||
| } | ||
|
|
||
| // Detect current feature gate state and test the appropriate path | ||
| if features.OperatorControllerFeatureGate.Enabled(features.CompositeVersionComparison) { | ||
| t.Log("CompositeVersionComparison enabled - testing hybrid comparison with build metadata fallback") | ||
| result := compare.ByVersionAndRelease(b1, b2) | ||
| assert.Positive(t, result, "Bundle.Compare() returns 0 for registry+v1, fallback to build metadata: 1.0.0+2 > 1.0.0+1") | ||
|
|
||
| // Test bundles with explicit pkg.Release field | ||
| explicitR1 := declcfg.Bundle{ | ||
| Name: "pkg.v2.0.0-r1", | ||
| Properties: []property.Property{ | ||
| {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "pkg", "version": "2.0.0", "release": "1"}`)}, | ||
| }, | ||
| } | ||
| explicitR2 := declcfg.Bundle{ | ||
| Name: "pkg.v2.0.0-r2", | ||
| Properties: []property.Property{ | ||
| {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "pkg", "version": "2.0.0", "release": "2"}`)}, | ||
| }, | ||
| } | ||
| result = compare.ByVersionAndRelease(explicitR1, explicitR2) | ||
| assert.Positive(t, result, "2.0.0+release.2 > 2.0.0+release.1 (explicit release field)") | ||
| } else { | ||
| t.Log("CompositeVersionComparison disabled - testing legacy build metadata comparison only") | ||
| result := compare.ByVersionAndRelease(b1, b2) | ||
| assert.Positive(t, result, "should sort by build metadata: 1.0.0+2 > 1.0.0+1") | ||
| } | ||
|
Comment on lines
+192
to
+217
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,3 +48,21 @@ func InAnyChannel(channels ...declcfg.Channel) filter.Predicate[declcfg.Bundle] | |
| return false | ||
| } | ||
| } | ||
|
|
||
| // SameVersionHigherRelease returns a predicate that matches bundles with the same | ||
| // semantic version as the provided version-release, but with a higher release value. | ||
| // This is used to identify re-released bundles (e.g., 2.0.0+2 when 2.0.0+1 is installed). | ||
| func SameVersionHigherRelease(expect bundle.VersionRelease) filter.Predicate[declcfg.Bundle] { | ||
| return func(b declcfg.Bundle) bool { | ||
| actual, err := bundleutil.GetVersionAndRelease(b) | ||
| if err != nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could failing silently here lead to some hard to spot bugs? |
||
| return false | ||
| } | ||
|
|
||
| if expect.Version.Compare(actual.Version) != 0 { | ||
| return false | ||
| } | ||
|
|
||
| return expect.Release.Compare(actual.Release) < 0 | ||
| } | ||
|
Comment on lines
+55
to
+67
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ByVersionAndRelease checks the CompositeVersionComparison feature gate on every comparator invocation. Since this comparator is used inside sorts (O(n log n) comparisons) and the gate value won’t change during a sort, consider hoisting the Enabled() check out (e.g., selecting a comparator function once at call site or providing a factory) to avoid repeated locking/map lookups in hot paths like catalog bundle sorting.