Skip to content

⚠️ allow filtering with a list of channels #1173

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions api/v1alpha1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
44 changes: 22 additions & 22 deletions internal/controllers/clusterextension_admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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{
Expand All @@ -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)
Expand Down
32 changes: 21 additions & 11 deletions internal/controllers/clusterextension_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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}
}
}
}
Expand All @@ -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)
}
}
}

Expand All @@ -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,
Expand Down
Loading
Loading