Skip to content

Commit 245e580

Browse files
authored
✨ Add in catalog label selection support (#1164)
* Add in catalog label selection support Signed-off-by: Todd Short <tshort@redhat.com> * fixup! Add in catalog label selection support Signed-off-by: Todd Short <tshort@redhat.com> --------- Signed-off-by: Todd Short <tshort@redhat.com>
1 parent 21670ae commit 245e580

File tree

4 files changed

+183
-5
lines changed

4 files changed

+183
-5
lines changed

internal/resolve/catalog.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77

88
mmsemver "github.com/Masterminds/semver/v3"
99
bsemver "github.com/blang/semver/v4"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/labels"
1012
"sigs.k8s.io/controller-runtime/pkg/client"
1113

1214
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
@@ -31,9 +33,17 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx
3133
versionRange := ext.Spec.Version
3234
channelName := ext.Spec.Channel
3335

36+
selector, err := metav1.LabelSelectorAsSelector(&ext.Spec.CatalogSelector)
37+
if err != nil {
38+
return nil, nil, nil, fmt.Errorf("desired catalog selector is invalid: %w", err)
39+
}
40+
// A nothing (empty) seletor selects everything
41+
if selector == labels.Nothing() {
42+
selector = labels.Everything()
43+
}
44+
3445
var versionRangeConstraints *mmsemver.Constraints
3546
if versionRange != "" {
36-
var err error
3747
versionRangeConstraints, err = mmsemver.NewConstraint(versionRange)
3848
if err != nil {
3949
return nil, nil, nil, fmt.Errorf("desired version range %q is invalid: %w", versionRange, err)
@@ -45,6 +55,9 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx
4555
resolvedDeprecation *declcfg.Deprecation
4656
)
4757

58+
listOptions := []client.ListOption{
59+
client.MatchingLabelsSelector{Selector: selector},
60+
}
4861
if err := r.WalkCatalogsFunc(ctx, packageName, func(ctx context.Context, cat *catalogd.ClusterCatalog, packageFBC *declcfg.DeclarativeConfig, err error) error {
4962
if err != nil {
5063
return fmt.Errorf("error getting package %q from catalog %q: %w", packageName, cat.Name, err)
@@ -110,7 +123,7 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx
110123
resolvedBundle = &thisBundle
111124
resolvedDeprecation = thisDeprecation
112125
return nil
113-
}); err != nil {
126+
}, listOptions...); err != nil {
114127
return nil, nil, nil, fmt.Errorf("error walking catalogs: %w", err)
115128
}
116129

internal/resolve/catalog_test.go

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
"github.com/stretchr/testify/require"
1212
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/labels"
1314
"k8s.io/apimachinery/pkg/util/rand"
1415
"k8s.io/apimachinery/pkg/util/sets"
1516
featuregatetesting "k8s.io/component-base/featuregate/testing"
@@ -516,10 +517,22 @@ type getPackageFunc func() (*declcfg.DeclarativeConfig, error)
516517

517518
type staticCatalogWalker map[string]getPackageFunc
518519

519-
func (w staticCatalogWalker) WalkCatalogs(ctx context.Context, _ string, f CatalogWalkFunc, _ ...client.ListOption) error {
520+
func (w staticCatalogWalker) WalkCatalogs(ctx context.Context, _ string, f CatalogWalkFunc, opts ...client.ListOption) error {
520521
for k, v := range w {
521522
cat := &catalogd.ClusterCatalog{
522-
ObjectMeta: metav1.ObjectMeta{Name: k},
523+
ObjectMeta: metav1.ObjectMeta{
524+
Name: k,
525+
Labels: map[string]string{
526+
"olm.operatorframework.io/name": k,
527+
},
528+
},
529+
}
530+
options := client.ListOptions{}
531+
for _, opt := range opts {
532+
opt.ApplyToList(&options)
533+
}
534+
if !options.LabelSelector.Matches(labels.Set(cat.ObjectMeta.Labels)) {
535+
continue
523536
}
524537
fbc, fbcErr := v()
525538
if err := f(ctx, cat, fbc, fbcErr); err != nil {
@@ -589,3 +602,97 @@ func genPackage(pkg string) *declcfg.DeclarativeConfig {
589602
Deprecations: []declcfg.Deprecation{packageDeprecation(pkg)},
590603
}
591604
}
605+
606+
func TestInvalidClusterExtensionCatalogMatchExpressions(t *testing.T) {
607+
r := CatalogResolver{}
608+
ce := &ocv1alpha1.ClusterExtension{
609+
ObjectMeta: metav1.ObjectMeta{
610+
Name: "foo",
611+
},
612+
Spec: ocv1alpha1.ClusterExtensionSpec{
613+
PackageName: "foo",
614+
CatalogSelector: metav1.LabelSelector{
615+
MatchExpressions: []metav1.LabelSelectorRequirement{
616+
{
617+
Key: "name",
618+
Operator: metav1.LabelSelectorOperator("bad"),
619+
Values: []string{"value"},
620+
},
621+
},
622+
},
623+
},
624+
}
625+
_, _, _, err := r.Resolve(context.Background(), ce, nil)
626+
assert.EqualError(t, err, "desired catalog selector is invalid: \"bad\" is not a valid label selector operator")
627+
}
628+
629+
func TestInvalidClusterExtensionCatalogMatchLabelsName(t *testing.T) {
630+
w := staticCatalogWalker{
631+
"a": func() (*declcfg.DeclarativeConfig, error) { return genPackage("foo"), nil },
632+
}
633+
r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs}
634+
ce := &ocv1alpha1.ClusterExtension{
635+
ObjectMeta: metav1.ObjectMeta{
636+
Name: "foo",
637+
},
638+
Spec: ocv1alpha1.ClusterExtensionSpec{
639+
PackageName: "foo",
640+
CatalogSelector: metav1.LabelSelector{
641+
MatchLabels: map[string]string{"": "value"},
642+
},
643+
},
644+
}
645+
_, _, _, err := r.Resolve(context.Background(), ce, nil)
646+
assert.ErrorContains(t, err, "desired catalog selector is invalid: key: Invalid value:")
647+
}
648+
649+
func TestInvalidClusterExtensionCatalogMatchLabelsValue(t *testing.T) {
650+
w := staticCatalogWalker{
651+
"a": func() (*declcfg.DeclarativeConfig, error) { return genPackage("foo"), nil },
652+
}
653+
r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs}
654+
ce := &ocv1alpha1.ClusterExtension{
655+
ObjectMeta: metav1.ObjectMeta{
656+
Name: "foo",
657+
},
658+
Spec: ocv1alpha1.ClusterExtensionSpec{
659+
PackageName: "foo",
660+
CatalogSelector: metav1.LabelSelector{
661+
MatchLabels: map[string]string{"name": "&value"},
662+
},
663+
},
664+
}
665+
_, _, _, err := r.Resolve(context.Background(), ce, nil)
666+
assert.ErrorContains(t, err, "desired catalog selector is invalid: values[0][name]: Invalid value:")
667+
}
668+
669+
func TestClusterExtensionMatchLabel(t *testing.T) {
670+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)()
671+
pkgName := randPkg()
672+
w := staticCatalogWalker{
673+
"a": func() (*declcfg.DeclarativeConfig, error) { return &declcfg.DeclarativeConfig{}, nil },
674+
"b": func() (*declcfg.DeclarativeConfig, error) { return genPackage(pkgName), nil },
675+
}
676+
r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs}
677+
ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce)
678+
ce.Spec.CatalogSelector.MatchLabels = map[string]string{"olm.operatorframework.io/name": "b"}
679+
680+
_, _, _, err := r.Resolve(context.Background(), ce, nil)
681+
require.NoError(t, err)
682+
}
683+
684+
func TestClusterExtensionNoMatchLabel(t *testing.T) {
685+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)()
686+
pkgName := randPkg()
687+
w := staticCatalogWalker{
688+
"a": func() (*declcfg.DeclarativeConfig, error) { return &declcfg.DeclarativeConfig{}, nil },
689+
"b": func() (*declcfg.DeclarativeConfig, error) { return genPackage(pkgName), nil },
690+
}
691+
r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs}
692+
ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce)
693+
ce.Spec.CatalogSelector.MatchLabels = map[string]string{"olm.operatorframework.io/name": "a"}
694+
695+
_, _, _, err := r.Resolve(context.Background(), ce, nil)
696+
require.Error(t, err)
697+
require.ErrorContains(t, err, fmt.Sprintf("no package %q found", pkgName))
698+
}

test/e2e/cluster_extension_install_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ func TestClusterExtensionInstallRegistry(t *testing.T) {
232232
ServiceAccount: ocv1alpha1.ServiceAccountReference{
233233
Name: sa.Name,
234234
},
235+
CatalogSelector: metav1.LabelSelector{
236+
MatchLabels: map[string]string{"olm.operatorframework.io/name": extensionCatalog.Name},
237+
},
235238
}
236239
t.Log("It resolves the specified package with correct bundle path")
237240
t.Log("By creating the ClusterExtension resource")
@@ -277,6 +280,41 @@ func TestClusterExtensionInstallRegistry(t *testing.T) {
277280
}, pollDuration, pollInterval)
278281
}
279282

283+
func TestClusterExtensionInstallRegistryMultipleBundles(t *testing.T) {
284+
t.Log("When a cluster extension is installed from a catalog")
285+
286+
clusterExtension, extensionCatalog, sa := testInit(t)
287+
defer testCleanup(t, extensionCatalog, clusterExtension, sa)
288+
defer getArtifactsOutput(t)
289+
290+
clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{
291+
PackageName: "prometheus",
292+
InstallNamespace: "default",
293+
ServiceAccount: ocv1alpha1.ServiceAccountReference{
294+
Name: sa.Name,
295+
},
296+
}
297+
t.Log("It resolves to multiple bundle paths")
298+
t.Log("By creating the ClusterExtension resource")
299+
require.NoError(t, c.Create(context.Background(), clusterExtension))
300+
301+
t.Log("By eventually reporting a failed resolution with multiple bundles")
302+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
303+
assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
304+
assert.Len(ct, clusterExtension.Status.Conditions, len(conditionsets.ConditionTypes))
305+
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved)
306+
if !assert.NotNil(ct, cond) {
307+
return
308+
}
309+
// TODO(tmshort/dtfranz): This should fail due to multiple bundles
310+
assert.Equal(ct, metav1.ConditionTrue, cond.Status)
311+
//assert.Equal(ct, metav1.ConditionFalse, cond.Status)
312+
//assert.Equal(ct, ocv1alpha1.ReasonResolutionFailed, cond.Reason)
313+
//assert.Contains(ct, cond.Message, "TODO: matching bundles found in multiple catalogs")
314+
//assert.Nil(ct, clusterExtension.Status.ResolvedBundle)
315+
}, pollDuration, pollInterval)
316+
}
317+
280318
func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) {
281319
t.Log("When a cluster extension is installed from a catalog")
282320
t.Log("When resolving upgrade edges")
@@ -293,6 +331,7 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) {
293331
ServiceAccount: ocv1alpha1.ServiceAccountReference{
294332
Name: sa.Name,
295333
},
334+
// No CatalogSelector since this is an exact version match
296335
}
297336
require.NoError(t, c.Create(context.Background(), clusterExtension))
298337
t.Log("By eventually reporting a successful installation")
@@ -435,6 +474,15 @@ func TestClusterExtensionInstallReResolvesWhenCatalogIsPatched(t *testing.T) {
435474
ServiceAccount: ocv1alpha1.ServiceAccountReference{
436475
Name: sa.Name,
437476
},
477+
CatalogSelector: metav1.LabelSelector{
478+
MatchExpressions: []metav1.LabelSelectorRequirement{
479+
{
480+
Key: "olm.operatorframework.io/name",
481+
Operator: metav1.LabelSelectorOpIn,
482+
Values: []string{extensionCatalog.Name},
483+
},
484+
},
485+
},
438486
}
439487
t.Log("It resolves the specified package with correct bundle path")
440488
t.Log("By creating the ClusterExtension resource")
@@ -515,6 +563,9 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) {
515563
ServiceAccount: ocv1alpha1.ServiceAccountReference{
516564
Name: sa.Name,
517565
},
566+
CatalogSelector: metav1.LabelSelector{
567+
MatchLabels: map[string]string{"olm.operatorframework.io/name": extensionCatalog.Name},
568+
},
518569
}
519570
t.Log("It resolves the specified package with correct bundle path")
520571
t.Log("By creating the ClusterExtension resource")
@@ -577,6 +628,9 @@ func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T
577628
ServiceAccount: ocv1alpha1.ServiceAccountReference{
578629
Name: sa.Name,
579630
},
631+
CatalogSelector: metav1.LabelSelector{
632+
MatchLabels: map[string]string{"olm.operatorframework.io/name": extensionCatalog.Name},
633+
},
580634
}
581635
t.Log("It installs the specified package with correct bundle path")
582636
t.Log("By creating the ClusterExtension resource")
@@ -634,6 +688,9 @@ func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed(t *tes
634688
ServiceAccount: ocv1alpha1.ServiceAccountReference{
635689
Name: sa.Name,
636690
},
691+
CatalogSelector: metav1.LabelSelector{
692+
MatchLabels: map[string]string{"olm.operatorframework.io/name": extensionCatalog.Name},
693+
},
637694
}
638695
t.Log("It resolves the specified package with correct bundle path")
639696
t.Log("By creating the ClusterExtension resource")

test/e2e/e2e_suite_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ func TestMain(m *testing.M) {
4444
func createTestCatalog(ctx context.Context, name string, imageRef string) (*catalogd.ClusterCatalog, error) {
4545
catalog := &catalogd.ClusterCatalog{
4646
ObjectMeta: metav1.ObjectMeta{
47-
Name: name,
47+
Name: name,
48+
Labels: map[string]string{"olm.operatorframework.io/name": name},
4849
},
4950
Spec: catalogd.ClusterCatalogSpec{
5051
Source: catalogd.CatalogSource{

0 commit comments

Comments
 (0)