From 9d88cb00e232ef7df53ffb4a52c05841aa93b4d8 Mon Sep 17 00:00:00 2001 From: dtfranz Date: Fri, 15 Sep 2023 16:33:26 -0700 Subject: [PATCH] Fix tests, bump catalogd, address comments. Signed-off-by: dtfranz --- cmd/resolutioncli/main.go | 14 +- cmd/resolutioncli/variable_source.go | 5 +- go.mod | 1 - internal/catalogmetadata/client/client.go | 4 - internal/controllers/operator_controller.go | 24 +- .../controllers/operator_controller_test.go | 149 +++---- internal/controllers/variable_source.go | 9 +- internal/resolution/variables/bundle.go | 2 +- internal/resolution/variables/bundle_test.go | 58 ++- .../resolution/variables/installed_package.go | 2 +- .../variables/installed_package_test.go | 37 +- .../resolution/variables/required_package.go | 2 +- .../variables/required_package_test.go | 36 +- .../variablesources/bundle_deployment.go | 10 +- .../variablesources/bundle_deployment_test.go | 101 +++-- .../variablesources/bundle_provider.go | 14 + .../bundles_and_dependencies.go | 17 +- .../bundles_and_dependencies_test.go | 400 +++++++++++------- .../variablesources/crd_constraints.go | 2 +- .../variablesources/crd_constraints_test.go | 386 +++++++++-------- .../variablesources/installed_package.go | 15 +- .../variablesources/installed_package_test.go | 127 ++++-- .../resolution/variablesources/operator.go | 9 +- .../variablesources/operator_test.go | 135 +++--- .../variablesources/required_package.go | 9 +- .../variablesources/required_package_test.go | 140 +++--- test/util/fake_catalog_client.go | 31 ++ 27 files changed, 1025 insertions(+), 714 deletions(-) create mode 100644 internal/resolution/variablesources/bundle_provider.go create mode 100644 test/util/fake_catalog_client.go diff --git a/cmd/resolutioncli/main.go b/cmd/resolutioncli/main.go index 5b036eb6c..9350fbb9b 100644 --- a/cmd/resolutioncli/main.go +++ b/cmd/resolutioncli/main.go @@ -145,24 +145,24 @@ func resolve(ctx context.Context, resolver *solver.DeppySolver, packageName stri return "", err } - bundleEntity, err := getBundleEntityFromSolution(solution, packageName) + bundle, err := bundleFromSolution(solution, packageName) if err != nil { return "", err } // Get the bundle image reference for the bundle - return bundleEntity.Image, nil + return bundle.Image, nil } -func getBundleEntityFromSolution(solution *solver.Solution, packageName string) (*catalogmetadata.Bundle, error) { +func bundleFromSolution(solution *solver.Solution, packageName string) (*catalogmetadata.Bundle, error) { for _, variable := range solution.SelectedVariables() { switch v := variable.(type) { case *olmvariables.BundleVariable: - entityPkgName := v.BundleEntity().Package - if packageName == entityPkgName { - return v.BundleEntity(), nil + bundlePkgName := v.Bundle().Package + if packageName == bundlePkgName { + return v.Bundle(), nil } } } - return nil, fmt.Errorf("entity for package %q not found in solution", packageName) + return nil, fmt.Errorf("bundle for package %q not found in solution", packageName) } diff --git a/cmd/resolutioncli/variable_source.go b/cmd/resolutioncli/variable_source.go index 7aa1b4252..cad8db182 100644 --- a/cmd/resolutioncli/variable_source.go +++ b/cmd/resolutioncli/variable_source.go @@ -19,14 +19,13 @@ package main import ( "github.com/operator-framework/deppy/pkg/deppy/input" - catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" ) -func newPackageVariableSource(catalog catalogclient.CatalogClient, packageName, packageVersion, packageChannel string) func(inputVariableSource input.VariableSource) (input.VariableSource, error) { +func newPackageVariableSource(catalogClient *indexRefClient, packageName, packageVersion, packageChannel string) func(inputVariableSource input.VariableSource) (input.VariableSource, error) { return func(inputVariableSource input.VariableSource) (input.VariableSource, error) { pkgSource, err := variablesources.NewRequiredPackageVariableSource( - catalog, + catalogClient, packageName, variablesources.InVersionRange(packageVersion), variablesources.InChannel(packageChannel), diff --git a/go.mod b/go.mod index 5b1416f55..795fe5e8a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/blang/semver/v4 v4.0.0 github.com/go-logr/logr v1.2.4 - github.com/google/go-cmp v0.5.9 github.com/onsi/ginkgo/v2 v2.12.1 github.com/onsi/gomega v1.27.10 github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 diff --git a/internal/catalogmetadata/client/client.go b/internal/catalogmetadata/client/client.go index 45e2b864d..bb48955f8 100644 --- a/internal/catalogmetadata/client/client.go +++ b/internal/catalogmetadata/client/client.go @@ -12,10 +12,6 @@ import ( "github.com/operator-framework/operator-controller/internal/catalogmetadata" ) -type CatalogClient interface { - Bundles(ctx context.Context) ([]*catalogmetadata.Bundle, error) -} - func New(cl client.Client) *Client { return &Client{cl: cl} } diff --git a/internal/controllers/operator_controller.go b/internal/controllers/operator_controller.go index 1136e9467..3b0ef5067 100644 --- a/internal/controllers/operator_controller.go +++ b/internal/controllers/operator_controller.go @@ -154,9 +154,9 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha return ctrl.Result{}, unsat } - // lookup the bundle entity in the solution that corresponds to the + // lookup the bundle in the solution that corresponds to the // Operator's desired package name. - bundleEntity, err := r.getBundleEntityFromSolution(solution, op.Spec.PackageName) + bundle, err := r.bundleFromSolution(solution, op.Spec.PackageName) if err != nil { op.Status.InstalledBundleResource = "" setInstalledStatusConditionUnknown(&op.Status.Conditions, "installation has not been attempted as resolution failed", op.GetGeneration()) @@ -165,11 +165,11 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha return ctrl.Result{}, err } - // Now we can set the Resolved Condition, and the resolvedBundleSource field to the bundleEntity.Image value. - op.Status.ResolvedBundleResource = bundleEntity.Image - setResolvedStatusConditionSuccess(&op.Status.Conditions, fmt.Sprintf("resolved to %q", bundleEntity.Image), op.GetGeneration()) + // Now we can set the Resolved Condition, and the resolvedBundleSource field to the bundle.Image value. + op.Status.ResolvedBundleResource = bundle.Image + setResolvedStatusConditionSuccess(&op.Status.Conditions, fmt.Sprintf("resolved to %q", bundle.Image), op.GetGeneration()) - mediaType, err := bundleEntity.MediaType() + mediaType, err := bundle.MediaType() if err != nil { setInstalledStatusConditionFailed(&op.Status.Conditions, err.Error(), op.GetGeneration()) return ctrl.Result{}, err @@ -181,7 +181,7 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha } // Ensure a BundleDeployment exists with its bundle source from the bundle // image we just looked up in the solution. - dep := r.generateExpectedBundleDeployment(*op, bundleEntity.Image, bundleProvisioner) + dep := r.generateExpectedBundleDeployment(*op, bundle.Image, bundleProvisioner) if err := r.ensureBundleDeployment(ctx, dep); err != nil { // originally Reason: operatorsv1alpha1.ReasonInstallationFailed op.Status.InstalledBundleResource = "" @@ -251,17 +251,17 @@ func mapBDStatusToInstalledCondition(existingTypedBundleDeployment *rukpakv1alph } } -func (r *OperatorReconciler) getBundleEntityFromSolution(solution *solver.Solution, packageName string) (*catalogmetadata.Bundle, error) { +func (r *OperatorReconciler) bundleFromSolution(solution *solver.Solution, packageName string) (*catalogmetadata.Bundle, error) { for _, variable := range solution.SelectedVariables() { switch v := variable.(type) { case *olmvariables.BundleVariable: - entityPkgName := v.BundleEntity().Package - if packageName == entityPkgName { - return v.BundleEntity(), nil + bundlePkgName := v.Bundle().Package + if packageName == bundlePkgName { + return v.Bundle(), nil } } } - return nil, fmt.Errorf("entity for package %q not found in solution", packageName) + return nil, fmt.Errorf("bundle for package %q not found in solution", packageName) } func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundlePath string, bundleProvisioner string) *unstructured.Unstructured { diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index bcc65caef..db35d8599 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -2,13 +2,14 @@ package controllers_test import ( "context" + "encoding/json" "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" "github.com/operator-framework/deppy/pkg/deppy/solver" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/alpha/property" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -20,21 +21,25 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + "github.com/operator-framework/operator-controller/internal/catalogmetadata" "github.com/operator-framework/operator-controller/internal/conditionsets" "github.com/operator-framework/operator-controller/internal/controllers" + testutil "github.com/operator-framework/operator-controller/test/util" ) var _ = Describe("Operator Controller Test", func() { var ( - ctx context.Context - reconciler *controllers.OperatorReconciler + ctx context.Context + fakeCatalogClient testutil.FakeCatalogClient + reconciler *controllers.OperatorReconciler ) BeforeEach(func() { ctx = context.Background() + fakeCatalogClient = testutil.NewFakeCatalogClient(testBundleList) reconciler = &controllers.OperatorReconciler{ Client: cl, Scheme: sch, - Resolver: solver.NewDeppySolver(testEntitySource, controllers.NewVariableSource(cl)), + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), } }) When("the operator does not exist", func() { @@ -575,43 +580,6 @@ var _ = Describe("Operator Controller Test", func() { }) }) }) - When("the selected bundle's image ref cannot be parsed", func() { - const pkgName = "badimage" - BeforeEach(func() { - By("initializing cluster state") - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status and returns an error", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(ContainSubstring(`error determining bundle path for entity`))) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionFailed)) - Expect(cond.Message).To(ContainSubstring(`error determining bundle path for entity`)) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown)) - Expect(cond.Message).To(Equal("installation has not been attempted as resolution failed")) - }) - }) When("the operator specifies a duplicate package", func() { const pkgName = "prometheus" var dupOperator *operatorsv1alpha1.Operator @@ -1080,41 +1048,62 @@ func verifyConditionsInvariants(op *operatorsv1alpha1.Operator) { } } -var testEntitySource = input.NewCacheQuerier(map[deppy.Identifier]input.Entity{ - "operatorhub/prometheus/0.37.0": *input.NewEntity("operatorhub/prometheus/0.37.0", map[string]string{ - "olm.bundle.path": `"quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35"`, - "olm.bundle.channelEntry": `{"name":"prometheus.0.37.0"}`, - "olm.channel": `{"channelName":"beta","priority":0}`, - "olm.package": `{"packageName":"prometheus","version":"0.37.0"}`, - "olm.gvk": `[]`, - }), - "operatorhub/prometheus/0.47.0": *input.NewEntity("operatorhub/prometheus/0.47.0", map[string]string{ - "olm.bundle.path": `"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed"`, - "olm.bundle.channelEntry": `{"name":"prometheus.0.47.0"}`, - "olm.channel": `{"channelName":"beta","priority":0,"replaces":"prometheusoperator.0.37.0"}`, - "olm.package": `{"packageName":"prometheus","version":"0.47.0"}`, - "olm.gvk": `[]`, - }), - "operatorhub/badimage/0.1.0": *input.NewEntity("operatorhub/badimage/0.1.0", map[string]string{ - "olm.bundle.path": `{"name": "quay.io/operatorhubio/badimage:v0.1.0"}`, - "olm.bundle.channelEntry": `{"name":"badimage.0.1.0"}`, - "olm.package": `{"packageName":"badimage","version":"0.1.0"}`, - "olm.gvk": `[]`, - }), - "operatorhub/plain/0.1.0": *input.NewEntity("operatorhub/plain/0.1.0", map[string]string{ - "olm.bundle.path": `"quay.io/operatorhub/plain@sha256:plain"`, - "olm.bundle.channelEntry": `{"name":"plain.0.1.0"}`, - "olm.channel": `{"channelName":"beta","priority":0}`, - "olm.package": `{"packageName":"plain","version":"0.1.0"}`, - "olm.gvk": `[]`, - "olm.bundle.mediatype": `"plain+v0"`, - }), - "operatorhub/badmedia/0.1.0": *input.NewEntity("operatorhub/badmedia/0.1.0", map[string]string{ - "olm.bundle.path": `"quay.io/operatorhub/badmedia@sha256:badmedia"`, - "olm.bundle.channelEntry": `{"name":"badmedia.0.1.0"}`, - "olm.channel": `{"channelName":"beta","priority":0}`, - "olm.package": `{"packageName":"badmedia","version":"0.1.0"}`, - "olm.gvk": `[]`, - "olm.bundle.mediatype": `"badmedia+v1"`, - }), -}) +var betaChannel = catalogmetadata.Channel{Channel: declcfg.Channel{ + Name: "beta", + Entries: []declcfg.ChannelEntry{ + { + Name: "operatorhub/prometheus/0.37.0", + }, + { + Name: "operatorhub/prometheus/0.47.0", + Replaces: "operatorhub/prometheus/0.37.0", + }, + { + Name: "operatorhub/plain/0.1.0", + }, + { + Name: "operatorhub/badmedia/0.1.0", + }, + }, +}} + +var testBundleList = []*catalogmetadata.Bundle{ + {Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/0.37.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"0.37.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, InChannels: []*catalogmetadata.Channel{&betaChannel}}, + {Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/0.47.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"0.47.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, InChannels: []*catalogmetadata.Channel{&betaChannel}}, + {Bundle: declcfg.Bundle{ + Name: "operatorhub/plain/0.1.0", + Package: "plain", + Image: "quay.io/operatorhub/plain@sha256:plain", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"plain","version":"0.1.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + {Type: "olm.bundle.mediatype", Value: json.RawMessage(`"plain+v0"`)}, + }, + }, InChannels: []*catalogmetadata.Channel{&betaChannel}}, + {Bundle: declcfg.Bundle{ + Name: "operatorhub/badmedia/0.1.0", + Package: "badmedia", + Image: "quay.io/operatorhub/badmedia@sha256:badmedia", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"badmedia","version":"0.1.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + {Type: "olm.bundle.mediatype", Value: json.RawMessage(`"badmedia+v1"`)}, + }, + }, InChannels: []*catalogmetadata.Channel{&betaChannel}}, +} diff --git a/internal/controllers/variable_source.go b/internal/controllers/variable_source.go index a4a748111..88608cdfb 100644 --- a/internal/controllers/variable_source.go +++ b/internal/controllers/variable_source.go @@ -21,20 +21,19 @@ import ( "github.com/operator-framework/deppy/pkg/deppy/input" - catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" ) -func NewVariableSource(cl client.Client, catalog catalogclient.CatalogClient) variablesources.NestedVariableSource { +func NewVariableSource(cl client.Client, catalogClient variablesources.BundleProvider) variablesources.NestedVariableSource { return variablesources.NestedVariableSource{ func(inputVariableSource input.VariableSource) (input.VariableSource, error) { - return variablesources.NewOperatorVariableSource(cl, catalog, inputVariableSource), nil + return variablesources.NewOperatorVariableSource(cl, catalogClient, inputVariableSource), nil }, func(inputVariableSource input.VariableSource) (input.VariableSource, error) { - return variablesources.NewBundleDeploymentVariableSource(cl, catalog, inputVariableSource), nil + return variablesources.NewBundleDeploymentVariableSource(cl, catalogClient, inputVariableSource), nil }, func(inputVariableSource input.VariableSource) (input.VariableSource, error) { - return variablesources.NewBundlesAndDepsVariableSource(catalog, inputVariableSource), nil + return variablesources.NewBundlesAndDepsVariableSource(catalogClient, inputVariableSource), nil }, func(inputVariableSource input.VariableSource) (input.VariableSource, error) { return variablesources.NewCRDUniquenessConstraintsVariableSource(inputVariableSource), nil diff --git a/internal/resolution/variables/bundle.go b/internal/resolution/variables/bundle.go index 4be11a634..7b0568ffe 100644 --- a/internal/resolution/variables/bundle.go +++ b/internal/resolution/variables/bundle.go @@ -18,7 +18,7 @@ type BundleVariable struct { dependencies []*catalogmetadata.Bundle } -func (b *BundleVariable) BundleEntity() *catalogmetadata.Bundle { +func (b *BundleVariable) Bundle() *catalogmetadata.Bundle { return b.bundle } diff --git a/internal/resolution/variables/bundle_test.go b/internal/resolution/variables/bundle_test.go index 85d15b762..da29e3742 100644 --- a/internal/resolution/variables/bundle_test.go +++ b/internal/resolution/variables/bundle_test.go @@ -1,37 +1,55 @@ package variables_test import ( + "encoding/json" "testing" "github.com/operator-framework/deppy/pkg/deppy" "github.com/operator-framework/deppy/pkg/deppy/constraint" - "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities" + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" ) func TestBundleVariable(t *testing.T) { - bundleEntity := olmentity.NewBundleEntity(input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })) - dependencies := []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(input.NewEntity("bundle-2", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-3", map[string]string{ - property.TypePackage: `{"packageName": "test-package-3", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - } - bv := olmvariables.NewBundleVariable(bundleEntity, dependencies) - - if bv.BundleEntity() != bundleEntity { - t.Errorf("bundle entity '%v' does not match expected '%v'", bv.BundleEntity(), bundleEntity) + bundle := &catalogmetadata.Bundle{ + InChannels: []*catalogmetadata.Channel{ + {Channel: declcfg.Channel{Name: "stable"}}, + }, + Bundle: declcfg.Bundle{Name: "bundle-1", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + }}, } + dependencies := []*catalogmetadata.Bundle{ + { + InChannels: []*catalogmetadata.Channel{ + {Channel: declcfg.Channel{Name: "stable"}}, + }, + Bundle: declcfg.Bundle{Name: "bundle-2", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + }}, + }, + { + InChannels: []*catalogmetadata.Channel{ + {Channel: declcfg.Channel{Name: "stable"}}, + }, + Bundle: declcfg.Bundle{Name: "bundle-3", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "3.0.0"}`)}, + }}, + }, + } + ids := olmvariables.BundleToBundleVariableIDs(bundle) + if len(ids) != len(bundle.InChannels) { + t.Fatalf("bundle should produce one variable ID per channel; received: %d", len(bundle.InChannels)) + } + bv := olmvariables.NewBundleVariable(ids[0], bundle, dependencies) + + if bv.Bundle() != bundle { + t.Errorf("bundle '%v' does not match expected '%v'", bv.Bundle(), bundle) + } + for i, d := range bv.Dependencies() { if d != dependencies[i] { t.Errorf("dependency[%v] '%v' does not match expected '%v'", i, d, dependencies[i]) diff --git a/internal/resolution/variables/installed_package.go b/internal/resolution/variables/installed_package.go index 6ab86126c..35bd87a01 100644 --- a/internal/resolution/variables/installed_package.go +++ b/internal/resolution/variables/installed_package.go @@ -17,7 +17,7 @@ type InstalledPackageVariable struct { bundles []*catalogmetadata.Bundle } -func (r *InstalledPackageVariable) BundleEntities() []*catalogmetadata.Bundle { +func (r *InstalledPackageVariable) Bundles() []*catalogmetadata.Bundle { return r.bundles } diff --git a/internal/resolution/variables/installed_package_test.go b/internal/resolution/variables/installed_package_test.go index edcb9f5a8..850783a6c 100644 --- a/internal/resolution/variables/installed_package_test.go +++ b/internal/resolution/variables/installed_package_test.go @@ -1,43 +1,42 @@ package variables_test import ( + "encoding/json" "fmt" "testing" "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities" + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" ) func TestInstalledPackageVariable(t *testing.T) { packageName := "test-package" - bundleEntities := []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-2", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-3", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), + + bundles := []*catalogmetadata.Bundle{ + {Bundle: declcfg.Bundle{Name: "bundle-1", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + }}}, + {Bundle: declcfg.Bundle{Name: "bundle-2", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + }}}, + {Bundle: declcfg.Bundle{Name: "bundle-3", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "3.0.0"}`)}, + }}}, } - ipv := olmvariables.NewInstalledPackageVariable(packageName, bundleEntities) + ipv := olmvariables.NewInstalledPackageVariable(packageName, bundles) id := deppy.IdentifierFromString(fmt.Sprintf("installed package %s", packageName)) if ipv.Identifier() != id { t.Errorf("package name '%v' does not match expected '%v'", ipv.Identifier(), id) } - for i, e := range ipv.BundleEntities() { - if e != bundleEntities[i] { - t.Errorf("bundle entity[%v] '%v' does not match expected '%v'", i, e, bundleEntities[i]) + for i, e := range ipv.Bundles() { + if e != bundles[i] { + t.Errorf("bundle[%v] '%v' does not match expected '%v'", i, e, bundles[i]) } } } diff --git a/internal/resolution/variables/required_package.go b/internal/resolution/variables/required_package.go index 3aff97743..368d09c6c 100644 --- a/internal/resolution/variables/required_package.go +++ b/internal/resolution/variables/required_package.go @@ -17,7 +17,7 @@ type RequiredPackageVariable struct { bundles []*catalogmetadata.Bundle } -func (r *RequiredPackageVariable) BundleEntities() []*catalogmetadata.Bundle { +func (r *RequiredPackageVariable) Bundles() []*catalogmetadata.Bundle { return r.bundles } diff --git a/internal/resolution/variables/required_package_test.go b/internal/resolution/variables/required_package_test.go index a0872fa6f..d8ece1473 100644 --- a/internal/resolution/variables/required_package_test.go +++ b/internal/resolution/variables/required_package_test.go @@ -1,43 +1,41 @@ package variables_test import ( + "encoding/json" "fmt" "testing" "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities" + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" ) func TestRequiredPackageVariable(t *testing.T) { packageName := "test-package" - bundleEntities := []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-2", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-3", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), + bundles := []*catalogmetadata.Bundle{ + {Bundle: declcfg.Bundle{Name: "bundle-1", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + }}}, + {Bundle: declcfg.Bundle{Name: "bundle-2", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + }}}, + {Bundle: declcfg.Bundle{Name: "bundle-3", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "3.0.0"}`)}, + }}}, } - rpv := olmvariables.NewRequiredPackageVariable(packageName, bundleEntities) + rpv := olmvariables.NewRequiredPackageVariable(packageName, bundles) id := deppy.IdentifierFromString(fmt.Sprintf("required package %s", packageName)) if rpv.Identifier() != id { t.Errorf("package name '%v' does not match expected '%v'", rpv.Identifier(), id) } - for i, e := range rpv.BundleEntities() { - if e != bundleEntities[i] { - t.Errorf("bundle entity[%v] '%v' does not match expected '%v'", i, e, bundleEntities[i]) + for i, e := range rpv.Bundles() { + if e != bundles[i] { + t.Errorf("bundle entity[%v] '%v' does not match expected '%v'", i, e, bundles[i]) } } diff --git a/internal/resolution/variablesources/bundle_deployment.go b/internal/resolution/variablesources/bundle_deployment.go index 5ce673298..25c287087 100644 --- a/internal/resolution/variablesources/bundle_deployment.go +++ b/internal/resolution/variablesources/bundle_deployment.go @@ -7,22 +7,20 @@ import ( "github.com/operator-framework/deppy/pkg/deppy/input" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" "sigs.k8s.io/controller-runtime/pkg/client" - - catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" ) var _ input.VariableSource = &BundleDeploymentVariableSource{} type BundleDeploymentVariableSource struct { client client.Client - catalog catalogclient.CatalogClient + catalogClient BundleProvider inputVariableSource input.VariableSource } -func NewBundleDeploymentVariableSource(cl client.Client, catalog catalogclient.CatalogClient, inputVariableSource input.VariableSource) *BundleDeploymentVariableSource { +func NewBundleDeploymentVariableSource(cl client.Client, catalogClient BundleProvider, inputVariableSource input.VariableSource) *BundleDeploymentVariableSource { return &BundleDeploymentVariableSource{ client: cl, - catalog: catalog, + catalogClient: catalogClient, inputVariableSource: inputVariableSource, } } @@ -46,7 +44,7 @@ func (o *BundleDeploymentVariableSource) GetVariables(ctx context.Context) ([]de continue } processed[sourceImage.Ref] = struct{}{} - ips, err := NewInstalledPackageVariableSource(o.catalog, bundleDeployment.Spec.Template.Spec.Source.Image.Ref) + ips, err := NewInstalledPackageVariableSource(o.catalogClient, bundleDeployment.Spec.Template.Spec.Source.Image.Ref) if err != nil { return nil, err } diff --git a/internal/resolution/variablesources/bundle_deployment_test.go b/internal/resolution/variablesources/bundle_deployment_test.go index 3eb82321a..0e640210e 100644 --- a/internal/resolution/variablesources/bundle_deployment_test.go +++ b/internal/resolution/variablesources/bundle_deployment_test.go @@ -2,12 +2,18 @@ package variablesources_test import ( "context" + "encoding/json" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/alpha/property" + + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" + testutil "github.com/operator-framework/operator-controller/test/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -16,7 +22,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" ) @@ -47,42 +52,74 @@ func bundleDeployment(name, image string) *rukpakv1alpha1.BundleDeployment { } } -var BundleDeploymentTestEntityCache = map[deppy.Identifier]input.Entity{ - "operatorhub/prometheus/0.37.0": *input.NewEntity("operatorhub/prometheus/0.37.0", map[string]string{ - "olm.bundle.path": `"quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35"`, - "olm.bundle.channelEntry": "{\"name\":\"prometheus.0.37.0\"}", - "olm.channel": "{\"channelName\":\"beta\",\"priority\":0,\"replaces\":\"prometheusoperator.0.32.0\"}", - "olm.gvk": "[{\"group\":\"monitoring.coreos.com\",\"kind\":\"Alertmanager\",\"version\":\"v1\"}, {\"group\":\"monitoring.coreos.com\",\"kind\":\"Prometheus\",\"version\":\"v1\"}]", - "olm.package": "{\"packageName\":\"prometheus\",\"version\":\"0.37.0\"}", - }), - "operatorhub/prometheus/0.47.0": *input.NewEntity("operatorhub/prometheus/0.47.0", map[string]string{ - "olm.bundle.path": `"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed"`, - "olm.bundle.channelEntry": "{\"replaces\":\"prometheus.0.37.0\", \"name\":\"prometheus.0.47.0\"}", - "olm.channel": "{\"channelName\":\"beta\",\"priority\":0,\"replaces\":\"prometheusoperator.0.37.0\"}", - "olm.gvk": "[{\"group\":\"monitoring.coreos.com\",\"kind\":\"Alertmanager\",\"version\":\"v1\"}, {\"group\":\"monitoring.coreos.com\",\"kind\":\"Prometheus\",\"version\":\"v1alpha1\"}]", - "olm.package": "{\"packageName\":\"prometheus\",\"version\":\"0.47.0\"}", - }), - "operatorhub/packageA/2.0.0": *input.NewEntity("operatorhub/packageA/2.0.0", map[string]string{ - "olm.bundle.path": `"foo.io/packageA/packageA:v2.0.0"`, - "olm.bundle.channelEntry": "{\"name\":\"packageA.2.0.0\"}", - "olm.channel": "{\"channelName\":\"stable\",\"priority\":0}", - "olm.gvk": "[{\"group\":\"foo.io\",\"kind\":\"Foo\",\"version\":\"v1\"}]", - "olm.package": "{\"packageName\":\"packageA\",\"version\":\"2.0.0\"}", - }), -} - var _ = Describe("BundleDeploymentVariableSource", func() { - var bundleTestEntityCache input.EntitySource + var fakeCatalogClient testutil.FakeCatalogClient + var betaChannel catalogmetadata.Channel + var stableChannel catalogmetadata.Channel + var testBundleList []*catalogmetadata.Bundle BeforeEach(func() { - bundleTestEntityCache = input.NewCacheQuerier(BundleDeploymentTestEntityCache) + betaChannel = catalogmetadata.Channel{Channel: declcfg.Channel{ + Name: "beta", + Entries: []declcfg.ChannelEntry{ + { + Name: "operatorhub/prometheus/0.37.0", + Replaces: "operatorhub/prometheus/0.32.0", + }, + { + Name: "operatorhub/prometheus/0.47.0", + Replaces: "operatorhub/prometheus/0.37.0", + }, + }, + }} + + stableChannel = catalogmetadata.Channel{Channel: declcfg.Channel{ + Name: "beta", + Entries: []declcfg.ChannelEntry{ + { + Name: "operatorhub/packageA/2.0.0", + }, + }, + }} + + testBundleList = []*catalogmetadata.Bundle{ + {Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/0.37.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"0.37.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"monitoring.coreos.com","kind":"Alertmanager","version":"v1"}, {"group":"monitoring.coreos.com","kind":"Prometheus","version":"v1"}]`)}, + }, + }, InChannels: []*catalogmetadata.Channel{&betaChannel}}, + {Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/0.47.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"0.47.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"monitoring.coreos.com","kind":"Alertmanager","version":"v1"}, {"group":"monitoring.coreos.com","kind":"Prometheus","version":"v1alpha1"}]`)}, + }, + }, InChannels: []*catalogmetadata.Channel{&betaChannel}}, + {Bundle: declcfg.Bundle{ + Name: "operatorhub/packageA/2.0.0", + Package: "packageA", + Image: "foo.io/packageA/packageA:v2.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"packageA","version":"2.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }, + }, InChannels: []*catalogmetadata.Channel{&stableChannel}}, + } + + fakeCatalogClient = testutil.NewFakeCatalogClient(testBundleList) }) It("should produce RequiredPackage variables", func() { cl := BundleDeploymentFakeClient(bundleDeployment("prometheus", "quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35")) - bdVariableSource := variablesources.NewBundleDeploymentVariableSource(cl, &MockRequiredPackageSource{}) - variables, err := bdVariableSource.GetVariables(context.Background(), bundleTestEntityCache) + bdVariableSource := variablesources.NewBundleDeploymentVariableSource(cl, &fakeCatalogClient, &MockRequiredPackageSource{}) + variables, err := bdVariableSource.GetVariables(context.Background()) Expect(err).ToNot(HaveOccurred()) installedPackageVariable := filterVariables[*olmvariables.InstalledPackageVariable](variables) @@ -90,7 +127,7 @@ var _ = Describe("BundleDeploymentVariableSource", func() { Expect(installedPackageVariable).To(WithTransform(func(bvars []*olmvariables.InstalledPackageVariable) map[deppy.Identifier]int { out := map[deppy.Identifier]int{} for _, variable := range bvars { - out[variable.Identifier()] = len(variable.BundleEntities()) + out[variable.Identifier()] = len(variable.Bundles()) } return out }, Equal(map[deppy.Identifier]int{ @@ -102,8 +139,8 @@ var _ = Describe("BundleDeploymentVariableSource", func() { It("should return an error if the bundleDeployment image doesn't match any operator resource", func() { cl := BundleDeploymentFakeClient(bundleDeployment("prometheus", "quay.io/operatorhubio/prometheus@sha256:nonexistent")) - bdVariableSource := variablesources.NewBundleDeploymentVariableSource(cl, &MockRequiredPackageSource{}) - _, err := bdVariableSource.GetVariables(context.Background(), bundleTestEntityCache) + bdVariableSource := variablesources.NewBundleDeploymentVariableSource(cl, &fakeCatalogClient, &MockRequiredPackageSource{}) + _, err := bdVariableSource.GetVariables(context.Background()) Expect(err.Error()).To(Equal("bundleImage \"quay.io/operatorhubio/prometheus@sha256:nonexistent\" not found")) }) }) diff --git a/internal/resolution/variablesources/bundle_provider.go b/internal/resolution/variablesources/bundle_provider.go new file mode 100644 index 000000000..c6517069a --- /dev/null +++ b/internal/resolution/variablesources/bundle_provider.go @@ -0,0 +1,14 @@ +package variablesources + +import ( + "context" + + "github.com/operator-framework/operator-controller/internal/catalogmetadata" +) + +// BundleProvider provides the Bundles method through which we can retrieve +// a list of Bundles from any source, generally from a catalog client of +// some kind. +type BundleProvider interface { + Bundles(ctx context.Context) ([]*catalogmetadata.Bundle, error) +} diff --git a/internal/resolution/variablesources/bundles_and_dependencies.go b/internal/resolution/variablesources/bundles_and_dependencies.go index 5f3d6d136..6b8dcd782 100644 --- a/internal/resolution/variablesources/bundles_and_dependencies.go +++ b/internal/resolution/variablesources/bundles_and_dependencies.go @@ -9,7 +9,6 @@ import ( "github.com/operator-framework/deppy/pkg/deppy/input" "github.com/operator-framework/operator-controller/internal/catalogmetadata" - catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" catalogfilter "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" catalogsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" @@ -18,13 +17,13 @@ import ( var _ input.VariableSource = &BundlesAndDepsVariableSource{} type BundlesAndDepsVariableSource struct { - catalog catalogclient.CatalogClient + catalogClient BundleProvider variableSources []input.VariableSource } -func NewBundlesAndDepsVariableSource(catalog catalogclient.CatalogClient, inputVariableSources ...input.VariableSource) *BundlesAndDepsVariableSource { +func NewBundlesAndDepsVariableSource(catalogClient BundleProvider, inputVariableSources ...input.VariableSource) *BundlesAndDepsVariableSource { return &BundlesAndDepsVariableSource{ - catalog: catalog, + catalogClient: catalogClient, variableSources: inputVariableSources, } } @@ -46,13 +45,13 @@ func (b *BundlesAndDepsVariableSource) GetVariables(ctx context.Context) ([]depp for _, variable := range variables { switch v := variable.(type) { case *olmvariables.RequiredPackageVariable: - bundleQueue = append(bundleQueue, v.BundleEntities()...) + bundleQueue = append(bundleQueue, v.Bundles()...) case *olmvariables.InstalledPackageVariable: - bundleQueue = append(bundleQueue, v.BundleEntities()...) + bundleQueue = append(bundleQueue, v.Bundles()...) } } - allBundles, err := b.catalog.Bundles(ctx) + allBundles, err := b.catalogClient.Bundles(ctx) if err != nil { return nil, err } @@ -74,7 +73,7 @@ func (b *BundlesAndDepsVariableSource) GetVariables(ctx context.Context) ([]depp // get bundle dependencies dependencies, err := b.filterBundleDependencies(allBundles, head) if err != nil { - return nil, fmt.Errorf("could not determine dependencies for entity with id '%s': %w", id, err) + return nil, fmt.Errorf("could not determine dependencies for bundle with id '%s': %w", id, err) } // add bundle dependencies to queue for processing @@ -96,7 +95,7 @@ func (b *BundlesAndDepsVariableSource) filterBundleDependencies(allBundles []*ca // todo(perdasilva): disambiguate between not found and actual errors requiredPackages, _ := bundle.RequiredPackages() for _, requiredPackage := range requiredPackages { - packageDependencyBundles := catalogfilter.Filter(allBundles, catalogfilter.And(catalogfilter.WithPackageName(requiredPackage.PackageName), catalogfilter.InBlangSemverRange(*requiredPackage.SemverRange))) + packageDependencyBundles := catalogfilter.Filter(allBundles, catalogfilter.And(catalogfilter.WithPackageName(requiredPackage.PackageName), catalogfilter.InBlangSemverRange(requiredPackage.SemverRange))) if len(packageDependencyBundles) == 0 { return nil, fmt.Errorf("could not find package dependencies for bundle '%s'", bundle.Name) } diff --git a/internal/resolution/variablesources/bundles_and_dependencies_test.go b/internal/resolution/variablesources/bundles_and_dependencies_test.go index a7ed680d1..0987f9537 100644 --- a/internal/resolution/variablesources/bundles_and_dependencies_test.go +++ b/internal/resolution/variablesources/bundles_and_dependencies_test.go @@ -2,155 +2,244 @@ package variablesources_test import ( "context" + "encoding/json" "errors" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities" + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" + testutil "github.com/operator-framework/operator-controller/test/util" ) var _ = Describe("BundlesAndDepsVariableSource", func() { var ( - bdvs *variablesources.BundlesAndDepsVariableSource - mockEntitySource input.EntitySource + bdvs *variablesources.BundlesAndDepsVariableSource + testBundleList []*catalogmetadata.Bundle + fakeCatalogClient testutil.FakeCatalogClient ) BeforeEach(func() { + channel := catalogmetadata.Channel{Channel: declcfg.Channel{Name: "stable"}} + testBundleList = []*catalogmetadata.Bundle{ + // required package bundles + {Bundle: declcfg.Bundle{ + Name: "bundle-1", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + + {Bundle: declcfg.Bundle{ + Name: "bundle-2", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + {Type: property.TypePackageRequired, Value: json.RawMessage(`[{"packageName": "some-package", "versionRange": ">=1.0.0 <2.0.0"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + + // dependencies + {Bundle: declcfg.Bundle{ + Name: "bundle-4", + Package: "some-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-package", "version": "1.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-5", + Package: "some-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-package", "version": "1.5.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-6", + Package: "some-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-package", "version": "2.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-7", + Package: "some-other-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-other-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-8", + Package: "some-other-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-other-package", "version": "1.5.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"bar.io","kind":"Bar","version":"v1"}]`)}, + {Type: property.TypePackageRequired, Value: json.RawMessage(`[{"packageName": "another-package", "versionRange": "< 2.0.0"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + + // dependencies of dependencies + {Bundle: declcfg.Bundle{ + Name: "bundle-9", Package: "another-package", Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "another-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-10", + Package: "bar-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "bar-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"bar.io","kind":"Bar","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-11", + Package: "bar-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "bar-package", "version": "2.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"bar.io","kind":"Bar","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + + // test-package-2 required package - no dependencies + {Bundle: declcfg.Bundle{ + Name: "bundle-15", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "1.5.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-16", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "2.0.1"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-17", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "3.16.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + + // completely unrelated + {Bundle: declcfg.Bundle{ + Name: "bundle-12", + Package: "unrelated-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "unrelated-package", "version": "2.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-13", + Package: "unrelated-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "unrelated-package-2", "version": "2.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-14", + Package: "unrelated-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "unrelated-package-2", "version": "3.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + } + fakeCatalogClient = testutil.NewFakeCatalogClient(testBundleList) bdvs = variablesources.NewBundlesAndDepsVariableSource( + &fakeCatalogClient, &MockRequiredPackageSource{ ResultSet: []deppy.Variable{ - // must match data in mockEntitySource - olmvariables.NewRequiredPackageVariable("test-package", []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(input.NewEntity("bundle-2", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVKRequired: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - property.TypePackageRequired: `[{"packageName": "some-package", "versionRange": ">=1.0.0 <2.0.0"}]`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVKRequired: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - })), + // must match data in fakeCatalogClient + olmvariables.NewRequiredPackageVariable("test-package", []*catalogmetadata.Bundle{ + {Bundle: declcfg.Bundle{ + Name: "bundle-2", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + {Type: property.TypePackageRequired, Value: json.RawMessage(`[{"packageName": "some-package", "versionRange": ">=1.0.0 <2.0.0"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-1", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, }), }, }, &MockRequiredPackageSource{ ResultSet: []deppy.Variable{ - // must match data in mockEntitySource - olmvariables.NewRequiredPackageVariable("test-package-2", []*olmentity.BundleEntity{ + // must match data in fakeCatalogClient + olmvariables.NewRequiredPackageVariable("test-package-2", []*catalogmetadata.Bundle{ // test-package-2 required package - no dependencies - olmentity.NewBundleEntity(input.NewEntity("bundle-15", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "1.5.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-16", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "2.0.1"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-17", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "3.16.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), + {Bundle: declcfg.Bundle{ + Name: "bundle-15", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "1.5.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-16", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "2.0.1"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "bundle-17", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "3.16.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, }), }, }, ) - mockEntitySource = input.NewCacheQuerier(map[deppy.Identifier]input.Entity{ - // required package bundles - "bundle-1": *input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVKRequired: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - }), - "bundle-2": *input.NewEntity("bundle-2", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVKRequired: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - property.TypePackageRequired: `[{"packageName": "some-package", "versionRange": ">=1.0.0 <2.0.0"}]`, - }), - - // dependencies - "bundle-4": *input.NewEntity("bundle-4", map[string]string{ - property.TypePackage: `{"packageName": "some-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-5": *input.NewEntity("bundle-5", map[string]string{ - property.TypePackage: `{"packageName": "some-package", "version": "1.5.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-6": *input.NewEntity("bundle-6", map[string]string{ - property.TypePackage: `{"packageName": "some-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-7": *input.NewEntity("bundle-7", map[string]string{ - property.TypePackage: `{"packageName": "some-other-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - }), - "bundle-8": *input.NewEntity("bundle-8", map[string]string{ - property.TypePackage: `{"packageName": "some-other-package", "version": "1.5.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - property.TypeGVKRequired: `[{"group":"bar.io","kind":"Bar","version":"v1"}]`, - property.TypePackageRequired: `[{"packageName": "another-package", "versionRange": "< 2.0.0"}]`, - }), - - // dependencies of dependencies - "bundle-9": *input.NewEntity("bundle-9", map[string]string{ - property.TypePackage: `{"packageName": "another-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - }), - "bundle-10": *input.NewEntity("bundle-10", map[string]string{ - property.TypePackage: `{"packageName": "bar-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"bar.io","kind":"Bar","version":"v1"}]`, - }), - "bundle-11": *input.NewEntity("bundle-11", map[string]string{ - property.TypePackage: `{"packageName": "bar-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"bar.io","kind":"Bar","version":"v1"}]`, - }), - - // test-package-2 required package - no dependencies - "bundle-15": *input.NewEntity("bundle-15", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "1.5.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-16": *input.NewEntity("bundle-16", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "2.0.1"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-17": *input.NewEntity("bundle-17", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "3.16.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - - // completely unrelated - "bundle-12": *input.NewEntity("bundle-12", map[string]string{ - property.TypePackage: `{"packageName": "unrelated-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-13": *input.NewEntity("bundle-13", map[string]string{ - property.TypePackage: `{"packageName": "unrelated-package-2", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-14": *input.NewEntity("bundle-14", map[string]string{ - property.TypePackage: `{"packageName": "unrelated-package-2", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - }) }) It("should return bundle variables with correct dependencies", func() { - variables, err := bdvs.GetVariables(context.TODO(), mockEntitySource) + variables, err := bdvs.GetVariables(context.TODO()) Expect(err).NotTo(HaveOccurred()) var bundleVariables []*olmvariables.BundleVariable @@ -166,34 +255,63 @@ var _ = Describe("BundlesAndDepsVariableSource", func() { Expect(bundleVariables).To(WithTransform(CollectBundleVariableIDs, Equal([]string{"bundle-2", "bundle-1", "bundle-15", "bundle-16", "bundle-17", "bundle-5", "bundle-4"}))) // check dependencies for one of the bundles - bundle2 := VariableWithID("bundle-2")(bundleVariables) + bundle2 := VariableWithName("bundle-2")(bundleVariables) // Note: As above, bundle-2 has GVK requirements satisfied by bundles 7, 8, and 9, but they // will not appear in this list as we are not currently taking Required GVKs into account - Expect(bundle2.Dependencies()).To(WithTransform(CollectDeppyEntities, Equal([]*input.Entity{ - input.NewEntity("bundle-5", map[string]string{ - property.TypePackage: `{"packageName": "some-package", "version": "1.5.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - input.NewEntity("bundle-4", map[string]string{ - property.TypePackage: `{"packageName": "some-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - }))) + Expect(bundle2.Dependencies()).To(HaveLen(2)) + Expect(bundle2.Dependencies()[0].Name).To(Equal("bundle-5")) + Expect(bundle2.Dependencies()[1].Name).To(Equal("bundle-4")) }) It("should return error if dependencies not found", func() { - mockEntitySource = input.NewCacheQuerier(map[deppy.Identifier]input.Entity{}) - _, err := bdvs.GetVariables(context.TODO(), mockEntitySource) + emptyCatalogClient := testutil.NewFakeCatalogClient(make([]*catalogmetadata.Bundle, 0)) + + bdvs = variablesources.NewBundlesAndDepsVariableSource( + &emptyCatalogClient, + &MockRequiredPackageSource{ + ResultSet: []deppy.Variable{ + // must match data in fakeCatalogClient + olmvariables.NewRequiredPackageVariable("test-package", []*catalogmetadata.Bundle{ + { + CatalogName: "fake-catalog", + Bundle: declcfg.Bundle{ + Name: "bundle-2", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + {Type: property.TypePackageRequired, Value: json.RawMessage(`[{"packageName": "some-package", "versionRange": ">=1.0.0 <2.0.0"}]`)}, + }, + }, + InChannels: []*catalogmetadata.Channel{{Channel: declcfg.Channel{Name: "stable"}}}, + }, + { + CatalogName: "fake-catalog", + Bundle: declcfg.Bundle{ + Name: "bundle-1", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }, + }, + InChannels: []*catalogmetadata.Channel{{Channel: declcfg.Channel{Name: "stable"}}}, + }, + }), + }, + }, + ) + _, err := bdvs.GetVariables(context.TODO()) Expect(err).To(HaveOccurred()) - Expect(err).To(MatchError("could not determine dependencies for entity with id 'bundle-2': could not find package dependencies for bundle 'bundle-2'")) + Expect(err.Error()).To(ContainSubstring("could not determine dependencies for bundle with id 'fake-catalog-test-package-stable-bundle-2': could not find package dependencies for bundle 'bundle-2'")) }) It("should return error if an inner variable source returns an error", func() { bdvs = variablesources.NewBundlesAndDepsVariableSource( + &fakeCatalogClient, &MockRequiredPackageSource{Error: errors.New("fake error")}, ) - mockEntitySource = input.NewCacheQuerier(map[deppy.Identifier]input.Entity{}) - _, err := bdvs.GetVariables(context.TODO(), mockEntitySource) + _, err := bdvs.GetVariables(context.TODO()) Expect(err).To(HaveOccurred()) Expect(err).To(MatchError("fake error")) }) @@ -204,14 +322,14 @@ type MockRequiredPackageSource struct { Error error } -func (m *MockRequiredPackageSource) GetVariables(_ context.Context, _ input.EntitySource) ([]deppy.Variable, error) { +func (m *MockRequiredPackageSource) GetVariables(_ context.Context) ([]deppy.Variable, error) { return m.ResultSet, m.Error } -func VariableWithID(id deppy.Identifier) func(vars []*olmvariables.BundleVariable) *olmvariables.BundleVariable { +func VariableWithName(name string) func(vars []*olmvariables.BundleVariable) *olmvariables.BundleVariable { return func(vars []*olmvariables.BundleVariable) *olmvariables.BundleVariable { for i := 0; i < len(vars); i++ { - if vars[i].Identifier() == id { + if vars[i].Bundle().Name == name { return vars[i] } } @@ -222,15 +340,7 @@ func VariableWithID(id deppy.Identifier) func(vars []*olmvariables.BundleVariabl func CollectBundleVariableIDs(vars []*olmvariables.BundleVariable) []string { ids := make([]string, 0, len(vars)) for _, v := range vars { - ids = append(ids, v.Identifier().String()) + ids = append(ids, v.Bundle().Name) } return ids } - -func CollectDeppyEntities(vars []*olmentity.BundleEntity) []*input.Entity { - entities := make([]*input.Entity, 0, len(vars)) - for _, v := range vars { - entities = append(entities, v.Entity) - } - return entities -} diff --git a/internal/resolution/variablesources/crd_constraints.go b/internal/resolution/variablesources/crd_constraints.go index 2108a6317..746ed14fa 100644 --- a/internal/resolution/variablesources/crd_constraints.go +++ b/internal/resolution/variablesources/crd_constraints.go @@ -47,7 +47,7 @@ func (g *CRDUniquenessConstraintsVariableSource) GetVariables(ctx context.Contex for _, variable := range variables { switch v := variable.(type) { case *olmvariables.BundleVariable: - bundles := []*catalogmetadata.Bundle{v.BundleEntity()} + bundles := []*catalogmetadata.Bundle{v.Bundle()} bundles = append(bundles, v.Dependencies()...) for _, bundle := range bundles { for _, id := range olmvariables.BundleToBundleVariableIDs(bundle) { diff --git a/internal/resolution/variablesources/crd_constraints_test.go b/internal/resolution/variablesources/crd_constraints_test.go index d88489666..200d673f8 100644 --- a/internal/resolution/variablesources/crd_constraints_test.go +++ b/internal/resolution/variablesources/crd_constraints_test.go @@ -2,115 +2,181 @@ package variablesources_test import ( "context" + "encoding/json" "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities" + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" ) -var bundleSet = map[deppy.Identifier]*input.Entity{ +var channel = catalogmetadata.Channel{Channel: declcfg.Channel{Name: "stable"}} +var bundleSet = map[string]*catalogmetadata.Bundle{ // required package bundles - "bundle-1": input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVKRequired: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - property.TypeGVK: `[{"group":"bit.io","kind":"Bit","version":"v1"}]`, - }), - "bundle-2": input.NewEntity("bundle-2", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVKRequired: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - property.TypePackageRequired: `[{"packageName": "some-package", "versionRange": ">=1.0.0 <2.0.0"}]`, - property.TypeGVK: `[{"group":"bit.io","kind":"Bit","version":"v1"}]`, - }), + "bundle-1": {Bundle: declcfg.Bundle{ + Name: "bundle-1", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"bit.io","kind":"Bit","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-2": {Bundle: declcfg.Bundle{ + Name: "bundle-2", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + {Type: property.TypePackageRequired, Value: json.RawMessage(`[{"packageName": "some-package", "versionRange": ">=1.0.0 <2.0.0"}]`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"bit.io","kind":"Bit","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, // dependencies - "bundle-3": input.NewEntity("bundle-3", map[string]string{ - property.TypePackage: `{"packageName": "some-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"fiz.io","kind":"Fiz","version":"v1"}]`, - }), - "bundle-4": input.NewEntity("bundle-4", map[string]string{ - property.TypePackage: `{"packageName": "some-package", "version": "1.5.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"fiz.io","kind":"Fiz","version":"v1"}]`, - }), - "bundle-5": input.NewEntity("bundle-5", map[string]string{ - property.TypePackage: `{"packageName": "some-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"fiz.io","kind":"Fiz","version":"v1"}]`, - }), - "bundle-6": input.NewEntity("bundle-6", map[string]string{ - property.TypePackage: `{"packageName": "some-other-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - }), - "bundle-7": input.NewEntity("bundle-7", map[string]string{ - property.TypePackage: `{"packageName": "some-other-package", "version": "1.5.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - property.TypeGVKRequired: `[{"group":"bar.io","kind":"Bar","version":"v1"}]`, - property.TypePackageRequired: `[{"packageName": "another-package", "versionRange": "< 2.0.0"}]`, - }), + "bundle-3": {Bundle: declcfg.Bundle{ + Name: "bundle-3", + Package: "some-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"fiz.io","kind":"Fiz","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-4": {Bundle: declcfg.Bundle{ + Name: "bundle-4", + Package: "some-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-package", "version": "1.5.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"fiz.io","kind":"Fiz","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-5": {Bundle: declcfg.Bundle{ + Name: "bundle-5", + Package: "some-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-package", "version": "2.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"fiz.io","kind":"Fiz","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-6": {Bundle: declcfg.Bundle{ + Name: "bundle-6", + Package: "some-other-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-other-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-7": {Bundle: declcfg.Bundle{ + Name: "bundle-7", + Package: "some-other-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "some-other-package", "version": "1.5.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + {Type: property.TypeGVKRequired, Value: json.RawMessage(`[{"group":"bar.io","kind":"Bar","version":"v1"}]`)}, + {Type: property.TypePackageRequired, Value: json.RawMessage(`[{"packageName": "another-package", "versionRange": "< 2.0.0"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, // dependencies of dependencies - "bundle-8": input.NewEntity("bundle-8", map[string]string{ - property.TypePackage: `{"packageName": "another-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"foo.io","kind":"Foo","version":"v1"}]`, - }), - "bundle-9": input.NewEntity("bundle-9", map[string]string{ - property.TypePackage: `{"packageName": "bar-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"bar.io","kind":"Bar","version":"v1"}]`, - }), - "bundle-10": input.NewEntity("bundle-10", map[string]string{ - property.TypePackage: `{"packageName": "bar-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"bar.io","kind":"Bar","version":"v1"}]`, - }), + "bundle-8": {Bundle: declcfg.Bundle{ + Name: "bundle-8", + Package: "another-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "another-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-9": {Bundle: declcfg.Bundle{ + Name: "bundle-9", + Package: "bar-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "bar-package", "version": "1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"bar.io","kind":"Bar","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-10": {Bundle: declcfg.Bundle{ + Name: "bundle-10", + Package: "bar-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "bar-package", "version": "2.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"bar.io","kind":"Bar","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, // test-package-2 required package - no dependencies - "bundle-14": input.NewEntity("bundle-14", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "1.5.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"buz.io","kind":"Buz","version":"v1"}]`, - }), - "bundle-15": input.NewEntity("bundle-15", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "2.0.1"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"buz.io","kind":"Buz","version":"v1"}]`, - }), - "bundle-16": input.NewEntity("bundle-16", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "3.16.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"buz.io","kind":"Buz","version":"v1"}]`, - }), + "bundle-14": {Bundle: declcfg.Bundle{ + Name: "bundle-14", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "1.5.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"buz.io","kind":"Buz","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-15": {Bundle: declcfg.Bundle{ + Name: "bundle-15", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "2.0.1"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"buz.io","kind":"Buz","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-16": {Bundle: declcfg.Bundle{ + Name: "bundle-16", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "3.16.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"buz.io","kind":"Buz","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, // completely unrelated - "bundle-11": input.NewEntity("bundle-11", map[string]string{ - property.TypePackage: `{"packageName": "unrelated-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"buz.io","kind":"Buz","version":"v1alpha1"}]`, - }), - "bundle-12": input.NewEntity("bundle-12", map[string]string{ - property.TypePackage: `{"packageName": "unrelated-package-2", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"buz.io","kind":"Buz","version":"v1alpha1"}]`, - }), - "bundle-13": input.NewEntity("bundle-13", map[string]string{ - property.TypePackage: `{"packageName": "unrelated-package-2", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - property.TypeGVK: `[{"group":"buz.io","kind":"Buz","version":"v1alpha1"}]`, - }), + "bundle-11": {Bundle: declcfg.Bundle{ + Name: "bundle-11", + Package: "unrelated-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "unrelated-package", "version": "2.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"buz.io","kind":"Buz","version":"v1alpha1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-12": {Bundle: declcfg.Bundle{ + Name: "bundle-12", + Package: "unrelated-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "unrelated-package-2", "version": "2.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"buz.io","kind":"Buz","version":"v1alpha1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + "bundle-13": {Bundle: declcfg.Bundle{ + Name: "bundle-13", + Package: "unrelated-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "unrelated-package-2", "version": "3.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"buz.io","kind":"Buz","version":"v1alpha1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, } var _ = Describe("CRDUniquenessConstraintsVariableSource", func() { @@ -118,97 +184,93 @@ var _ = Describe("CRDUniquenessConstraintsVariableSource", func() { inputVariableSource *MockInputVariableSource crdConstraintVariableSource *variablesources.CRDUniquenessConstraintsVariableSource ctx context.Context - entitySource input.EntitySource ) BeforeEach(func() { inputVariableSource = &MockInputVariableSource{} crdConstraintVariableSource = variablesources.NewCRDUniquenessConstraintsVariableSource(inputVariableSource) ctx = context.Background() - - // the entity is not used in this variable source - entitySource = &PanicEntitySource{} }) It("should get variables from the input variable source and create global constraint variables", func() { inputVariableSource.ResultSet = []deppy.Variable{ - olmvariables.NewRequiredPackageVariable("test-package", []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(bundleSet["bundle-2"]), - olmentity.NewBundleEntity(bundleSet["bundle-1"]), + olmvariables.NewRequiredPackageVariable("test-package", []*catalogmetadata.Bundle{ + bundleSet["bundle-2"], + bundleSet["bundle-1"], }), - olmvariables.NewRequiredPackageVariable("test-package-2", []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(bundleSet["bundle-14"]), - olmentity.NewBundleEntity(bundleSet["bundle-15"]), - olmentity.NewBundleEntity(bundleSet["bundle-16"]), + olmvariables.NewRequiredPackageVariable("test-package-2", []*catalogmetadata.Bundle{ + bundleSet["bundle-14"], + bundleSet["bundle-15"], + bundleSet["bundle-16"], }), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-2"]), - []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(bundleSet["bundle-3"]), - olmentity.NewBundleEntity(bundleSet["bundle-4"]), - olmentity.NewBundleEntity(bundleSet["bundle-5"]), - olmentity.NewBundleEntity(bundleSet["bundle-6"]), - olmentity.NewBundleEntity(bundleSet["bundle-7"]), + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-2"])[0], + bundleSet["bundle-2"], + []*catalogmetadata.Bundle{ + bundleSet["bundle-3"], + bundleSet["bundle-4"], + bundleSet["bundle-5"], + bundleSet["bundle-6"], + bundleSet["bundle-7"], }, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-1"]), - []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(bundleSet["bundle-6"]), - olmentity.NewBundleEntity(bundleSet["bundle-7"]), - olmentity.NewBundleEntity(bundleSet["bundle-8"]), + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-1"])[0], + bundleSet["bundle-1"], + []*catalogmetadata.Bundle{ + bundleSet["bundle-6"], + bundleSet["bundle-7"], + bundleSet["bundle-8"], }, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-3"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-3"])[0], + bundleSet["bundle-3"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-4"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-4"])[0], + bundleSet["bundle-4"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-5"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-5"])[0], + bundleSet["bundle-5"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-6"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-6"])[0], + bundleSet["bundle-6"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-7"]), - []*olmentity.BundleEntity{ - olmentity.NewBundleEntity(bundleSet["bundle-8"]), - olmentity.NewBundleEntity(bundleSet["bundle-9"]), - olmentity.NewBundleEntity(bundleSet["bundle-10"]), + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-7"])[0], + bundleSet["bundle-7"], + []*catalogmetadata.Bundle{ + bundleSet["bundle-8"], + bundleSet["bundle-9"], + bundleSet["bundle-10"], }, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-8"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-8"])[0], + bundleSet["bundle-8"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-9"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-9"])[0], + bundleSet["bundle-9"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-10"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-10"])[0], + bundleSet["bundle-10"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-14"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-14"])[0], + bundleSet["bundle-14"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-15"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-15"])[0], + bundleSet["bundle-15"], + []*catalogmetadata.Bundle{}, ), - olmvariables.NewBundleVariable( - olmentity.NewBundleEntity(bundleSet["bundle-16"]), - []*olmentity.BundleEntity{}, + olmvariables.NewBundleVariable(olmvariables.BundleToBundleVariableIDs(bundleSet["bundle-16"])[0], + bundleSet["bundle-16"], + []*catalogmetadata.Bundle{}, ), } - variables, err := crdConstraintVariableSource.GetVariables(ctx, entitySource) + variables, err := crdConstraintVariableSource.GetVariables(ctx) Expect(err).ToNot(HaveOccurred()) // Note: When accounting for GVK Uniqueness (which we are currently not doing), we // would expect to have 26 variables from the 5 unique GVKs (Bar, Bit, Buz, Fiz, Foo). @@ -235,38 +297,18 @@ var _ = Describe("CRDUniquenessConstraintsVariableSource", func() { It("should return an error if input variable source returns an error", func() { inputVariableSource = &MockInputVariableSource{Err: fmt.Errorf("error getting variables")} crdConstraintVariableSource = variablesources.NewCRDUniquenessConstraintsVariableSource(inputVariableSource) - _, err := crdConstraintVariableSource.GetVariables(ctx, entitySource) + _, err := crdConstraintVariableSource.GetVariables(ctx) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("error getting variables")) }) }) -var _ input.EntitySource = &PanicEntitySource{} - -type PanicEntitySource struct{} - -func (p PanicEntitySource) Get(_ context.Context, _ deppy.Identifier) (*input.Entity, error) { - return nil, fmt.Errorf("if you are seeing this it is because the global variable source is calling the entity source - this shouldn't happen") -} - -func (p PanicEntitySource) Filter(_ context.Context, _ input.Predicate) (input.EntityList, error) { - return nil, fmt.Errorf("if you are seeing this it is because the global variable source is calling the entity source - this shouldn't happen") -} - -func (p PanicEntitySource) GroupBy(_ context.Context, _ input.GroupByFunction) (input.EntityListMap, error) { - return nil, fmt.Errorf("if you are seeing this it is because the global variable source is calling the entity source - this shouldn't happen") -} - -func (p PanicEntitySource) Iterate(_ context.Context, _ input.IteratorFunction) error { - return fmt.Errorf("if you are seeing this it is because the global variable source is calling the entity source - this shouldn't happen") -} - type MockInputVariableSource struct { ResultSet []deppy.Variable Err error } -func (m *MockInputVariableSource) GetVariables(_ context.Context, _ input.EntitySource) ([]deppy.Variable, error) { +func (m *MockInputVariableSource) GetVariables(_ context.Context) ([]deppy.Variable, error) { if m.Err != nil { return nil, m.Err } diff --git a/internal/resolution/variablesources/installed_package.go b/internal/resolution/variablesources/installed_package.go index 0dbee55ad..3687c04e5 100644 --- a/internal/resolution/variablesources/installed_package.go +++ b/internal/resolution/variablesources/installed_package.go @@ -8,7 +8,6 @@ import ( "github.com/operator-framework/deppy/pkg/deppy" "github.com/operator-framework/deppy/pkg/deppy/input" - catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" catalogfilter "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" catalogsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort" "github.com/operator-framework/operator-controller/internal/resolution/variables" @@ -17,17 +16,17 @@ import ( var _ input.VariableSource = &InstalledPackageVariableSource{} type InstalledPackageVariableSource struct { - catalog catalogclient.CatalogClient - bundleImage string + catalogClient BundleProvider + bundleImage string } func (r *InstalledPackageVariableSource) GetVariables(ctx context.Context) ([]deppy.Variable, error) { - allBundles, err := r.catalog.Bundles(ctx) + allBundles, err := r.catalogClient.Bundles(ctx) if err != nil { return nil, err } - // find corresponding bundle entity for the installed content + // find corresponding bundle for the installed content resultSet := catalogfilter.Filter(allBundles, catalogfilter.WithBundleImage(r.bundleImage)) if len(resultSet) == 0 { return nil, r.notFoundError() @@ -60,9 +59,9 @@ func (r *InstalledPackageVariableSource) notFoundError() error { return fmt.Errorf("bundleImage %q not found", r.bundleImage) } -func NewInstalledPackageVariableSource(catalog catalogclient.CatalogClient, bundleImage string) (*InstalledPackageVariableSource, error) { +func NewInstalledPackageVariableSource(catalogClient BundleProvider, bundleImage string) (*InstalledPackageVariableSource, error) { return &InstalledPackageVariableSource{ - catalog: catalog, - bundleImage: bundleImage, + catalogClient: catalogClient, + bundleImage: bundleImage, }, nil } diff --git a/internal/resolution/variablesources/installed_package_test.go b/internal/resolution/variablesources/installed_package_test.go index 5581039a4..b224f3714 100644 --- a/internal/resolution/variablesources/installed_package_test.go +++ b/internal/resolution/variablesources/installed_package_test.go @@ -2,75 +2,116 @@ package variablesources_test import ( "context" + "encoding/json" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities" + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" + testutil "github.com/operator-framework/operator-controller/test/util" ) var _ = Describe("InstalledPackageVariableSource", func() { var ( - ipvs *variablesources.InstalledPackageVariableSource - bundleImage string - mockEntitySource input.EntitySource + ipvs *variablesources.InstalledPackageVariableSource + fakeCatalogClient testutil.FakeCatalogClient + bundleImage string ) BeforeEach(func() { + channel := catalogmetadata.Channel{Channel: declcfg.Channel{ + Name: "stable", + Entries: []declcfg.ChannelEntry{ + { + Name: "test-package.v1.0.0", + }, + { + Name: "test-package.v2.0.0", + Replaces: "test-package.v1.0.0", + }, + { + Name: "test-package.v3.0.0", + Replaces: "test-package.v2.0.0", + }, + { + Name: "test-package.v4.0.0", + Replaces: "test-package.v3.0.0", + }, + { + Name: "test-package.v5.0.0", + Replaces: "test-package.v4.0.0", + }, + }, + }} + bundleList := []*catalogmetadata.Bundle{ + {Bundle: declcfg.Bundle{ + Name: "test-package.v1.0.0", + Package: "test-package", + Image: "registry.io/repo/test-package@v1.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "test-package.v3.0.0", + Package: "test-package", + Image: "registry.io/repo/test-package@v3.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "3.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "test-package.v2.0.0", + Package: "test-package", + Image: "registry.io/repo/test-package@v2.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "test-package.v4.0.0", + Package: "test-package", + Image: "registry.io/repo/test-package@v4.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "4.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "test-package.v5.0.0", + Package: "test-package", + Image: "registry.io/repo/test-package@v5.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "5-0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + } var err error bundleImage = "registry.io/repo/test-package@v2.0.0" - ipvs, err = variablesources.NewInstalledPackageVariableSource(bundleImage) + fakeCatalogClient = testutil.NewFakeCatalogClient(bundleList) + ipvs, err = variablesources.NewInstalledPackageVariableSource(&fakeCatalogClient, bundleImage) Expect(err).NotTo(HaveOccurred()) - - mockEntitySource = input.NewCacheQuerier(map[deppy.Identifier]input.Entity{ - "test-package.v1.0.0": *input.NewEntity("test-package.v1.0.0test-packagestable", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - olmentity.PropertyBundlePath: `"registry.io/repo/test-package@v1.0.0"`, - olmentity.PropertyBundleChannelEntry: `{"name": "test-package.v1.0.0"}`, - }), - "test-package.v3.0.0": *input.NewEntity("test-package.v3.0.0test-packagestable", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - olmentity.PropertyBundlePath: `"registry.io/repo/test-package@v3.0.0"`, - olmentity.PropertyBundleChannelEntry: `{"name": "test-package.v3.0.0","replaces": "test-package.v2.0.0"}`, - }), - "test-package.v2.0.0": *input.NewEntity("test-package.v2.0.0test-packagestable", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - olmentity.PropertyBundlePath: `"registry.io/repo/test-package@v2.0.0"`, - olmentity.PropertyBundleChannelEntry: `{"name": "test-package.v2.0.0","replaces": "test-package.v1.0.0"}`, - }), - "test-package.4.0.0": *input.NewEntity("test-package.v4.0.0test-packagestable", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "4.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - olmentity.PropertyBundlePath: `"registry.io/repo/test-package@v4.0.0"`, - olmentity.PropertyBundleChannelEntry: `{"name": "test-package.v4.0.0","replaces": "test-package.v3.0.0"}`, - }), - "test-package.5.0.0": *input.NewEntity("test-package.v5.0.0test-packagestable", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "5-00"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - olmentity.PropertyBundlePath: `"registry.io/repo/test-package@v5.0.0"`, - olmentity.PropertyBundleChannelEntry: `{"name": "test-package.v5.0.0","replaces": "test-package.v4.0.0"}`, - }), - }) }) It("should return the correct package variable", func() { - variables, err := ipvs.GetVariables(context.TODO(), mockEntitySource) + variables, err := ipvs.GetVariables(context.TODO()) Expect(err).NotTo(HaveOccurred()) Expect(variables).To(HaveLen(1)) reqPackageVar, ok := variables[0].(*olmvariables.InstalledPackageVariable) Expect(ok).To(BeTrue()) Expect(reqPackageVar.Identifier()).To(Equal(deppy.IdentifierFromString("installed package test-package"))) - // ensure bundle entities are in version order (high to low) - Expect(reqPackageVar.BundleEntities()[0].ID).To(Equal(deppy.IdentifierFromString("test-package.v3.0.0test-packagestable"))) - Expect(reqPackageVar.BundleEntities()[1].ID).To(Equal(deppy.IdentifierFromString("test-package.v2.0.0test-packagestable"))) + // ensure bundles are in version order (high to low) + Expect(reqPackageVar.Bundles()[0].Name).To(Equal("test-package.v3.0.0")) + Expect(reqPackageVar.Bundles()[1].Name).To(Equal("test-package.v2.0.0")) }) }) diff --git a/internal/resolution/variablesources/operator.go b/internal/resolution/variablesources/operator.go index a57112a4d..19cc88443 100644 --- a/internal/resolution/variablesources/operator.go +++ b/internal/resolution/variablesources/operator.go @@ -4,7 +4,6 @@ import ( "context" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" "github.com/operator-framework/deppy/pkg/deppy" "github.com/operator-framework/deppy/pkg/deppy/input" @@ -15,14 +14,14 @@ var _ input.VariableSource = &OperatorVariableSource{} type OperatorVariableSource struct { client client.Client - catalog catalogclient.CatalogClient + catalogClient BundleProvider inputVariableSource input.VariableSource } -func NewOperatorVariableSource(cl client.Client, catalog catalogclient.CatalogClient, inputVariableSource input.VariableSource) *OperatorVariableSource { +func NewOperatorVariableSource(cl client.Client, catalogClient BundleProvider, inputVariableSource input.VariableSource) *OperatorVariableSource { return &OperatorVariableSource{ client: cl, - catalog: catalog, + catalogClient: catalogClient, inputVariableSource: inputVariableSource, } } @@ -41,7 +40,7 @@ func (o *OperatorVariableSource) GetVariables(ctx context.Context) ([]deppy.Vari // build required package variable sources for _, operator := range operatorList.Items { rps, err := NewRequiredPackageVariableSource( - o.catalog, + o.catalogClient, operator.Spec.PackageName, InVersionRange(operator.Spec.Version), InChannel(operator.Spec.Channel), diff --git a/internal/resolution/variablesources/operator_test.go b/internal/resolution/variablesources/operator_test.go index 0920b4961..426cf82c2 100644 --- a/internal/resolution/variablesources/operator_test.go +++ b/internal/resolution/variablesources/operator_test.go @@ -2,21 +2,28 @@ package variablesources_test import ( "context" - "fmt" + "encoding/json" + "errors" + + "github.com/operator-framework/deppy/pkg/deppy" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/alpha/property" . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" - operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + testutil "github.com/operator-framework/operator-controller/test/util" + + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" ) @@ -27,27 +34,6 @@ func FakeClient(objects ...client.Object) client.Client { return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() } -var testEntityCache = map[deppy.Identifier]input.Entity{ - "operatorhub/prometheus/0.37.0": *input.NewEntity("operatorhub/prometheus/0.37.0", map[string]string{ - "olm.bundle.path": `"quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35"`, - "olm.channel": "{\"channelName\":\"beta\",\"priority\":0,\"replaces\":\"prometheusoperator.0.32.0\"}", - "olm.gvk": "[{\"group\":\"monitoring.coreos.com\",\"kind\":\"Alertmanager\",\"version\":\"v1\"}, {\"group\":\"monitoring.coreos.com\",\"kind\":\"Prometheus\",\"version\":\"v1\"}]", - "olm.package": "{\"packageName\":\"prometheus\",\"version\":\"0.37.0\"}", - }), - "operatorhub/prometheus/0.47.0": *input.NewEntity("operatorhub/prometheus/0.47.0", map[string]string{ - "olm.bundle.path": `"quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed"`, - "olm.channel": "{\"channelName\":\"beta\",\"priority\":0,\"replaces\":\"prometheusoperator.0.37.0\"}", - "olm.gvk": "[{\"group\":\"monitoring.coreos.com\",\"kind\":\"Alertmanager\",\"version\":\"v1\"}, {\"group\":\"monitoring.coreos.com\",\"kind\":\"Prometheus\",\"version\":\"v1alpha1\"}]", - "olm.package": "{\"packageName\":\"prometheus\",\"version\":\"0.47.0\"}", - }), - "operatorhub/packageA/2.0.0": *input.NewEntity("operatorhub/packageA/2.0.0", map[string]string{ - "olm.bundle.path": `"foo.io/packageA/packageA:v2.0.0"`, - "olm.channel": "{\"channelName\":\"stable\",\"priority\":0}", - "olm.gvk": "[{\"group\":\"foo.io\",\"kind\":\"Foo\",\"version\":\"v1\"}]", - "olm.package": "{\"packageName\":\"packageA\",\"version\":\"2.0.0\"}", - }), -} - func operator(name string) *operatorsv1alpha1.Operator { return &operatorsv1alpha1.Operator{ ObjectMeta: metav1.ObjectMeta{ @@ -60,17 +46,78 @@ func operator(name string) *operatorsv1alpha1.Operator { } var _ = Describe("OperatorVariableSource", func() { - var testEntitySource input.EntitySource + var betaChannel catalogmetadata.Channel + var stableChannel catalogmetadata.Channel + var testBundleList []*catalogmetadata.Bundle BeforeEach(func() { - testEntitySource = input.NewCacheQuerier(testEntityCache) + betaChannel = catalogmetadata.Channel{ + Channel: declcfg.Channel{ + Name: "beta", + Entries: []declcfg.ChannelEntry{ + { + Name: "operatorhub/prometheus/0.37.0", + Replaces: "operatorhub/prometheus/0.32.0", + }, + { + Name: "operatorhub/prometheus/0.47.0", + Replaces: "operatorhub/prometheus/0.37.0", + }, + }, + }, + } + + stableChannel = catalogmetadata.Channel{ + Channel: declcfg.Channel{ + Name: "stable", + Entries: []declcfg.ChannelEntry{ + { + Name: "operatorhub/packageA/2.0.0", + }, + }, + }, + } + + testBundleList = []*catalogmetadata.Bundle{ + {Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/0.37.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"0.37.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"monitoring.coreos.com","kind":"Alertmanager","version":"v1"}, {"group":"monitoring.coreos.com","kind":"Prometheus","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&betaChannel}, + }, + {Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/0.47.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"0.47.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"monitoring.coreos.com","kind":"Alertmanager","version":"v1"}, {"group":"monitoring.coreos.com","kind":"Prometheus","version":"v1alpha1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&betaChannel}, + }, + {Bundle: declcfg.Bundle{ + Name: "operatorhub/packageA/2.0.0", + Package: "packageA", + Image: "foo.io/packageA/packageA:v2.0.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"packageA","version":"2.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[{"group":"foo.io","kind":"Foo","version":"v1"}]`)}, + }}, + InChannels: []*catalogmetadata.Channel{&stableChannel}, + }, + } + }) It("should produce RequiredPackage variables", func() { cl := FakeClient(operator("prometheus"), operator("packageA")) - - opVariableSource := variablesources.NewOperatorVariableSource(cl, &MockRequiredPackageSource{}) - variables, err := opVariableSource.GetVariables(context.Background(), testEntitySource) + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + opVariableSource := variablesources.NewOperatorVariableSource(cl, &fakeCatalogClient, &MockRequiredPackageSource{}) + variables, err := opVariableSource.GetVariables(context.Background()) Expect(err).ToNot(HaveOccurred()) packageRequiredVariables := filterVariables[*olmvariables.RequiredPackageVariable](variables) @@ -78,7 +125,7 @@ var _ = Describe("OperatorVariableSource", func() { Expect(packageRequiredVariables).To(WithTransform(func(bvars []*olmvariables.RequiredPackageVariable) map[deppy.Identifier]int { out := map[deppy.Identifier]int{} for _, variable := range bvars { - out[variable.Identifier()] = len(variable.BundleEntities()) + out[variable.Identifier()] = len(variable.Bundles()) } return out }, Equal(map[deppy.Identifier]int{ @@ -89,34 +136,14 @@ var _ = Describe("OperatorVariableSource", func() { It("should return an errors when they occur", func() { cl := FakeClient(operator("prometheus"), operator("packageA")) + fakeCatalogClient := testutil.NewFakeCatalogClientWithError(errors.New("something bad happened")) - opVariableSource := variablesources.NewOperatorVariableSource(cl, nil) - _, err := opVariableSource.GetVariables(context.Background(), FailEntitySource{}) + opVariableSource := variablesources.NewOperatorVariableSource(cl, &fakeCatalogClient, nil) + _, err := opVariableSource.GetVariables(context.Background()) Expect(err).To(HaveOccurred()) }) }) -var _ input.EntitySource = &FailEntitySource{} - -type FailEntitySource struct { -} - -func (f FailEntitySource) Get(_ context.Context, _ deppy.Identifier) (*input.Entity, error) { - return nil, fmt.Errorf("error executing get") -} - -func (f FailEntitySource) Filter(_ context.Context, _ input.Predicate) (input.EntityList, error) { - return nil, fmt.Errorf("error executing filter") -} - -func (f FailEntitySource) GroupBy(_ context.Context, _ input.GroupByFunction) (input.EntityListMap, error) { - return nil, fmt.Errorf("error executing group by") -} - -func (f FailEntitySource) Iterate(_ context.Context, _ input.IteratorFunction) error { - return fmt.Errorf("error executing iterate") -} - func filterVariables[D deppy.Variable](variables []deppy.Variable) []D { var out []D for _, variable := range variables { diff --git a/internal/resolution/variablesources/required_package.go b/internal/resolution/variablesources/required_package.go index 947e6d1f2..568a7ebfa 100644 --- a/internal/resolution/variablesources/required_package.go +++ b/internal/resolution/variablesources/required_package.go @@ -10,7 +10,6 @@ import ( "github.com/operator-framework/deppy/pkg/deppy/input" "github.com/operator-framework/operator-controller/internal/catalogmetadata" - catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client" catalogfilter "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" catalogsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" @@ -47,7 +46,7 @@ func InChannel(channelName string) RequiredPackageVariableSourceOption { } type RequiredPackageVariableSource struct { - catalog catalogclient.CatalogClient + catalogClient BundleProvider packageName string versionRange string @@ -55,12 +54,12 @@ type RequiredPackageVariableSource struct { predicates []catalogfilter.Predicate[catalogmetadata.Bundle] } -func NewRequiredPackageVariableSource(catalog catalogclient.CatalogClient, packageName string, options ...RequiredPackageVariableSourceOption) (*RequiredPackageVariableSource, error) { +func NewRequiredPackageVariableSource(catalogClient BundleProvider, packageName string, options ...RequiredPackageVariableSourceOption) (*RequiredPackageVariableSource, error) { if packageName == "" { return nil, fmt.Errorf("package name must not be empty") } r := &RequiredPackageVariableSource{ - catalog: catalog, + catalogClient: catalogClient, packageName: packageName, predicates: []catalogfilter.Predicate[catalogmetadata.Bundle]{catalogfilter.WithPackageName(packageName)}, @@ -74,7 +73,7 @@ func NewRequiredPackageVariableSource(catalog catalogclient.CatalogClient, packa } func (r *RequiredPackageVariableSource) GetVariables(ctx context.Context) ([]deppy.Variable, error) { - resultSet, err := r.catalog.Bundles(ctx) + resultSet, err := r.catalogClient.Bundles(ctx) if err != nil { return nil, err } diff --git a/internal/resolution/variablesources/required_package_test.go b/internal/resolution/variablesources/required_package_test.go index 834054c70..ae87f874e 100644 --- a/internal/resolution/variablesources/required_package_test.go +++ b/internal/resolution/variablesources/required_package_test.go @@ -2,118 +2,136 @@ package variablesources_test import ( "context" + "encoding/json" + "errors" "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/operator-framework/deppy/pkg/deppy" - "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" - olmentity "github.com/operator-framework/operator-controller/internal/resolution/entities" + "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" + testutil "github.com/operator-framework/operator-controller/test/util" ) var _ = Describe("RequiredPackageVariableSource", func() { var ( - rpvs *variablesources.RequiredPackageVariableSource - packageName string - mockEntitySource input.EntitySource + rpvs *variablesources.RequiredPackageVariableSource + fakeCatalogClient testutil.FakeCatalogClient + packageName string ) BeforeEach(func() { var err error packageName = "test-package" - rpvs, err = variablesources.NewRequiredPackageVariableSource(packageName) - Expect(err).NotTo(HaveOccurred()) - mockEntitySource = input.NewCacheQuerier(map[deppy.Identifier]input.Entity{ - "bundle-1": *input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-2": *input.NewEntity("bundle-2", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-3": *input.NewEntity("bundle-3", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - + channel := catalogmetadata.Channel{Channel: declcfg.Channel{ + Name: "stable", + }} + bundleList := []*catalogmetadata.Bundle{ + {Bundle: declcfg.Bundle{ + Name: "test-package.v1.0.0", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "1.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "test-package.v3.0.0", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "3.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "test-package.v2.0.0", + Package: "test-package", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package", "version": "2.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, // add some bundles from a different package - "bundle-4": *input.NewEntity("bundle-4", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - "bundle-5": *input.NewEntity("bundle-5", map[string]string{ - property.TypePackage: `{"packageName": "test-package-2", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - }), - }) + {Bundle: declcfg.Bundle{ + Name: "test-package-2.v1.0.0", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "1.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + {Bundle: declcfg.Bundle{ + Name: "test-package-2.v2.0.0", + Package: "test-package-2", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName": "test-package-2", "version": "2.0.0"}`)}, + }}, + InChannels: []*catalogmetadata.Channel{&channel}, + }, + } + fakeCatalogClient = testutil.NewFakeCatalogClient(bundleList) + rpvs, err = variablesources.NewRequiredPackageVariableSource(&fakeCatalogClient, packageName) + Expect(err).NotTo(HaveOccurred()) }) It("should return the correct package variable", func() { - variables, err := rpvs.GetVariables(context.TODO(), mockEntitySource) + variables, err := rpvs.GetVariables(context.TODO()) Expect(err).NotTo(HaveOccurred()) Expect(variables).To(HaveLen(1)) reqPackageVar, ok := variables[0].(*olmvariables.RequiredPackageVariable) Expect(ok).To(BeTrue()) Expect(reqPackageVar.Identifier()).To(Equal(deppy.IdentifierFromString(fmt.Sprintf("required package %s", packageName)))) - - // ensure bundle entities are in version order (high to low) - Expect(reqPackageVar.BundleEntities()).To(Equal([]*olmentity.BundleEntity{ - olmentity.NewBundleEntity(input.NewEntity("bundle-2", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "3.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-3", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "2.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - olmentity.NewBundleEntity(input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`})), - })) + Expect(reqPackageVar.Bundles()).To(HaveLen(3)) + // ensure bundles are in version order (high to low) + Expect(reqPackageVar.Bundles()[0].Name).To(Equal("test-package.v3.0.0")) + Expect(reqPackageVar.Bundles()[1].Name).To(Equal("test-package.v2.0.0")) + Expect(reqPackageVar.Bundles()[2].Name).To(Equal("test-package.v1.0.0")) }) It("should filter by version range", func() { // recreate source with version range option var err error - rpvs, err = variablesources.NewRequiredPackageVariableSource(packageName, variablesources.InVersionRange(">=1.0.0 !=2.0.0 <3.0.0")) + rpvs, err = variablesources.NewRequiredPackageVariableSource(&fakeCatalogClient, packageName, variablesources.InVersionRange(">=1.0.0 !=2.0.0 <3.0.0")) Expect(err).NotTo(HaveOccurred()) - variables, err := rpvs.GetVariables(context.TODO(), mockEntitySource) + variables, err := rpvs.GetVariables(context.TODO()) Expect(err).NotTo(HaveOccurred()) Expect(variables).To(HaveLen(1)) reqPackageVar, ok := variables[0].(*olmvariables.RequiredPackageVariable) Expect(ok).To(BeTrue()) Expect(reqPackageVar.Identifier()).To(Equal(deppy.IdentifierFromString(fmt.Sprintf("required package %s", packageName)))) - // ensure bundle entities are in version order (high to low) - Expect(reqPackageVar.BundleEntities()).To(Equal([]*olmentity.BundleEntity{ - olmentity.NewBundleEntity(input.NewEntity("bundle-1", map[string]string{ - property.TypePackage: `{"packageName": "test-package", "version": "1.0.0"}`, - property.TypeChannel: `{"channelName":"stable","priority":0}`, - })), - })) + Expect(reqPackageVar.Bundles()).To(HaveLen(1)) + // test-package.v1.0.0 is the only package that matches the provided filter + Expect(reqPackageVar.Bundles()[0].Name).To(Equal("test-package.v1.0.0")) }) It("should fail with bad semver range", func() { - _, err := variablesources.NewRequiredPackageVariableSource(packageName, variablesources.InVersionRange("not a valid semver")) + _, err := variablesources.NewRequiredPackageVariableSource(&fakeCatalogClient, packageName, variablesources.InVersionRange("not a valid semver")) Expect(err).To(HaveOccurred()) }) It("should return an error if package not found", func() { - mockEntitySource := input.NewCacheQuerier(map[deppy.Identifier]input.Entity{}) - _, err := rpvs.GetVariables(context.TODO(), mockEntitySource) + emptyCatalogClient := testutil.NewFakeCatalogClient([]*catalogmetadata.Bundle{}) + rpvs, err := variablesources.NewRequiredPackageVariableSource(&emptyCatalogClient, packageName) + Expect(err).NotTo(HaveOccurred()) + _, err = rpvs.GetVariables(context.TODO()) Expect(err).To(HaveOccurred()) Expect(err).To(MatchError("package 'test-package' not found")) }) - It("should return an error if entity source errors", func() { - _, err := rpvs.GetVariables(context.TODO(), FailEntitySource{}) + It("should return an error if catalog client errors", func() { + testError := errors.New("something bad happened") + emptyCatalogClient := testutil.NewFakeCatalogClientWithError(testError) + rpvs, err := variablesources.NewRequiredPackageVariableSource(&emptyCatalogClient, packageName, variablesources.InVersionRange(">=1.0.0 !=2.0.0 <3.0.0")) + Expect(err).NotTo(HaveOccurred()) + _, err = rpvs.GetVariables(context.TODO()) Expect(err).To(HaveOccurred()) - Expect(err).To(MatchError("error executing filter")) + Expect(err).To(MatchError(testError)) }) }) diff --git a/test/util/fake_catalog_client.go b/test/util/fake_catalog_client.go new file mode 100644 index 000000000..20c3c8b29 --- /dev/null +++ b/test/util/fake_catalog_client.go @@ -0,0 +1,31 @@ +package testutil + +import ( + "context" + + "github.com/operator-framework/operator-controller/internal/catalogmetadata" +) + +type FakeCatalogClient struct { + bundles []*catalogmetadata.Bundle + err error +} + +func NewFakeCatalogClient(b []*catalogmetadata.Bundle) FakeCatalogClient { + return FakeCatalogClient{ + bundles: b, + } +} + +func NewFakeCatalogClientWithError(e error) FakeCatalogClient { + return FakeCatalogClient{ + err: e, + } +} + +func (c *FakeCatalogClient) Bundles(_ context.Context) ([]*catalogmetadata.Bundle, error) { + if c.err != nil { + return nil, c.err + } + return c.bundles, nil +}