diff --git a/cmd/resolutioncli/main.go b/cmd/resolutioncli/main.go index f368a720d..413c4a66f 100644 --- a/cmd/resolutioncli/main.go +++ b/cmd/resolutioncli/main.go @@ -151,24 +151,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/internal/catalogmetadata/types_test.go b/internal/catalogmetadata/types_test.go index cc612d105..ec2dc3a96 100644 --- a/internal/catalogmetadata/types_test.go +++ b/internal/catalogmetadata/types_test.go @@ -123,7 +123,13 @@ func TestBundleRequiredPackages(t *testing.T) { } { t.Run(tt.name, func(t *testing.T) { packages, err := tt.bundle.RequiredPackages() - assert.Equal(t, tt.wantRequiredPackages, packages) + assert.Equal(t, len(tt.wantRequiredPackages), len(packages)) + for i, pkg := range packages { + // Must custom compare due to Semver function type + assert.Equal(t, tt.wantRequiredPackages[i].PackageRequired, pkg.PackageRequired) + assert.Equal(t, tt.wantRequiredPackages[i].PackageName, pkg.PackageName) + assert.Equal(t, tt.wantRequiredPackages[i].VersionRange, pkg.VersionRange) + } if tt.wantErr != "" { assert.EqualError(t, err, tt.wantErr) } else { 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/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..9ac944f7b 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-2", 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..f0c09c2ed 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-2", 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_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/bundles_and_dependencies.go b/internal/resolution/variablesources/bundles_and_dependencies.go index 5f3d6d136..5b15a4724 100644 --- a/internal/resolution/variablesources/bundles_and_dependencies.go +++ b/internal/resolution/variablesources/bundles_and_dependencies.go @@ -46,9 +46,9 @@ 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()...) } } @@ -74,7 +74,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 diff --git a/internal/resolution/variablesources/bundles_and_dependencies_test.go b/internal/resolution/variablesources/bundles_and_dependencies_test.go index a7ed680d1..604ce5f95 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,57 @@ 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{ + {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"}}}, + }, + {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 '-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 +316,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 +334,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..76445cb13 100644 --- a/internal/resolution/variablesources/installed_package.go +++ b/internal/resolution/variablesources/installed_package.go @@ -27,7 +27,7 @@ func (r *InstalledPackageVariableSource) GetVariables(ctx context.Context) ([]de 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() 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_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_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 +}