Skip to content
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
17 changes: 15 additions & 2 deletions internal/resolve/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

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"
"sigs.k8s.io/controller-runtime/pkg/client"

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

selector, err := metav1.LabelSelectorAsSelector(&ext.Spec.CatalogSelector)
if err != nil {
return nil, nil, nil, fmt.Errorf("desired catalog selector is invalid: %w", err)
}
// A nothing (empty) seletor selects everything
if selector == labels.Nothing() {
selector = labels.Everything()
}

var versionRangeConstraints *mmsemver.Constraints
if versionRange != "" {
var err error
versionRangeConstraints, err = mmsemver.NewConstraint(versionRange)
if err != nil {
return nil, nil, nil, fmt.Errorf("desired version range %q is invalid: %w", versionRange, err)
Expand All @@ -45,6 +55,9 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx
resolvedDeprecation *declcfg.Deprecation
)

listOptions := []client.ListOption{
client.MatchingLabelsSelector{Selector: selector},
}
if err := r.WalkCatalogsFunc(ctx, packageName, func(ctx context.Context, cat *catalogd.ClusterCatalog, packageFBC *declcfg.DeclarativeConfig, err error) error {
if err != nil {
return fmt.Errorf("error getting package %q from catalog %q: %w", packageName, cat.Name, err)
Expand Down Expand Up @@ -110,7 +123,7 @@ func (r *CatalogResolver) Resolve(ctx context.Context, ext *ocv1alpha1.ClusterEx
resolvedBundle = &thisBundle
resolvedDeprecation = thisDeprecation
return nil
}); err != nil {
}, listOptions...); err != nil {
return nil, nil, nil, fmt.Errorf("error walking catalogs: %w", err)
}

Expand Down
111 changes: 109 additions & 2 deletions internal/resolve/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/rand"
"k8s.io/apimachinery/pkg/util/sets"
featuregatetesting "k8s.io/component-base/featuregate/testing"
Expand Down Expand Up @@ -516,10 +517,22 @@ type getPackageFunc func() (*declcfg.DeclarativeConfig, error)

type staticCatalogWalker map[string]getPackageFunc

func (w staticCatalogWalker) WalkCatalogs(ctx context.Context, _ string, f CatalogWalkFunc, _ ...client.ListOption) error {
func (w staticCatalogWalker) WalkCatalogs(ctx context.Context, _ string, f CatalogWalkFunc, opts ...client.ListOption) error {
for k, v := range w {
cat := &catalogd.ClusterCatalog{
ObjectMeta: metav1.ObjectMeta{Name: k},
ObjectMeta: metav1.ObjectMeta{
Name: k,
Labels: map[string]string{
"olm.operatorframework.io/name": k,
},
},
}
options := client.ListOptions{}
for _, opt := range opts {
opt.ApplyToList(&options)
}
if !options.LabelSelector.Matches(labels.Set(cat.ObjectMeta.Labels)) {
continue
}
fbc, fbcErr := v()
if err := f(ctx, cat, fbc, fbcErr); err != nil {
Expand Down Expand Up @@ -589,3 +602,97 @@ func genPackage(pkg string) *declcfg.DeclarativeConfig {
Deprecations: []declcfg.Deprecation{packageDeprecation(pkg)},
}
}

func TestInvalidClusterExtensionCatalogMatchExpressions(t *testing.T) {
r := CatalogResolver{}
ce := &ocv1alpha1.ClusterExtension{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: ocv1alpha1.ClusterExtensionSpec{
PackageName: "foo",
CatalogSelector: metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "name",
Operator: metav1.LabelSelectorOperator("bad"),
Values: []string{"value"},
},
},
},
},
}
_, _, _, err := r.Resolve(context.Background(), ce, nil)
assert.EqualError(t, err, "desired catalog selector is invalid: \"bad\" is not a valid label selector operator")
}

func TestInvalidClusterExtensionCatalogMatchLabelsName(t *testing.T) {
w := staticCatalogWalker{
"a": func() (*declcfg.DeclarativeConfig, error) { return genPackage("foo"), nil },
}
r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs}
ce := &ocv1alpha1.ClusterExtension{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: ocv1alpha1.ClusterExtensionSpec{
PackageName: "foo",
CatalogSelector: metav1.LabelSelector{
MatchLabels: map[string]string{"": "value"},
},
},
}
_, _, _, err := r.Resolve(context.Background(), ce, nil)
assert.ErrorContains(t, err, "desired catalog selector is invalid: key: Invalid value:")
}

func TestInvalidClusterExtensionCatalogMatchLabelsValue(t *testing.T) {
w := staticCatalogWalker{
"a": func() (*declcfg.DeclarativeConfig, error) { return genPackage("foo"), nil },
}
r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs}
ce := &ocv1alpha1.ClusterExtension{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: ocv1alpha1.ClusterExtensionSpec{
PackageName: "foo",
CatalogSelector: metav1.LabelSelector{
MatchLabels: map[string]string{"name": "&value"},
},
},
}
_, _, _, err := r.Resolve(context.Background(), ce, nil)
assert.ErrorContains(t, err, "desired catalog selector is invalid: values[0][name]: Invalid value:")
}

func TestClusterExtensionMatchLabel(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)()
pkgName := randPkg()
w := staticCatalogWalker{
"a": func() (*declcfg.DeclarativeConfig, error) { return &declcfg.DeclarativeConfig{}, nil },
"b": func() (*declcfg.DeclarativeConfig, error) { return genPackage(pkgName), nil },
}
r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs}
ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce)
ce.Spec.CatalogSelector.MatchLabels = map[string]string{"olm.operatorframework.io/name": "b"}

_, _, _, err := r.Resolve(context.Background(), ce, nil)
require.NoError(t, err)
}

func TestClusterExtensionNoMatchLabel(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)()
pkgName := randPkg()
w := staticCatalogWalker{
"a": func() (*declcfg.DeclarativeConfig, error) { return &declcfg.DeclarativeConfig{}, nil },
"b": func() (*declcfg.DeclarativeConfig, error) { return genPackage(pkgName), nil },
}
r := CatalogResolver{WalkCatalogsFunc: w.WalkCatalogs}
ce := buildFooClusterExtension(pkgName, "", "", ocv1alpha1.UpgradeConstraintPolicyEnforce)
ce.Spec.CatalogSelector.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))
}
57 changes: 57 additions & 0 deletions test/e2e/cluster_extension_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ func TestClusterExtensionInstallRegistry(t *testing.T) {
ServiceAccount: ocv1alpha1.ServiceAccountReference{
Name: sa.Name,
},
CatalogSelector: metav1.LabelSelector{
MatchLabels: map[string]string{"olm.operatorframework.io/name": extensionCatalog.Name},
},
}
t.Log("It resolves the specified package with correct bundle path")
t.Log("By creating the ClusterExtension resource")
Expand Down Expand Up @@ -277,6 +280,41 @@ func TestClusterExtensionInstallRegistry(t *testing.T) {
}, pollDuration, pollInterval)
}

func TestClusterExtensionInstallRegistryMultipleBundles(t *testing.T) {
t.Log("When a cluster extension is installed from a catalog")

clusterExtension, extensionCatalog, sa := testInit(t)
defer testCleanup(t, extensionCatalog, clusterExtension, sa)
defer getArtifactsOutput(t)

clusterExtension.Spec = ocv1alpha1.ClusterExtensionSpec{
PackageName: "prometheus",
InstallNamespace: "default",
ServiceAccount: ocv1alpha1.ServiceAccountReference{
Name: sa.Name,
},
}
t.Log("It resolves to multiple bundle paths")
t.Log("By creating the ClusterExtension resource")
require.NoError(t, c.Create(context.Background(), clusterExtension))

t.Log("By eventually reporting a failed resolution with multiple bundles")
require.EventuallyWithT(t, func(ct *assert.CollectT) {
assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
assert.Len(ct, clusterExtension.Status.Conditions, len(conditionsets.ConditionTypes))
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved)
if !assert.NotNil(ct, cond) {
return
}
// TODO(tmshort/dtfranz): This should fail due to multiple bundles
assert.Equal(ct, metav1.ConditionTrue, cond.Status)
//assert.Equal(ct, metav1.ConditionFalse, cond.Status)
//assert.Equal(ct, ocv1alpha1.ReasonResolutionFailed, cond.Reason)
//assert.Contains(ct, cond.Message, "TODO: matching bundles found in multiple catalogs")
//assert.Nil(ct, clusterExtension.Status.ResolvedBundle)
}, pollDuration, pollInterval)
}

func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) {
t.Log("When a cluster extension is installed from a catalog")
t.Log("When resolving upgrade edges")
Expand All @@ -293,6 +331,7 @@ func TestClusterExtensionBlockInstallNonSuccessorVersion(t *testing.T) {
ServiceAccount: ocv1alpha1.ServiceAccountReference{
Name: sa.Name,
},
// No CatalogSelector since this is an exact version match
}
require.NoError(t, c.Create(context.Background(), clusterExtension))
t.Log("By eventually reporting a successful installation")
Expand Down Expand Up @@ -435,6 +474,15 @@ func TestClusterExtensionInstallReResolvesWhenCatalogIsPatched(t *testing.T) {
ServiceAccount: ocv1alpha1.ServiceAccountReference{
Name: sa.Name,
},
CatalogSelector: metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "olm.operatorframework.io/name",
Operator: metav1.LabelSelectorOpIn,
Values: []string{extensionCatalog.Name},
},
},
},
}
t.Log("It resolves the specified package with correct bundle path")
t.Log("By creating the ClusterExtension resource")
Expand Down Expand Up @@ -515,6 +563,9 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) {
ServiceAccount: ocv1alpha1.ServiceAccountReference{
Name: sa.Name,
},
CatalogSelector: metav1.LabelSelector{
MatchLabels: map[string]string{"olm.operatorframework.io/name": extensionCatalog.Name},
},
}
t.Log("It resolves the specified package with correct bundle path")
t.Log("By creating the ClusterExtension resource")
Expand Down Expand Up @@ -577,6 +628,9 @@ func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T
ServiceAccount: ocv1alpha1.ServiceAccountReference{
Name: sa.Name,
},
CatalogSelector: metav1.LabelSelector{
MatchLabels: map[string]string{"olm.operatorframework.io/name": extensionCatalog.Name},
},
}
t.Log("It installs the specified package with correct bundle path")
t.Log("By creating the ClusterExtension resource")
Expand Down Expand Up @@ -634,6 +688,9 @@ func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed(t *tes
ServiceAccount: ocv1alpha1.ServiceAccountReference{
Name: sa.Name,
},
CatalogSelector: metav1.LabelSelector{
MatchLabels: map[string]string{"olm.operatorframework.io/name": extensionCatalog.Name},
},
}
t.Log("It resolves the specified package with correct bundle path")
t.Log("By creating the ClusterExtension resource")
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ func TestMain(m *testing.M) {
func createTestCatalog(ctx context.Context, name string, imageRef string) (*catalogd.ClusterCatalog, error) {
catalog := &catalogd.ClusterCatalog{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Name: name,
Labels: map[string]string{"olm.operatorframework.io/name": name},
},
Spec: catalogd.ClusterCatalogSpec{
Source: catalogd.CatalogSource{
Expand Down