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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/operator-framework/deppy/pkg/deppy/input"
olmentity "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
"github.com/operator-framework/operator-registry/alpha/property"

olmentity "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
)

func TestBundleEntity(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package required_package

import (
"context"
"fmt"

"github.com/operator-framework/deppy/pkg/deppy"
"github.com/operator-framework/deppy/pkg/deppy/constraint"
"github.com/operator-framework/deppy/pkg/deppy/input"

olmentity "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/utils/predicates"
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/utils/sort"
)

type RequiredPackageVariable struct {
*input.SimpleVariable
bundleEntities []*olmentity.BundleEntity
}

func (r *RequiredPackageVariable) BundleEntities() []*olmentity.BundleEntity {
return r.bundleEntities
}

func NewRequiredPackageVariable(packageName string, bundleEntities []*olmentity.BundleEntity) *RequiredPackageVariable {
id := deppy.IdentifierFromString(fmt.Sprintf("required package %s", packageName))
var entityIDs []deppy.Identifier
for _, bundle := range bundleEntities {
entityIDs = append(entityIDs, bundle.ID)
}
return &RequiredPackageVariable{
SimpleVariable: input.NewSimpleVariable(id, constraint.Mandatory(), constraint.Dependency(entityIDs...)),
bundleEntities: bundleEntities,
}
}

var _ input.VariableSource = &RequiredPackageVariableSource{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I get this construct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ensuring that RequiredPackageVariableSource correctly implements input.VariableSource: https://go.dev/doc/effective_go#blank_implements


type RequiredPackageVariableSource struct {
packageName string
}

func NewRequiredPackage(packageName string) *RequiredPackageVariableSource {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If RequiredPackageVariableSource is exported, why do I need this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just being more idiomatic, I suppose. Plus if something changes in the initialization we don't need to refactor every call.

return &RequiredPackageVariableSource{
packageName: packageName,
}
}

func (r *RequiredPackageVariableSource) GetVariables(ctx context.Context, entitySource input.EntitySource) ([]deppy.Variable, error) {
resultSet, err := entitySource.Filter(ctx, predicates.WithPackageName(r.packageName))
if err != nil {
return nil, err
}
if len(resultSet) == 0 {
return nil, fmt.Errorf("package '%s' not found", r.packageName)
}
resultSet = resultSet.Sort(sort.ByChannelAndVersion)
var bundleEntities []*olmentity.BundleEntity
for i := 0; i < len(resultSet); i++ {
bundleEntities = append(bundleEntities, olmentity.NewBundleEntity(&resultSet[i]))
}
return []deppy.Variable{
NewRequiredPackageVariable(r.packageName, bundleEntities),
}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package required_package_test

import (
"context"
"fmt"
"testing"

. "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/property"

olmentity "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/required_package"
)

func TestRequiredPackage(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "RequiredPackageVariableSource Suite")
}

var _ = Describe("RequiredPackageVariable", func() {
var (
rpv *required_package.RequiredPackageVariable
packageName string
bundleEntities []*olmentity.BundleEntity
)

BeforeEach(func() {
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}`,
})),
}
rpv = required_package.NewRequiredPackageVariable(packageName, bundleEntities)
})

It("should return the correct package name", func() {
Expect(rpv.Identifier()).To(Equal(deppy.IdentifierFromString(fmt.Sprintf("required package %s", packageName))))
})

It("should return the correct bundle entities", func() {
Expect(rpv.BundleEntities()).To(Equal(bundleEntities))
})

It("should contain both mandatory and dependency constraints", func() {
// TODO: add this test once https://github.com/operator-framework/deppy/pull/85 gets merged
// then we'll be able to inspect constraint types
})
})

var _ = Describe("RequiredPackageVariableSource", func() {
var (
rpvs *required_package.RequiredPackageVariableSource
packageName string
mockEntitySource input.EntitySource
)

BeforeEach(func() {
packageName = "test-package"
rpvs = required_package.NewRequiredPackage(packageName)
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}`,
}),

// 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}`,
}),
})
})

It("should return the correct package variable", func() {
variables, err := rpvs.GetVariables(context.TODO(), mockEntitySource)
Expect(err).NotTo(HaveOccurred())
Expect(len(variables)).To(Equal(1))
reqPackageVar, ok := variables[0].(*required_package.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
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}`})),
}))
})

It("should return an error if package not found", func() {
mockEntitySource := input.NewCacheQuerier(map[deppy.Identifier]input.Entity{})
_, err := rpvs.GetVariables(context.TODO(), mockEntitySource)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("package 'test-package' not found"))
})
})