Skip to content

Commit 6372625

Browse files
committed
Revert semantic changes to ClusterExtension version selection and improve VersionRelease parsing
This commit reverts the user-facing semantic changes to the ClusterExtension version field that were introduced to support exact version pinning with build metadata. The version field now ignores build metadata when matching versions, consistent with semver specification. Additionally, this commit modifies the VersionRelease parsing logic to be more tolerant of semver versions whose build metadata is not a valid release. When build metadata cannot be parsed as a release, the full version (including build metadata) is preserved in the Version field, with an empty Release field. Changes include: - Removed documentation about pinning to exact versions with build metadata - Removed exactVersionMatcher logic that enforced build metadata equality - Updated NewLegacyRegistryV1VersionRelease to tolerate non-release build metadata - Updated test expectations to reflect new behavior
1 parent 72c3a34 commit 6372625

File tree

12 files changed

+50
-92
lines changed

12 files changed

+50
-92
lines changed

api/v1/clusterextension_types.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,6 @@ type CatalogFilter struct {
235235
// "0.6.0", which means "only install version 0.6.0 and never
236236
// upgrade from this version".
237237
//
238-
// For registry+v1 bundles that include release information in the build
239-
// metadata field (e.g., "1.0.0+20230101"), you can pin to an exact version
240-
// including the release by specifying the full version string with build
241-
// metadata (e.g., "1.0.0+20230101"). This ensures an exact match of both
242-
// the semver version and the release. If you specify a version without build
243-
// metadata (e.g., "1.0.0"), it will match all bundles with that version
244-
// regardless of their release information.
245-
//
246238
// # Basic Comparison Operators
247239
//
248240
// The basic comparison operators and their meanings are:

docs/api-reference/olmv1-api-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ _Appears in:_
9797
| Field | Description | Default | Validation |
9898
| --- | --- | --- | --- |
9999
| `packageName` _string_ | packageName is a reference to the name of the package to be installed<br />and is used to filter the content from catalogs.<br /><br />packageName is required, immutable, and follows the DNS subdomain standard<br />as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters,<br />hyphens (-) or periods (.), start and end with an alphanumeric character,<br />and be no longer than 253 characters.<br /><br />Some examples of valid values are:<br /> - some-package<br /> - 123-package<br /> - 1-package-2<br /> - somepackage<br /><br />Some examples of invalid values are:<br /> - -some-package<br /> - some-package-<br /> - thisisareallylongpackagenamethatisgreaterthanthemaximumlength<br /> - some.package<br /><br />[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxLength: 253 <br />Required: \{\} <br /> |
100-
| `version` _string_ | version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed.<br /><br />Acceptable version ranges are no longer than 64 characters.<br />Version ranges are composed of comma- or space-delimited values and one or<br />more comparison operators, known as comparison strings. Additional<br />comparison strings can be added using the OR operator (\|\|).<br /><br /># Range Comparisons<br /><br />To specify a version range, you can use a comparison string like ">=3.0,<br /><3.6". When specifying a range, automatic updates will occur within that<br />range. The example comparison string means "install any version greater than<br />or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any<br />upgrades are available within the version range after initial installation,<br />those upgrades should be automatically performed.<br /><br /># Pinned Versions<br /><br />To specify an exact version to install you can use a version range that<br />"pins" to a specific version. When pinning to a specific version, no<br />automatic updates will occur. An example of a pinned version range is<br />"0.6.0", which means "only install version 0.6.0 and never<br />upgrade from this version".<br /><br />For registry+v1 bundles that include release information in the build<br />metadata field (e.g., "1.0.0+20230101"), you can pin to an exact version<br />including the release by specifying the full version string with build<br />metadata (e.g., "1.0.0+20230101"). This ensures an exact match of both<br />the semver version and the release. If you specify a version without build<br />metadata (e.g., "1.0.0"), it will match all bundles with that version<br />regardless of their release information.<br /><br /># Basic Comparison Operators<br /><br />The basic comparison operators and their meanings are:<br /> - "=", equal (not aliased to an operator)<br /> - "!=", not equal<br /> - "<", less than<br /> - ">", greater than<br /> - ">=", greater than OR equal to<br /> - "<=", less than OR equal to<br /><br /># Wildcard Comparisons<br /><br />You can use the "x", "X", and "*" characters as wildcard characters in all<br />comparison operations. Some examples of using the wildcard characters:<br /> - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0"<br /> - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0"<br /> - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3"<br /> - "x", "X", and "*" is equivalent to ">= 0.0.0"<br /><br /># Patch Release Comparisons<br /><br />When you want to specify a minor version up to the next major version you<br />can use the "~" character to perform patch comparisons. Some examples:<br /> - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0"<br /> - "~1" and "~1.x" is equivalent to ">=1, <2"<br /> - "~2.3" is equivalent to ">=2.3, <2.4"<br /> - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0"<br /><br /># Major Release Comparisons<br /><br />You can use the "^" character to make major release comparisons after a<br />stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples:<br /> - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0"<br /> - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0"<br /> - "^2.3" is equivalent to ">=2.3, <3"<br /> - "^2.x" is equivalent to ">=2.0.0, <3"<br /> - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0"<br /> - "^0.2" is equivalent to ">=0.2.0, <0.3.0"<br /> - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4"<br /> - "^0.0" is equivalent to ">=0.0.0, <0.1.0"<br /> - "^0" is equivalent to ">=0.0.0, <1.0.0"<br /><br /># OR Comparisons<br />You can use the "\|\|" character to represent an OR operation in the version<br />range. Some examples:<br /> - ">=1.2.3, <2.0.0 \|\| >3.0.0"<br /> - "^0 \|\| ^3 \|\| ^5"<br /><br />For more information on semver, please see https://semver.org/ | | MaxLength: 64 <br /> |
100+
| `version` _string_ | version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed.<br /><br />Acceptable version ranges are no longer than 64 characters.<br />Version ranges are composed of comma- or space-delimited values and one or<br />more comparison operators, known as comparison strings. Additional<br />comparison strings can be added using the OR operator (\|\|).<br /><br /># Range Comparisons<br /><br />To specify a version range, you can use a comparison string like ">=3.0,<br /><3.6". When specifying a range, automatic updates will occur within that<br />range. The example comparison string means "install any version greater than<br />or equal to 3.0.0 but less than 3.6.0.". It also states intent that if any<br />upgrades are available within the version range after initial installation,<br />those upgrades should be automatically performed.<br /><br /># Pinned Versions<br /><br />To specify an exact version to install you can use a version range that<br />"pins" to a specific version. When pinning to a specific version, no<br />automatic updates will occur. An example of a pinned version range is<br />"0.6.0", which means "only install version 0.6.0 and never<br />upgrade from this version".<br /><br /># Basic Comparison Operators<br /><br />The basic comparison operators and their meanings are:<br /> - "=", equal (not aliased to an operator)<br /> - "!=", not equal<br /> - "<", less than<br /> - ">", greater than<br /> - ">=", greater than OR equal to<br /> - "<=", less than OR equal to<br /><br /># Wildcard Comparisons<br /><br />You can use the "x", "X", and "*" characters as wildcard characters in all<br />comparison operations. Some examples of using the wildcard characters:<br /> - "1.2.x", "1.2.X", and "1.2.*" is equivalent to ">=1.2.0, < 1.3.0"<br /> - ">= 1.2.x", ">= 1.2.X", and ">= 1.2.*" is equivalent to ">= 1.2.0"<br /> - "<= 2.x", "<= 2.X", and "<= 2.*" is equivalent to "< 3"<br /> - "x", "X", and "*" is equivalent to ">= 0.0.0"<br /><br /># Patch Release Comparisons<br /><br />When you want to specify a minor version up to the next major version you<br />can use the "~" character to perform patch comparisons. Some examples:<br /> - "~1.2.3" is equivalent to ">=1.2.3, <1.3.0"<br /> - "~1" and "~1.x" is equivalent to ">=1, <2"<br /> - "~2.3" is equivalent to ">=2.3, <2.4"<br /> - "~1.2.x" is equivalent to ">=1.2.0, <1.3.0"<br /><br /># Major Release Comparisons<br /><br />You can use the "^" character to make major release comparisons after a<br />stable 1.0.0 version is published. If there is no stable version published, // minor versions define the stability level. Some examples:<br /> - "^1.2.3" is equivalent to ">=1.2.3, <2.0.0"<br /> - "^1.2.x" is equivalent to ">=1.2.0, <2.0.0"<br /> - "^2.3" is equivalent to ">=2.3, <3"<br /> - "^2.x" is equivalent to ">=2.0.0, <3"<br /> - "^0.2.3" is equivalent to ">=0.2.3, <0.3.0"<br /> - "^0.2" is equivalent to ">=0.2.0, <0.3.0"<br /> - "^0.0.3" is equvalent to ">=0.0.3, <0.0.4"<br /> - "^0.0" is equivalent to ">=0.0.0, <0.1.0"<br /> - "^0" is equivalent to ">=0.0.0, <1.0.0"<br /><br /># OR Comparisons<br />You can use the "\|\|" character to represent an OR operation in the version<br />range. Some examples:<br /> - ">=1.2.3, <2.0.0 \|\| >3.0.0"<br /> - "^0 \|\| ^3 \|\| ^5"<br /><br />For more information on semver, please see https://semver.org/ | | MaxLength: 64 <br /> |
101101
| `channels` _string array_ | channels is an optional reference to a set of channels belonging to<br />the package specified in the packageName field.<br /><br />A "channel" is a package-author-defined stream of updates for an extension.<br /><br />Each channel in the list must follow the DNS subdomain standard<br />as defined in [RFC 1123]. It must contain only lowercase alphanumeric characters,<br />hyphens (-) or periods (.), start and end with an alphanumeric character,<br />and be no longer than 253 characters. No more than 256 channels can be specified.<br /><br />When specified, it is used to constrain the set of installable bundles and<br />the automated upgrade path. This constraint is an AND operation with the<br />version field. For example:<br /> - Given channel is set to "foo"<br /> - Given version is set to ">=1.0.0, <1.5.0"<br /> - Only bundles that exist in channel "foo" AND satisfy the version range comparison will be considered installable<br /> - Automatic upgrades will be constrained to upgrade edges defined by the selected channel<br /><br />When unspecified, upgrade edges across all channels will be used to identify valid automatic upgrade paths.<br /><br />Some examples of valid values are:<br /> - 1.1.x<br /> - alpha<br /> - stable<br /> - stable-v1<br /> - v1-stable<br /> - dev-preview<br /> - preview<br /> - community<br /><br />Some examples of invalid values are:<br /> - -some-channel<br /> - some-channel-<br /> - thisisareallylongchannelnamethatisgreaterthanthemaximumlength<br /> - original_40<br /> - --default-channel<br /><br />[RFC 1123]: https://tools.ietf.org/html/rfc1123 | | MaxItems: 256 <br /> |
102102
| `selector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#labelselector-v1-meta)_ | selector is an optional field that can be used<br />to filter the set of ClusterCatalogs used in the bundle<br />selection process.<br /><br />When unspecified, all ClusterCatalogs will be used in<br />the bundle selection process. | | |
103103
| `upgradeConstraintPolicy` _[UpgradeConstraintPolicy](#upgradeconstraintpolicy)_ | upgradeConstraintPolicy is an optional field that controls whether<br />the upgrade path(s) defined in the catalog are enforced for the package<br />referenced in the packageName field.<br /><br />Allowed values are: "CatalogProvided" or "SelfCertified", or omitted.<br /><br />When this field is set to "CatalogProvided", automatic upgrades will only occur<br />when upgrade constraints specified by the package author are met.<br /><br />When this field is set to "SelfCertified", the upgrade constraints specified by<br />the package author are ignored. This allows for upgrades and downgrades to<br />any version of the package. This is considered a dangerous operation as it<br />can lead to unknown and potentially disastrous outcomes, such as data<br />loss. It is assumed that users have independently verified changes when<br />using this option.<br /><br />When this field is omitted, the default value is "CatalogProvided". | CatalogProvided | Enum: [CatalogProvided SelfCertified] <br /> |

helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -413,14 +413,6 @@ spec:
413413
"0.6.0", which means "only install version 0.6.0 and never
414414
upgrade from this version".
415415
416-
For registry+v1 bundles that include release information in the build
417-
metadata field (e.g., "1.0.0+20230101"), you can pin to an exact version
418-
including the release by specifying the full version string with build
419-
metadata (e.g., "1.0.0+20230101"). This ensures an exact match of both
420-
the semver version and the release. If you specify a version without build
421-
metadata (e.g., "1.0.0"), it will match all bundles with that version
422-
regardless of their release information.
423-
424416
# Basic Comparison Operators
425417
426418
The basic comparison operators and their meanings are:

helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -379,14 +379,6 @@ spec:
379379
"0.6.0", which means "only install version 0.6.0 and never
380380
upgrade from this version".
381381
382-
For registry+v1 bundles that include release information in the build
383-
metadata field (e.g., "1.0.0+20230101"), you can pin to an exact version
384-
including the release by specifying the full version string with build
385-
metadata (e.g., "1.0.0+20230101"). This ensures an exact match of both
386-
the semver version and the release. If you specify a version without build
387-
metadata (e.g., "1.0.0"), it will match all bundles with that version
388-
regardless of their release information.
389-
390382
# Basic Comparison Operators
391383
392384
The basic comparison operators and their meanings are:

internal/operator-controller/bundle/versionrelease.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,40 @@ import (
1010
slicesutil "github.com/operator-framework/operator-controller/internal/shared/util/slices"
1111
)
1212

13-
// NewLegacyRegistryV1VersionRelease parses a registry+v1 bundle version string and returns
14-
// a VersionRelease. For registry+v1 bundles, the build metadata field of the semver version
15-
// is treated as release information (a semver spec violation maintained for backward compatibility).
16-
// The returned VersionRelease has the build metadata extracted into the Release field, and the
17-
// Version field has its Build metadata cleared.
13+
// NewLegacyRegistryV1VersionRelease parses a registry+v1 bundle version string and returns a
14+
// VersionRelease. Some registry+v1 bundles utilize the build metadata field of the semver version
15+
// as release information (a semver spec violation maintained for backward compatibility). When the
16+
// bundle version includes build metadata that is parsable as a release, the returned
17+
// VersionRelease has the build metadata extracted into the Release field, and the Version field
18+
// has its Build metadata cleared. When the bundle version includes build metadata that is NOT
19+
// parseable as a release, the returned VersionRelease has its Version set to the full semver
20+
// version (with build metadata) and its Release left empty.
1821
func NewLegacyRegistryV1VersionRelease(vStr string) (*VersionRelease, error) {
1922
vers, err := bsemver.Parse(vStr)
2023
if err != nil {
2124
return nil, err
2225
}
2326

24-
rel, err := NewRelease(strings.Join(vers.Build, "."))
25-
if err != nil {
26-
return nil, err
27+
vr := &VersionRelease{
28+
Version: vers,
2729
}
28-
vers.Build = nil
2930

30-
return &VersionRelease{
31-
Version: vers,
32-
Release: rel,
33-
}, nil
31+
rel, err := NewRelease(strings.Join(vr.Version.Build, "."))
32+
if err == nil {
33+
// If the version build metadata parses successfully as a release
34+
// then use it as a release and drop the build metadata
35+
//
36+
// If we don't parse the build metadata as a release successfully,
37+
// that doesn't mean we have an invalid version. It just means
38+
// that we have a valid semver version with valid build metadata,
39+
// but no release value. In this case, we return a VersionRelease
40+
// with:
41+
// - Version: the full version (with build metadata)
42+
// - Release: <empty>
43+
vr.Release = rel
44+
vr.Version.Build = nil
45+
}
46+
return vr, nil
3447
}
3548

3649
type VersionRelease struct {

internal/operator-controller/bundleutil/bundle_test.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,38 @@ import (
44
"encoding/json"
55
"testing"
66

7+
"github.com/blang/semver/v4"
8+
bsemver "github.com/blang/semver/v4"
79
"github.com/stretchr/testify/require"
810

911
"github.com/operator-framework/operator-registry/alpha/declcfg"
1012
"github.com/operator-framework/operator-registry/alpha/property"
1113

14+
"github.com/operator-framework/operator-controller/internal/operator-controller/bundle"
1215
"github.com/operator-framework/operator-controller/internal/operator-controller/bundleutil"
1316
)
1417

1518
func TestGetVersionAndRelease(t *testing.T) {
1619
tests := []struct {
17-
name string
18-
pkgProperty *property.Property
19-
wantErr bool
20+
name string
21+
pkgProperty *property.Property
22+
wantVersionRelease *bundle.VersionRelease
23+
wantErr bool
2024
}{
2125
{
2226
name: "valid version",
2327
pkgProperty: &property.Property{
2428
Type: property.TypePackage,
2529
Value: json.RawMessage(`{"version": "1.0.0-pre+1.alpha.2"}`),
2630
},
31+
wantVersionRelease: &bundle.VersionRelease{
32+
Version: semver.MustParse("1.0.0-pre"),
33+
Release: bundle.Release([]bsemver.PRVersion{
34+
{VersionNum: 1, IsNum: true},
35+
{VersionStr: "alpha"},
36+
{VersionNum: 2, IsNum: true},
37+
}),
38+
},
2739
wantErr: false,
2840
},
2941
{
@@ -40,7 +52,10 @@ func TestGetVersionAndRelease(t *testing.T) {
4052
Type: property.TypePackage,
4153
Value: json.RawMessage(`{"version": "1.0.0+001"}`),
4254
},
43-
wantErr: true,
55+
wantVersionRelease: &bundle.VersionRelease{
56+
Version: semver.MustParse("1.0.0+001"),
57+
},
58+
wantErr: false,
4459
},
4560
{
4661
name: "invalid json",

internal/operator-controller/catalogmetadata/compare/compare.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package compare
22

33
import (
4-
"slices"
54
"strings"
65

76
mmsemver "github.com/Masterminds/semver/v3"
@@ -18,28 +17,15 @@ import (
1817
// provided versionRange. The versionRange provided to this function can be any valid semver
1918
// version string or any range constraint.
2019
//
21-
// When the provided version range is a valid semver version that includes build metadata, then the
22-
// returned function will only match an identical version with the same build metadata.
23-
//
24-
// When the provided version range is a valid semver version that does NOT include build metadata,
25-
// then the returned function will match any version that matches the semver version, ignoring the
26-
// build metadata of matched versions.
20+
// When the provided version range is a valid semver version then the returned function will match
21+
// any version that matches the semver version, ignoring the build metadata of matched versions.
2722
//
2823
// This function is intended to be used to parse the ClusterExtension.spec.source.catalog.version
2924
// field. See the API documentation for more details on the supported syntax.
3025
func NewVersionRange(versionRange string) (bsemver.Range, error) {
31-
if versionPin, err := bsemver.Parse(versionRange); err == nil && len(versionPin.Build) > 0 {
32-
return exactVersionMatcher(versionPin), nil
33-
}
3426
return newMastermindsRange(versionRange)
3527
}
3628

37-
func exactVersionMatcher(pin bsemver.Version) bsemver.Range {
38-
return func(v bsemver.Version) bool {
39-
return pin.Compare(v) == 0 && slices.Compare(pin.Build, v.Build) == 0
40-
}
41-
}
42-
4329
func newMastermindsRange(versionRange string) (bsemver.Range, error) {
4430
constraint, err := mmsemver.NewConstraint(versionRange)
4531
if err != nil {

internal/operator-controller/catalogmetadata/compare/compare_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ func TestNewVersionRange(t *testing.T) {
2626
{
2727
versionRange: "1.0.0+1",
2828
inputVersion: bsemver.MustParse("1.0.0"),
29-
expect: false,
29+
expect: true,
3030
},
3131
{
3232
versionRange: "1.0.0+1",
3333
inputVersion: bsemver.MustParse("1.0.0+2"),
34-
expect: false,
34+
expect: true,
3535
},
3636
{
3737
versionRange: "1.0.0+1",

0 commit comments

Comments
 (0)