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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-air/gini v1.0.4 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
Expand Down
38 changes: 0 additions & 38 deletions internal/resolution/deppy.go

This file was deleted.

55 changes: 55 additions & 0 deletions internal/resolution/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package resolution

import (
"context"
"fmt"

"github.com/operator-framework/deppy/pkg/deppy/input"
"github.com/operator-framework/deppy/pkg/deppy/solver"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/operator-framework/operator-controller/api/v1alpha1"
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/olm"
)

type OperatorResolver struct {
entitySource input.EntitySource
client client.Client
}

func NewOperatorResolver(client client.Client, entitySource input.EntitySource) *OperatorResolver {
return &OperatorResolver{
entitySource: entitySource,
client: client,
}
}

func (o *OperatorResolver) Resolve(ctx context.Context) (solver.Solution, error) {
packageNames, err := o.getPackageNames(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get package names for resolution: %w", err)
}
olmVariableSource := olm.NewOLMVariableSource(packageNames...)
deppySolver, err := solver.NewDeppySolver(o.entitySource, olmVariableSource)
if err != nil {
return nil, err
}

solution, err := deppySolver.Solve(ctx)
if err != nil {
return nil, err
}
return solution, nil
}

func (o *OperatorResolver) getPackageNames(ctx context.Context) ([]string, error) {
operatorList := v1alpha1.OperatorList{}
if err := o.client.List(ctx, &operatorList); err != nil {
return nil, err
}
var packageNames []string
for _, operator := range operatorList.Items {
packageNames = append(packageNames, operator.Spec.PackageName)
}
return packageNames, nil
}
183 changes: 183 additions & 0 deletions internal/resolution/resolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package resolution_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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/operator-framework/operator-controller/api/v1alpha1"
"github.com/operator-framework/operator-controller/internal/resolution"
)

func TestOperatorResolver(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Operator Resolver Suite")
}

func FakeClient(objects ...client.Object) client.Client {
scheme := runtime.NewScheme()
if err := v1alpha1.AddToScheme(scheme); err != nil {
panic(fmt.Sprintf("error creating fake client: %s", err))
}
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\"}",
}),
}

var _ = Describe("OperatorResolver", func() {
It("should resolve the packages described by the available Operator resources", func() {
resources := []client.Object{
&v1alpha1.Operator{
ObjectMeta: metav1.ObjectMeta{
Name: "prometheus",
},
Spec: v1alpha1.OperatorSpec{
PackageName: "prometheus",
},
},
&v1alpha1.Operator{
ObjectMeta: metav1.ObjectMeta{
Name: "packageA",
},
Spec: v1alpha1.OperatorSpec{
PackageName: "packageA",
},
},
}
client := FakeClient(resources...)
entitySource := input.NewCacheQuerier(testEntityCache)
resolver := resolution.NewOperatorResolver(client, entitySource)
solution, err := resolver.Resolve(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(solution).To(HaveLen(3))
Expect(solution["operatorhub/packageA/2.0.0"]).To(BeTrue())
Expect(solution["operatorhub/prometheus/0.47.0"]).To(BeTrue())
Expect(solution["operatorhub/prometheus/0.37.0"]).To(BeFalse())
})

It("should not return an error if there are not Operator resources", func() {
var resources []client.Object
client := FakeClient(resources...)
entitySource := input.NewCacheQuerier(testEntityCache)
resolver := resolution.NewOperatorResolver(client, entitySource)
solution, err := resolver.Resolve(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(solution).To(HaveLen(0))
})

It("should return an error if the entity source throws an error", func() {
resource := &v1alpha1.Operator{
ObjectMeta: metav1.ObjectMeta{
Name: "prometheus",
},
Spec: v1alpha1.OperatorSpec{
PackageName: "prometheus",
},
}
client := FakeClient(resource)
entitySource := FailEntitySource{}
resolver := resolution.NewOperatorResolver(client, entitySource)
solution, err := resolver.Resolve(context.Background())
Expect(solution).To(BeNil())
Expect(err).To(HaveOccurred())
})

It("should return an error if the client throws an error", func() {
client := NewFailClientWithError(fmt.Errorf("something bad happened"))
entitySource := input.NewCacheQuerier(testEntityCache)
resolver := resolution.NewOperatorResolver(client, entitySource)
solution, err := resolver.Resolve(context.Background())
Expect(solution).To(BeNil())
Expect(err).To(HaveOccurred())
})
})

var _ input.EntitySource = &FailEntitySource{}

type FailEntitySource struct{}

func (f FailEntitySource) Get(ctx context.Context, id deppy.Identifier) *input.Entity {
return nil
}

func (f FailEntitySource) Filter(ctx context.Context, filter input.Predicate) (input.EntityList, error) {
return nil, fmt.Errorf("error calling filter in entity source")
}

func (f FailEntitySource) GroupBy(ctx context.Context, fn input.GroupByFunction) (input.EntityListMap, error) {
return nil, fmt.Errorf("error calling group by in entity source")
}

func (f FailEntitySource) Iterate(ctx context.Context, fn input.IteratorFunction) error {
return fmt.Errorf("error calling iterate in entity source")
}

var _ client.Client = &FailClient{}

type FailClient struct {
client.Client
err error
}

func NewFailClientWithError(err error) client.Client {
return &FailClient{
Client: FakeClient(),
err: err,
}
}

func (f FailClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
return f.err
}

func (f FailClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
return f.err
}

func (f FailClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
return f.err
}

func (f FailClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
return f.err
}

func (f FailClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
return f.err
}

func (f FailClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
return f.err
}

func (f FailClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
return f.err
}