From 690c2ce953aa71c6e51b756dff5d08c905e75007 Mon Sep 17 00:00:00 2001 From: everettraven Date: Thu, 5 Sep 2024 14:29:00 -0400 Subject: [PATCH] (feat): allow filtering with list of channels Signed-off-by: everettraven --- api/v1alpha1/clusterextension_types.go | 8 +- api/v1alpha1/zz_generated.deepcopy.go | 5 + ...peratorframework.io_clusterextensions.yaml | 12 +- .../clusterextension_admission_test.go | 44 +++---- .../clusterextension_controller.go | 32 +++-- .../clusterextension_controller_test.go | 123 ++++++++++++++---- internal/resolve/catalog.go | 95 +++++++++----- internal/resolve/catalog_test.go | 116 ++++++++++------- test/e2e/cluster_extension_install_test.go | 4 +- 9 files changed, 292 insertions(+), 147 deletions(-) diff --git a/api/v1alpha1/clusterextension_types.go b/api/v1alpha1/clusterextension_types.go index 589ca734d..645a069fb 100644 --- a/api/v1alpha1/clusterextension_types.go +++ b/api/v1alpha1/clusterextension_types.go @@ -263,7 +263,7 @@ type CatalogSource struct { //+optional Version string `json:"version,omitempty"` - // channel is an optional reference to a channel belonging to + // channels is an optional reference to a set of channels belonging to // the package specified in the packageName field. // // A "channel" is a package author defined stream of updates for an extension. @@ -304,10 +304,10 @@ type CatalogSource struct { // // [RFC 1123]: https://tools.ietf.org/html/rfc1123 // - //+kubebuilder:validation:MaxLength:=253 - //+kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + //+kubebuilder:validation:items:MaxLength:=253 + //+kubebuilder:validation:items:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ //+optional - Channel string `json:"channel,omitempty"` + Channels []string `json:"channels,omitempty"` // selector is an optional field that can be used // to filter the set of ClusterCatalogs used in the bundle diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 0d57be29f..702c3f404 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -58,6 +58,11 @@ func (in *CRDUpgradeSafetyPreflightConfig) DeepCopy() *CRDUpgradeSafetyPreflight // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CatalogSource) DeepCopyInto(out *CatalogSource) { *out = *in + if in.Channels != nil { + in, out := &in.Channels, &out.Channels + *out = make([]string, len(*in)) + copy(*out, *in) + } in.Selector.DeepCopyInto(&out.Selector) } diff --git a/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml b/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml index b55d0b328..a10aea93e 100644 --- a/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml +++ b/config/base/crd/bases/olm.operatorframework.io_clusterextensions.yaml @@ -199,9 +199,9 @@ spec: catalog is used to configure how information is sourced from a catalog. This field must be defined when sourceType is set to "Catalog", and must be the only field defined for this sourceType. properties: - channel: + channels: description: |- - channel is an optional reference to a channel belonging to + channels is an optional reference to a set of channels belonging to the package specified in the packageName field. A "channel" is a package author defined stream of updates for an extension. @@ -241,9 +241,11 @@ spec: - --default-channel [RFC 1123]: https://tools.ietf.org/html/rfc1123 - maxLength: 253 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string + items: + maxLength: 253 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array packageName: description: |- packageName is a reference to the name of the package to be installed diff --git a/internal/controllers/clusterextension_admission_test.go b/internal/controllers/clusterextension_admission_test.go index 15cb0fd33..c7bc3ae1a 100644 --- a/internal/controllers/clusterextension_admission_test.go +++ b/internal/controllers/clusterextension_admission_test.go @@ -235,29 +235,29 @@ func TestClusterExtensionAdmissionVersion(t *testing.T) { } func TestClusterExtensionAdmissionChannel(t *testing.T) { - tooLongError := "spec.source.catalog.channel: Too long: may not be longer than 253" - regexMismatchError := "spec.source.catalog.channel in body should match" + tooLongError := "spec.source.catalog.channels[0]: Too long: may not be longer than 253" + regexMismatchError := "spec.source.catalog.channels[0] in body should match" testCases := []struct { - name string - channelName string - errMsg string + name string + channels []string + errMsg string }{ - {"no channel name", "", ""}, - {"hypen-separated", "hyphenated-name", ""}, - {"dot-separated", "dotted.name", ""}, - {"includes version", "channel-has-version-1.0.1", ""}, - {"long channel name", strings.Repeat("x", 254), tooLongError}, - {"spaces", "spaces spaces", regexMismatchError}, - {"capitalized", "Capitalized", regexMismatchError}, - {"camel case", "camelCase", regexMismatchError}, - {"invalid characters", "many/invalid$characters+in_name", regexMismatchError}, - {"starts with hyphen", "-start-with-hyphen", regexMismatchError}, - {"ends with hyphen", "end-with-hyphen-", regexMismatchError}, - {"starts with period", ".start-with-period", regexMismatchError}, - {"ends with period", "end-with-period.", regexMismatchError}, - {"contains underscore", "some_thing", regexMismatchError}, - {"multiple sequential separators", "a.-b", regexMismatchError}, + {"no channel name", []string{""}, regexMismatchError}, + {"hypen-separated", []string{"hyphenated-name"}, ""}, + {"dot-separated", []string{"dotted.name"}, ""}, + {"includes version", []string{"channel-has-version-1.0.1"}, ""}, + {"long channel name", []string{strings.Repeat("x", 254)}, tooLongError}, + {"spaces", []string{"spaces spaces"}, regexMismatchError}, + {"capitalized", []string{"Capitalized"}, regexMismatchError}, + {"camel case", []string{"camelCase"}, regexMismatchError}, + {"invalid characters", []string{"many/invalid$characters+in_name"}, regexMismatchError}, + {"starts with hyphen", []string{"-start-with-hyphen"}, regexMismatchError}, + {"ends with hyphen", []string{"end-with-hyphen-"}, regexMismatchError}, + {"starts with period", []string{".start-with-period"}, regexMismatchError}, + {"ends with period", []string{"end-with-period."}, regexMismatchError}, + {"contains underscore", []string{"some_thing"}, regexMismatchError}, + {"multiple sequential separators", []string{"a.-b"}, regexMismatchError}, } t.Parallel() @@ -271,7 +271,7 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ PackageName: "package", - Channel: tc.channelName, + Channels: tc.channels, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -282,7 +282,7 @@ func TestClusterExtensionAdmissionChannel(t *testing.T) { }, })) if tc.errMsg == "" { - require.NoError(t, err, "unexpected error for channel %q: %w", tc.channelName, err) + require.NoError(t, err, "unexpected error for channel %q: %w", tc.channels, err) } else { require.Error(t, err) require.Contains(t, err.Error(), tc.errMsg) diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go index 685bfd35f..4213a5687 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -30,6 +30,7 @@ import ( apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/cache" @@ -342,22 +343,27 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp // SetDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension // based on the provided bundle func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundleName string, deprecation *declcfg.Deprecation) { - deprecations := map[string]declcfg.DeprecationEntry{} + deprecations := map[string][]declcfg.DeprecationEntry{} + channelSet := sets.New[string]() + if ext.Spec.Source.Catalog != nil { + for _, channel := range ext.Spec.Source.Catalog.Channels { + channelSet.Insert(channel) + } + } if deprecation != nil { for _, entry := range deprecation.Entries { switch entry.Reference.Schema { case declcfg.SchemaPackage: - deprecations[ocv1alpha1.TypePackageDeprecated] = entry + deprecations[ocv1alpha1.TypePackageDeprecated] = []declcfg.DeprecationEntry{entry} case declcfg.SchemaChannel: - if ext.Spec.Source.Catalog.Channel != entry.Reference.Name { - continue + if channelSet.Has(entry.Reference.Name) { + deprecations[ocv1alpha1.TypeChannelDeprecated] = append(deprecations[ocv1alpha1.TypeChannelDeprecated], entry) } - deprecations[ocv1alpha1.TypeChannelDeprecated] = entry case declcfg.SchemaBundle: if bundleName != entry.Reference.Name { continue } - deprecations[ocv1alpha1.TypeBundleDeprecated] = entry + deprecations[ocv1alpha1.TypeBundleDeprecated] = []declcfg.DeprecationEntry{entry} } } } @@ -369,8 +375,10 @@ func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundleName string, d ocv1alpha1.TypeChannelDeprecated, ocv1alpha1.TypeBundleDeprecated, } { - if entry, ok := deprecations[conditionType]; ok { - deprecationMessages = append(deprecationMessages, entry.Message) + if entries, ok := deprecations[conditionType]; ok { + for _, entry := range entries { + deprecationMessages = append(deprecationMessages, entry.Message) + } } } @@ -393,11 +401,13 @@ func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundleName string, d ocv1alpha1.TypeChannelDeprecated, ocv1alpha1.TypeBundleDeprecated, } { - entry, ok := deprecations[conditionType] + entries, ok := deprecations[conditionType] status, reason, message := metav1.ConditionFalse, ocv1alpha1.ReasonDeprecated, "" if ok { - status, reason, message = metav1.ConditionTrue, ocv1alpha1.ReasonDeprecated, entry.Message - deprecationMessages = append(deprecationMessages, message) + status, reason = metav1.ConditionTrue, ocv1alpha1.ReasonDeprecated + for _, entry := range entries { + message = fmt.Sprintf("%s\n%s", message, entry.Message) + } } apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ Type: conditionType, diff --git a/internal/controllers/clusterextension_controller_test.go b/internal/controllers/clusterextension_controller_test.go index a6637ff66..81dcf3686 100644 --- a/internal/controllers/clusterextension_controller_test.go +++ b/internal/controllers/clusterextension_controller_test.go @@ -120,7 +120,7 @@ func TestClusterExtensionResolutionSucceeds(t *testing.T) { Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkgName, Version: pkgVer, - Channel: pkgChan, + Channels: []string{pkgChan}, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -196,7 +196,7 @@ func TestClusterExtensionUnpackFails(t *testing.T) { Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkgName, Version: pkgVer, - Channel: pkgChan, + Channels: []string{pkgChan}, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -274,7 +274,7 @@ func TestClusterExtensionUnpackUnexpectedState(t *testing.T) { Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkgName, Version: pkgVer, - Channel: pkgChan, + Channels: []string{pkgChan}, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -353,7 +353,7 @@ func TestClusterExtensionUnpackSucceeds(t *testing.T) { Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkgName, Version: pkgVer, - Channel: pkgChan, + Channels: []string{pkgChan}, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -435,7 +435,7 @@ func TestClusterExtensionInstallationFailedApplierFails(t *testing.T) { Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkgName, Version: pkgVer, - Channel: pkgChan, + Channels: []string{pkgChan}, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -523,7 +523,7 @@ func TestClusterExtensionManagerFailed(t *testing.T) { Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkgName, Version: pkgVer, - Channel: pkgChan, + Channels: []string{pkgChan}, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -621,7 +621,7 @@ func TestClusterExtensionManagedContentCacheWatchFail(t *testing.T) { Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkgName, Version: pkgVer, - Channel: pkgChan, + Channels: []string{pkgChan}, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -720,7 +720,7 @@ func TestClusterExtensionInstallationSucceeds(t *testing.T) { Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkgName, Version: pkgVer, - Channel: pkgChan, + Channels: []string{pkgChan}, }, }, Install: ocv1alpha1.ClusterExtensionInstallConfig{ @@ -868,9 +868,7 @@ func TestSetDeprecationStatus(t *testing.T) { Spec: ocv1alpha1.ClusterExtensionSpec{ Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channel: "", - }, + Catalog: &ocv1alpha1.CatalogSource{}, }, }, Status: ocv1alpha1.ClusterExtensionStatus{ @@ -884,9 +882,7 @@ func TestSetDeprecationStatus(t *testing.T) { Spec: ocv1alpha1.ClusterExtensionSpec{ Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", - Catalog: &ocv1alpha1.CatalogSource{ - Channel: "", - }, + Catalog: &ocv1alpha1.CatalogSource{}, }, }, Status: ocv1alpha1.ClusterExtensionStatus{ @@ -938,7 +934,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "nondeprecated", + Channels: []string{"nondeprecated"}, }, }, }, @@ -954,7 +950,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "nondeprecated", + Channels: []string{"nondeprecated"}, }, }, }, @@ -1009,7 +1005,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "badchannel", + Channels: []string{"badchannel"}, }, }, }, @@ -1025,7 +1021,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "badchannel", + Channels: []string{"badchannel"}, }, }, }, @@ -1081,7 +1077,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "badchannel", + Channels: []string{"badchannel"}, }, }, }, @@ -1097,7 +1093,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "badchannel", + Channels: []string{"badchannel"}, }, }, }, @@ -1166,7 +1162,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "badchannel", + Channels: []string{"badchannel"}, }, }, }, @@ -1182,7 +1178,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "badchannel", + Channels: []string{"badchannel"}, }, }, }, @@ -1245,7 +1241,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "badchannel", + Channels: []string{"badchannel"}, }, }, }, @@ -1261,7 +1257,7 @@ func TestSetDeprecationStatus(t *testing.T) { Source: ocv1alpha1.SourceConfig{ SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ - Channel: "badchannel", + Channels: []string{"badchannel"}, }, }, }, @@ -1313,6 +1309,85 @@ func TestSetDeprecationStatus(t *testing.T) { }, }, }, + { + name: "deprecated channels specified, ChannelDeprecated and Deprecated status set to true, others set to false", + clusterExtension: &ocv1alpha1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1alpha1.ClusterExtensionSpec{ + Source: ocv1alpha1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1alpha1.CatalogSource{ + Channels: []string{"badchannel", "anotherbadchannel"}, + }, + }, + }, + Status: ocv1alpha1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{}, + }, + }, + expectedClusterExtension: &ocv1alpha1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Spec: ocv1alpha1.ClusterExtensionSpec{ + Source: ocv1alpha1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1alpha1.CatalogSource{ + Channels: []string{"badchannel", "anotherbadchannel"}, + }, + }, + }, + Status: ocv1alpha1.ClusterExtensionStatus{ + Conditions: []metav1.Condition{ + { + Type: ocv1alpha1.TypeDeprecated, + Reason: ocv1alpha1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1alpha1.TypePackageDeprecated, + Reason: ocv1alpha1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + { + Type: ocv1alpha1.TypeChannelDeprecated, + Reason: ocv1alpha1.ReasonDeprecated, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: ocv1alpha1.TypeBundleDeprecated, + Reason: ocv1alpha1.ReasonDeprecated, + Status: metav1.ConditionFalse, + ObservedGeneration: 1, + }, + }, + }, + }, + bundle: &declcfg.Bundle{}, + deprecation: &declcfg.Deprecation{ + Entries: []declcfg.DeprecationEntry{ + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "badchannel", + }, + Message: "bad channel!", + }, + { + Reference: declcfg.PackageScopedReference{ + Schema: declcfg.SchemaChannel, + Name: "anotherbadchannel", + }, + Message: "another bad channedl!", + }, + }, + }, + }, } { t.Run(tc.name, func(t *testing.T) { controllers.SetDeprecationStatus(tc.clusterExtension, tc.bundle.Name, tc.deprecation) diff --git a/internal/resolve/catalog.go b/internal/resolve/catalog.go index ccd282bcb..83857a8fe 100644 --- a/internal/resolve/catalog.go +++ b/internal/resolve/catalog.go @@ -5,11 +5,13 @@ import ( "fmt" "slices" "sort" + "strings" mmsemver "github.com/Masterminds/semver/v3" bsemver "github.com/blang/semver/v4" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" @@ -28,11 +30,17 @@ type CatalogResolver struct { Validations []ValidationFunc } +type foundBundle struct { + bundle *declcfg.Bundle + catalog string + priority int32 +} + // Resolve returns a Bundle from a catalog that needs to get installed on the cluster. func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterExtension, installedBundle *ocv1alpha1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { packageName := ext.Spec.Source.Catalog.PackageName versionRange := ext.Spec.Source.Catalog.Version - channelName := ext.Spec.Source.Catalog.Channel + channels := ext.Spec.Source.Catalog.Channels selector, err := metav1.LabelSelectorAsSelector(&ext.Spec.Source.Catalog.Selector) if err != nil { @@ -51,16 +59,8 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx } } - type foundBundle struct { - bundle *declcfg.Bundle - catalog string - priority int32 - } - resolvedBundles := []foundBundle{} - var ( - priorDeprecation *declcfg.Deprecation - ) + var priorDeprecation *declcfg.Deprecation listOptions := []client.ListOption{ client.MatchingLabelsSelector{Selector: selector}, @@ -71,11 +71,12 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx } var predicates []filter.Predicate[declcfg.Bundle] - if channelName != "" { - channels := slices.DeleteFunc(packageFBC.Channels, func(c declcfg.Channel) bool { - return channelName != "" && c.Name != channelName + if len(channels) > 0 { + channelSet := sets.New(channels...) + filteredChannels := slices.DeleteFunc(packageFBC.Channels, func(c declcfg.Channel) bool { + return !channelSet.Has(c.Name) }) - predicates = append(predicates, filter.InAnyChannel(channels...)) + predicates = append(predicates, filter.InAnyChannel(filteredChannels...)) } if versionRangeConstraints != nil { @@ -151,26 +152,12 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx // Check for ambiguity if len(resolvedBundles) != 1 { - errPrefix := "" - if installedBundle != nil { - errPrefix = fmt.Sprintf("error upgrading from currently installed version %q: ", installedBundle.Version) - } - switch { - case len(resolvedBundles) > 1: - matchedCatalogs := []string{} - for _, r := range resolvedBundles { - matchedCatalogs = append(matchedCatalogs, r.catalog) - } - slices.Sort(matchedCatalogs) // sort for consistent error message - return nil, nil, nil, fmt.Errorf("%smatching packages found in multiple catalogs: %v", errPrefix, matchedCatalogs) - case versionRange != "" && channelName != "": - return nil, nil, nil, fmt.Errorf("%sno package %q matching version %q in channel %q found", errPrefix, packageName, versionRange, channelName) - case versionRange != "": - return nil, nil, nil, fmt.Errorf("%sno package %q matching version %q found", errPrefix, packageName, versionRange) - case channelName != "": - return nil, nil, nil, fmt.Errorf("%sno package %q in channel %q found", errPrefix, packageName, channelName) - default: - return nil, nil, nil, fmt.Errorf("%sno package %q found", errPrefix, packageName) + return nil, nil, nil, resolutionError{ + PackageName: packageName, + Version: versionRange, + Channels: channels, + InstalledBundle: installedBundle, + ResolvedBundles: resolvedBundles, } } resolvedBundle := resolvedBundles[0].bundle @@ -190,6 +177,46 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx return resolvedBundle, resolvedBundleVersion, priorDeprecation, nil } +type resolutionError struct { + PackageName string + Version string + Channels []string + InstalledBundle *ocv1alpha1.BundleMetadata + ResolvedBundles []foundBundle +} + +func (rei resolutionError) Error() string { + var sb strings.Builder + if rei.InstalledBundle != nil { + sb.WriteString(fmt.Sprintf("error upgrading from currently installed version %q: ", rei.InstalledBundle.Version)) + } + + if len(rei.ResolvedBundles) > 1 { + sb.WriteString(fmt.Sprintf("found bundles for package %q ", rei.PackageName)) + } else { + sb.WriteString(fmt.Sprintf("no bundles found for package %q ", rei.PackageName)) + } + + if rei.Version != "" { + sb.WriteString(fmt.Sprintf("matching version %q ", rei.Version)) + } + + if len(rei.Channels) > 0 { + sb.WriteString(fmt.Sprintf("in channels %v ", rei.Channels)) + } + + matchedCatalogs := []string{} + for _, r := range rei.ResolvedBundles { + matchedCatalogs = append(matchedCatalogs, r.catalog) + } + slices.Sort(matchedCatalogs) // sort for consistent error message + if len(matchedCatalogs) > 1 { + sb.WriteString(fmt.Sprintf("in multiple catalogs with the same priority %v ", matchedCatalogs)) + } + + return strings.TrimSpace(sb.String()) +} + func isDeprecated(bundle declcfg.Bundle, deprecation *declcfg.Deprecation) bool { if deprecation == nil { return false diff --git a/internal/resolve/catalog_test.go b/internal/resolve/catalog_test.go index 46c279a2a..89ea7e3f2 100644 --- a/internal/resolve/catalog_test.go +++ b/internal/resolve/catalog_test.go @@ -27,7 +27,7 @@ import ( func TestInvalidClusterExtensionVersionRange(t *testing.T) { r := CatalogResolver{} pkgName := randPkg() - ce := buildFooClusterExtension(pkgName, "", "foobar", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "foobar", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, `desired version range "foobar" is invalid: improper constraint: foobar`) } @@ -37,7 +37,7 @@ func TestErrorWalkingCatalogs(t *testing.T) { return fmt.Errorf("fake error") }} pkgName := randPkg() - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, "error walking catalogs: fake error") } @@ -50,7 +50,7 @@ func TestErrorGettingPackage(t *testing.T) { } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} pkgName := randPkg() - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) assert.EqualError(t, err, fmt.Sprintf(`error walking catalogs: error getting package %q from catalog "a": fake error`, pkgName)) } @@ -69,9 +69,9 @@ func TestPackageDoesNotExist(t *testing.T) { } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} pkgName := randPkg() - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) - assert.EqualError(t, err, fmt.Sprintf(`no package %q found`, pkgName)) + assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q`, pkgName)) } func TestPackageExists(t *testing.T) { @@ -88,11 +88,11 @@ func TestPackageExists(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) - assert.Equal(t, genBundle(pkgName, "2.0.0"), *gotBundle) - assert.Equal(t, bsemver.MustParse("2.0.0"), *gotVersion) + assert.Equal(t, genBundle(pkgName, "3.0.0"), *gotBundle) + assert.Equal(t, bsemver.MustParse("3.0.0"), *gotVersion) assert.Equal(t, ptr.To(packageDeprecation(pkgName)), gotDeprecation) } @@ -117,7 +117,7 @@ func TestValidationFailed(t *testing.T) { }, }, } - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) } @@ -136,9 +136,9 @@ func TestVersionDoesNotExist(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "3.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "4.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) - assert.EqualError(t, err, fmt.Sprintf(`no package %q matching version "3.0.0" found`, pkgName)) + assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q matching version "4.0.0"`, pkgName)) } func TestVersionExists(t *testing.T) { @@ -155,7 +155,7 @@ func TestVersionExists(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", ">=1.0.0 <2.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <2.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "1.0.2"), *gotBundle) @@ -177,9 +177,9 @@ func TestChannelDoesNotExist(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "stable", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{"stable"}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) - assert.EqualError(t, err, fmt.Sprintf(`no package %q in channel "stable" found`, pkgName)) + assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q in channels [stable]`, pkgName)) } func TestChannelExists(t *testing.T) { @@ -196,7 +196,7 @@ func TestChannelExists(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "beta", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{"beta"}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "1.0.2"), *gotBundle) @@ -218,9 +218,9 @@ func TestChannelExistsButNotVersion(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "beta", "3.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{"beta"}, "3.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) - assert.EqualError(t, err, fmt.Sprintf(`no package %q matching version "3.0.0" in channel "beta" found`, pkgName)) + assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q matching version "3.0.0" in channels [beta]`, pkgName)) } func TestVersionExistsButNotChannel(t *testing.T) { @@ -237,9 +237,9 @@ func TestVersionExistsButNotChannel(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "stable", "1.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{"stable"}, "1.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, _, _, err := r.Resolve(context.Background(), ce, nil) - assert.EqualError(t, err, fmt.Sprintf(`no package %q matching version "1.0.0" in channel "stable" found`, pkgName)) + assert.EqualError(t, err, fmt.Sprintf(`no bundles found for package %q matching version "1.0.0" in channels [stable]`, pkgName)) } func TestChannelAndVersionExist(t *testing.T) { @@ -256,7 +256,7 @@ func TestChannelAndVersionExist(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "alpha", "0.1.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{"alpha"}, "0.1.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "0.1.0"), *gotBundle) @@ -278,7 +278,7 @@ func TestPreferNonDeprecated(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", ">=0.1.0 <=1.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, ">=0.1.0 <=1.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "0.1.0"), *gotBundle) @@ -300,7 +300,7 @@ func TestAcceptDeprecated(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) assert.Equal(t, genBundle(pkgName, "1.0.1"), *gotBundle) @@ -383,7 +383,7 @@ func TestPackageVariationsBetweenCatalogs(t *testing.T) { r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} t.Run("when bundle candidates for a package are deprecated in all but one catalog", func(t *testing.T) { - ce := buildFooClusterExtension(pkgName, "", ">=1.0.0 <=1.0.3", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.3", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) // We choose the only non-deprecated package @@ -393,29 +393,29 @@ func TestPackageVariationsBetweenCatalogs(t *testing.T) { }) t.Run("when bundle candidates are found and deprecated in multiple catalogs", func(t *testing.T) { - ce := buildFooClusterExtension(pkgName, "", ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) // We will not make a decision on which catalog to use - assert.ErrorContains(t, err, "found in multiple catalogs: [b c]") + assert.ErrorContains(t, err, "in multiple catalogs with the same priority [b c]") assert.Nil(t, gotBundle) assert.Nil(t, gotVersion) assert.Nil(t, gotDeprecation) }) t.Run("when bundle candidates are found and not deprecated in multiple catalogs", func(t *testing.T) { - ce := buildFooClusterExtension(pkgName, "", ">=1.0.0 <=1.0.4", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.4", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) // We will not make a decision on which catalog to use - assert.ErrorContains(t, err, "found in multiple catalogs: [d f]") + assert.ErrorContains(t, err, "in multiple catalogs with the same priority [d f]") assert.Nil(t, gotBundle) assert.Nil(t, gotVersion) assert.Nil(t, gotDeprecation) }) t.Run("highest semver bundle is chosen when candidates are all from the same catalog", func(t *testing.T) { - ce := buildFooClusterExtension(pkgName, "", ">=1.0.4 <=1.0.5", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.4 <=1.0.5", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) // Bundles within one catalog for a package will be sorted by semver and deprecation and the best is returned @@ -440,7 +440,7 @@ func TestUpgradeFoundLegacy(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) installedBundle := &ocv1alpha1.BundleMetadata{ Name: bundleName(pkgName, "0.1.0"), Version: "0.1.0", @@ -468,14 +468,14 @@ func TestUpgradeNotFoundLegacy(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "<1.0.0 >=2.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "<1.0.0 >=2.0.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) installedBundle := &ocv1alpha1.BundleMetadata{ Name: bundleName(pkgName, "0.1.0"), Version: "0.1.0", } // 0.1.0 only upgrades to 1.0.x with its legacy upgrade edges, so this fails. _, _, _, err := r.Resolve(context.Background(), ce, installedBundle) - assert.EqualError(t, err, fmt.Sprintf(`error upgrading from currently installed version "0.1.0": no package %q matching version "<1.0.0 >=2.0.0" found`, pkgName)) + assert.EqualError(t, err, fmt.Sprintf(`error upgrading from currently installed version "0.1.0": no bundles found for package %q matching version "<1.0.0 >=2.0.0"`, pkgName)) } func TestUpgradeFoundSemver(t *testing.T) { @@ -493,7 +493,7 @@ func TestUpgradeFoundSemver(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) installedBundle := &ocv1alpha1.BundleMetadata{ Name: bundleName(pkgName, "1.0.0"), Version: "1.0.0", @@ -523,7 +523,7 @@ func TestUpgradeNotFoundSemver(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "!=0.1.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "!=0.1.0", ocv1alpha1.UpgradeConstraintPolicyEnforce) installedBundle := &ocv1alpha1.BundleMetadata{ Name: bundleName(pkgName, "0.1.0"), Version: "0.1.0", @@ -531,7 +531,7 @@ func TestUpgradeNotFoundSemver(t *testing.T) { // there are legacy upgrade edges from 0.1.0 to 1.0.x, but we are using semver semantics here. // therefore, we expect to fail because there are no semver-compatible upgrade edges from 0.1.0. _, _, _, err := r.Resolve(context.Background(), ce, installedBundle) - assert.EqualError(t, err, fmt.Sprintf(`error upgrading from currently installed version "0.1.0": no package %q matching version "!=0.1.0" found`, pkgName)) + assert.EqualError(t, err, fmt.Sprintf(`error upgrading from currently installed version "0.1.0": no bundles found for package %q matching version "!=0.1.0"`, pkgName)) } func TestDowngradeFound(t *testing.T) { @@ -548,7 +548,7 @@ func TestDowngradeFound(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "<1.0.2", ocv1alpha1.UpgradeConstraintPolicyIgnore) + ce := buildFooClusterExtension(pkgName, []string{}, "<1.0.2", ocv1alpha1.UpgradeConstraintPolicyIgnore) installedBundle := &ocv1alpha1.BundleMetadata{ Name: bundleName(pkgName, "1.0.2"), Version: "1.0.2", @@ -576,14 +576,14 @@ func TestDowngradeNotFound(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", ">0.1.0 <1.0.0", ocv1alpha1.UpgradeConstraintPolicyIgnore) + ce := buildFooClusterExtension(pkgName, []string{}, ">0.1.0 <1.0.0", ocv1alpha1.UpgradeConstraintPolicyIgnore) installedBundle := &ocv1alpha1.BundleMetadata{ Name: bundleName(pkgName, "1.0.2"), Version: "1.0.2", } // Downgrades are allowed via the upgrade constraint policy, but there is no bundle in the specified range. _, _, _, err := r.Resolve(context.Background(), ce, installedBundle) - assert.EqualError(t, err, fmt.Sprintf(`error upgrading from currently installed version "1.0.2": no package %q matching version ">0.1.0 <1.0.0" found`, pkgName)) + assert.EqualError(t, err, fmt.Sprintf(`error upgrading from currently installed version "1.0.2": no bundles found for package %q matching version ">0.1.0 <1.0.0"`, pkgName)) } func TestCatalogWalker(t *testing.T) { @@ -640,7 +640,7 @@ func TestCatalogWalker(t *testing.T) { }) } -func buildFooClusterExtension(pkg, channel, version string, upgradeConstraintPolicy ocv1alpha1.UpgradeConstraintPolicy) *ocv1alpha1.ClusterExtension { +func buildFooClusterExtension(pkg string, channels []string, version string, upgradeConstraintPolicy ocv1alpha1.UpgradeConstraintPolicy) *ocv1alpha1.ClusterExtension { return &ocv1alpha1.ClusterExtension{ ObjectMeta: metav1.ObjectMeta{ Name: pkg, @@ -654,8 +654,8 @@ func buildFooClusterExtension(pkg, channel, version string, upgradeConstraintPol SourceType: "Catalog", Catalog: &ocv1alpha1.CatalogSource{ PackageName: pkg, - Channel: channel, Version: version, + Channels: channels, UpgradeConstraintPolicy: upgradeConstraintPolicy, }, }, @@ -744,6 +744,9 @@ func genPackage(pkg string) *declcfg.DeclarativeConfig { {Name: bundleName(pkg, "0.1.0")}, {Name: bundleName(pkg, "1.0.2"), SkipRange: "<1.0.2"}, }}, + {Package: pkg, Name: "gamma", Entries: []declcfg.ChannelEntry{ + {Name: bundleName(pkg, "3.0.0"), SkipRange: ">=2.0.0 <3.0.0"}, + }}, }, Bundles: []declcfg.Bundle{ genBundle(pkg, "0.1.0"), @@ -751,6 +754,7 @@ func genPackage(pkg string) *declcfg.DeclarativeConfig { genBundle(pkg, "1.0.1"), genBundle(pkg, "1.0.2"), genBundle(pkg, "2.0.0"), + genBundle(pkg, "3.0.0"), }, Deprecations: []declcfg.Deprecation{packageDeprecation(pkg)}, } @@ -847,7 +851,7 @@ func TestClusterExtensionMatchLabel(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) ce.Spec.Source.Catalog.Selector.MatchLabels = map[string]string{"olm.operatorframework.io/name": "b"} _, _, _, err := r.Resolve(context.Background(), ce, nil) @@ -866,12 +870,12 @@ func TestClusterExtensionNoMatchLabel(t *testing.T) { }, } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) ce.Spec.Source.Catalog.Selector.MatchLabels = map[string]string{"olm.operatorframework.io/name": "a"} _, _, _, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) - require.ErrorContains(t, err, fmt.Sprintf("no package %q found", pkgName)) + require.ErrorContains(t, err, fmt.Sprintf("no bundles found for package %q", pkgName)) } func TestUnequalPriority(t *testing.T) { @@ -908,7 +912,7 @@ func TestUnequalPriority(t *testing.T) { } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) _, gotVersion, _, err := r.Resolve(context.Background(), ce, nil) require.NoError(t, err) require.Equal(t, bsemver.MustParse("1.0.0"), *gotVersion) @@ -929,11 +933,33 @@ func TestMultiplePriority(t *testing.T) { } r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} - ce := buildFooClusterExtension(pkgName, "", ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyEnforce) + ce := buildFooClusterExtension(pkgName, []string{}, ">=1.0.0 <=1.0.1", ocv1alpha1.UpgradeConstraintPolicyEnforce) gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) require.Error(t, err) - assert.ErrorContains(t, err, "found in multiple catalogs: [a b c]") + assert.ErrorContains(t, err, "in multiple catalogs with the same priority [a b c]") assert.Nil(t, gotBundle) assert.Nil(t, gotVersion) assert.Nil(t, gotDeprecation) } + +func TestMultipleChannels(t *testing.T) { + pkgName := randPkg() + w := staticCatalogWalker{ + "a": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + return &declcfg.DeclarativeConfig{}, nil, nil + }, + "b": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + return &declcfg.DeclarativeConfig{}, nil, nil + }, + "c": func() (*declcfg.DeclarativeConfig, *catalogd.ClusterCatalogSpec, error) { + return genPackage(pkgName), nil, nil + }, + } + r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs} + ce := buildFooClusterExtension(pkgName, []string{"beta", "alpha"}, "", ocv1alpha1.UpgradeConstraintPolicyEnforce) + gotBundle, gotVersion, gotDeprecation, err := r.Resolve(context.Background(), ce, nil) + require.NoError(t, err) + assert.Equal(t, genBundle(pkgName, "2.0.0"), *gotBundle) + assert.Equal(t, bsemver.MustParse("2.0.0"), *gotVersion) + assert.Equal(t, ptr.To(packageDeprecation(pkgName)), gotDeprecation) +} diff --git a/test/e2e/cluster_extension_install_test.go b/test/e2e/cluster_extension_install_test.go index a20554a48..d18cb21c2 100644 --- a/test/e2e/cluster_extension_install_test.go +++ b/test/e2e/cluster_extension_install_test.go @@ -325,7 +325,7 @@ func TestClusterExtensionInstallRegistryMultipleBundles(t *testing.T) { } assert.Equal(ct, metav1.ConditionFalse, cond.Status) assert.Equal(ct, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - assert.Contains(ct, cond.Message, "matching packages found in multiple catalogs") + assert.Contains(ct, cond.Message, "in multiple catalogs with the same priority [operatorhubio test-catalog]") assert.Nil(ct, clusterExtension.Status.Resolution) }, pollDuration, pollInterval) } @@ -394,7 +394,7 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) { return } assert.Equal(ct, ocv1alpha1.ReasonResolutionFailed, cond.Reason) - assert.Equal(ct, "error upgrading from currently installed version \"1.0.0\": no package \"prometheus\" matching version \"1.2.0\" found", cond.Message) + assert.Equal(ct, "error upgrading from currently installed version \"1.0.0\": no bundles found for package \"prometheus\" matching version \"1.2.0\"", cond.Message) assert.Empty(ct, clusterExtension.Status.Resolution) }, pollDuration, pollInterval) }