diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 77a6ef0a7..86246adc2 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -20,6 +20,7 @@ import ( "flag" "os" + "github.com/operator-framework/deppy/pkg/deppy/solver" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" "go.uber.org/zap/zapcore" "k8s.io/apimachinery/pkg/runtime" @@ -33,8 +34,8 @@ import ( catalogd "github.com/operator-framework/catalogd/pkg/apis/core/v1beta1" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/controllers" - "github.com/operator-framework/operator-controller/internal/resolution" "github.com/operator-framework/operator-controller/internal/resolution/entitysources" + "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/olm" ) var ( @@ -93,10 +94,19 @@ func main() { os.Exit(1) } + solver, err := solver.NewDeppySolver( + entitysources.NewCatalogdEntitySource(mgr.GetClient()), + olm.NewOLMVariableSource(mgr.GetClient()), + ) + if err != nil { + setupLog.Error(err, "unable create a solver") + os.Exit(1) + } + if err = (&controllers.OperatorReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Resolver: resolution.NewOperatorResolver(mgr.GetClient(), entitysources.NewCatalogdEntitySource(mgr.GetClient())), + Resolver: solver, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Operator") os.Exit(1) diff --git a/internal/controllers/operator_controller.go b/internal/controllers/operator_controller.go index 0c1f85f1c..c1cb5f595 100644 --- a/internal/controllers/operator_controller.go +++ b/internal/controllers/operator_controller.go @@ -34,10 +34,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/operator-framework/operator-controller/internal/controllers/validators" - operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" - "github.com/operator-framework/operator-controller/internal/resolution" + "github.com/operator-framework/operator-controller/internal/controllers/validators" "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/bundles_and_dependencies" "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity" ) @@ -46,7 +44,7 @@ import ( type OperatorReconciler struct { client.Client Scheme *runtime.Scheme - Resolver *resolution.OperatorResolver + Resolver *solver.DeppySolver } //+kubebuilder:rbac:groups=operators.operatorframework.io,resources=operators,verbs=get;list;watch @@ -117,7 +115,7 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha return ctrl.Result{}, nil } // run resolution - solution, err := r.Resolver.Resolve(ctx) + solution, err := r.Resolver.Solve(ctx) if err != nil { op.Status.InstalledBundleResource = "" setInstalledStatusConditionUnknown(&op.Status.Conditions, "installation has not been attempted as resolution failed", op.GetGeneration()) diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index d20fa5326..67fdd615c 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -8,6 +8,7 @@ import ( . "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" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,7 +22,7 @@ import ( operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/conditionsets" "github.com/operator-framework/operator-controller/internal/controllers" - "github.com/operator-framework/operator-controller/internal/resolution" + "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/olm" ) var _ = Describe("Operator Controller Test", func() { @@ -31,10 +32,12 @@ var _ = Describe("Operator Controller Test", func() { ) BeforeEach(func() { ctx = context.Background() + solver, err := solver.NewDeppySolver(testEntitySource, olm.NewOLMVariableSource(cl)) + Expect(err).NotTo(HaveOccurred()) reconciler = &controllers.OperatorReconciler{ Client: cl, Scheme: sch, - Resolver: resolution.NewOperatorResolver(cl, testEntitySource), + Resolver: solver, } }) When("the operator does not exist", func() { diff --git a/internal/resolution/resolver.go b/internal/resolution/resolver.go deleted file mode 100644 index a074e7bf6..000000000 --- a/internal/resolution/resolver.go +++ /dev/null @@ -1,46 +0,0 @@ -package resolution - -import ( - "context" - - "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) { - operatorList := v1alpha1.OperatorList{} - if err := o.client.List(ctx, &operatorList); err != nil { - return nil, err - } - if len(operatorList.Items) == 0 { - return &solver.Solution{}, nil - } - - olmVariableSource := olm.NewOLMVariableSource(operatorList.Items...) - 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 -} diff --git a/internal/resolution/resolver_test.go b/internal/resolution/resolver_test.go index 25bbc908a..166eff777 100644 --- a/internal/resolution/resolver_test.go +++ b/internal/resolution/resolver_test.go @@ -9,13 +9,14 @@ import ( . "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" 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" + "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/olm" ) func TestOperatorResolver(t *testing.T) { @@ -74,8 +75,10 @@ var _ = Describe("OperatorResolver", func() { } client := FakeClient(resources...) entitySource := input.NewCacheQuerier(testEntityCache) - resolver := resolution.NewOperatorResolver(client, entitySource) - solution, err := resolver.Resolve(context.Background()) + variableSource := olm.NewOLMVariableSource(client) + resolver, err := solver.NewDeppySolver(entitySource, variableSource) + Expect(err).ToNot(HaveOccurred()) + solution, err := resolver.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) // 2 * required package variables + 2 * bundle variables Expect(solution.SelectedVariables()).To(HaveLen(4)) @@ -93,8 +96,10 @@ var _ = Describe("OperatorResolver", func() { var resources []client.Object client := FakeClient(resources...) entitySource := input.NewCacheQuerier(testEntityCache) - resolver := resolution.NewOperatorResolver(client, entitySource) - solution, err := resolver.Resolve(context.Background()) + variableSource := olm.NewOLMVariableSource(client) + resolver, err := solver.NewDeppySolver(entitySource, variableSource) + Expect(err).ToNot(HaveOccurred()) + solution, err := resolver.Solve(context.Background()) Expect(err).ToNot(HaveOccurred()) Expect(solution.SelectedVariables()).To(HaveLen(0)) }) @@ -110,8 +115,10 @@ var _ = Describe("OperatorResolver", func() { } client := FakeClient(resource) entitySource := FailEntitySource{} - resolver := resolution.NewOperatorResolver(client, entitySource) - solution, err := resolver.Resolve(context.Background()) + variableSource := olm.NewOLMVariableSource(client) + resolver, err := solver.NewDeppySolver(entitySource, variableSource) + Expect(err).ToNot(HaveOccurred()) + solution, err := resolver.Solve(context.Background()) Expect(solution).To(BeNil()) Expect(err).To(HaveOccurred()) }) @@ -119,8 +126,10 @@ var _ = Describe("OperatorResolver", func() { 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()) + variableSource := olm.NewOLMVariableSource(client) + resolver, err := solver.NewDeppySolver(entitySource, variableSource) + Expect(err).ToNot(HaveOccurred()) + solution, err := resolver.Solve(context.Background()) Expect(solution).To(BeNil()) Expect(err).To(HaveOccurred()) }) diff --git a/internal/resolution/variable_sources/olm/olm.go b/internal/resolution/variable_sources/olm/olm.go index 97fae6f80..bdeb06083 100644 --- a/internal/resolution/variable_sources/olm/olm.go +++ b/internal/resolution/variable_sources/olm/olm.go @@ -5,6 +5,7 @@ import ( "github.com/operator-framework/deppy/pkg/deppy" "github.com/operator-framework/deppy/pkg/deppy/input" + "sigs.k8s.io/controller-runtime/pkg/client" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/bundles_and_dependencies" @@ -15,20 +16,25 @@ import ( var _ input.VariableSource = &OLMVariableSource{} type OLMVariableSource struct { - operators []operatorsv1alpha1.Operator + client client.Client } -func NewOLMVariableSource(operators ...operatorsv1alpha1.Operator) *OLMVariableSource { +func NewOLMVariableSource(cl client.Client) *OLMVariableSource { return &OLMVariableSource{ - operators: operators, + client: cl, } } func (o *OLMVariableSource) GetVariables(ctx context.Context, entitySource input.EntitySource) ([]deppy.Variable, error) { + operatorList := operatorsv1alpha1.OperatorList{} + if err := o.client.List(ctx, &operatorList); err != nil { + return nil, err + } + var inputVariableSources []input.VariableSource // build required package variable sources - for _, operator := range o.operators { + for _, operator := range operatorList.Items { rps, err := o.requiredPackageFromOperator(&operator) if err != nil { return nil, err diff --git a/internal/resolution/variable_sources/olm/olm_test.go b/internal/resolution/variable_sources/olm/olm_test.go index e3010fb4b..efa7632d2 100644 --- a/internal/resolution/variable_sources/olm/olm_test.go +++ b/internal/resolution/variable_sources/olm/olm_test.go @@ -11,14 +11,25 @@ import ( . "github.com/onsi/gomega" "github.com/operator-framework/deppy/pkg/deppy" "github.com/operator-framework/deppy/pkg/deppy/input" + "github.com/operator-framework/operator-controller/api/v1alpha1" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/bundles_and_dependencies" "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/crd_constraints" "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/olm" "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/required_package" 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" ) +func FakeClient(objects ...client.Object) client.Client { + scheme := runtime.NewScheme() + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() +} + func TestGlobalConstraints(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "OLMVariableSource Suite") @@ -59,7 +70,7 @@ func withVersionRange(versionRange string) opOption { } } -func operator(name string, opts ...opOption) operatorsv1alpha1.Operator { +func operator(name string, opts ...opOption) *operatorsv1alpha1.Operator { op := operatorsv1alpha1.Operator{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -73,7 +84,7 @@ func operator(name string, opts ...opOption) operatorsv1alpha1.Operator { Fail(err.Error()) } } - return op + return &op } var _ = Describe("OLMVariableSource", func() { @@ -84,20 +95,30 @@ var _ = Describe("OLMVariableSource", func() { }) It("should produce RequiredPackage variables", func() { - olmVariableSource := olm.NewOLMVariableSource(operator("prometheus"), operator("packageA")) + cl := FakeClient(operator("prometheus"), operator("packageA")) + + olmVariableSource := olm.NewOLMVariableSource(cl) variables, err := olmVariableSource.GetVariables(context.Background(), testEntitySource) Expect(err).ToNot(HaveOccurred()) packageRequiredVariables := filterVariables[*required_package.RequiredPackageVariable](variables) Expect(packageRequiredVariables).To(HaveLen(2)) - Expect(packageRequiredVariables[0].Identifier()).To(Equal(deppy.IdentifierFromString("required package prometheus"))) - Expect(packageRequiredVariables[0].BundleEntities()).To(HaveLen(2)) - Expect(packageRequiredVariables[1].Identifier()).To(Equal(deppy.IdentifierFromString("required package packageA"))) - Expect(packageRequiredVariables[1].BundleEntities()).To(HaveLen(1)) + Expect(packageRequiredVariables).To(WithTransform(func(bvars []*required_package.RequiredPackageVariable) map[deppy.Identifier]int { + out := map[deppy.Identifier]int{} + for _, variable := range bvars { + out[variable.Identifier()] = len(variable.BundleEntities()) + } + return out + }, Equal(map[deppy.Identifier]int{ + deppy.IdentifierFromString("required package prometheus"): 2, + deppy.IdentifierFromString("required package packageA"): 1, + }))) }) It("should produce BundleVariables variables", func() { - olmVariableSource := olm.NewOLMVariableSource(operator("prometheus"), operator("packageA")) + cl := FakeClient(operator("prometheus"), operator("packageA")) + + olmVariableSource := olm.NewOLMVariableSource(cl) variables, err := olmVariableSource.GetVariables(context.Background(), testEntitySource) Expect(err).ToNot(HaveOccurred()) @@ -109,7 +130,7 @@ var _ = Describe("OLMVariableSource", func() { out = append(out, variable.BundleEntity().Entity) } return out - }, Equal([]*input.Entity{ + }, ConsistOf([]*input.Entity{ entityFromCache("operatorhub/prometheus/0.47.0"), entityFromCache("operatorhub/prometheus/0.37.0"), entityFromCache("operatorhub/packageA/2.0.0"), @@ -117,7 +138,9 @@ var _ = Describe("OLMVariableSource", func() { }) It("should produce version filtered BundleVariables variables", func() { - olmVariableSource := olm.NewOLMVariableSource(operator("prometheus", withVersionRange(">0.40.0")), operator("packageA")) + cl := FakeClient(operator("prometheus", withVersionRange(">0.40.0")), operator("packageA")) + + olmVariableSource := olm.NewOLMVariableSource(cl) variables, err := olmVariableSource.GetVariables(context.Background(), testEntitySource) Expect(err).ToNot(HaveOccurred()) @@ -129,7 +152,7 @@ var _ = Describe("OLMVariableSource", func() { out = append(out, variable.BundleEntity().Entity) } return out - }, Equal([]*input.Entity{ + }, ConsistOf([]*input.Entity{ entityFromCache("operatorhub/prometheus/0.47.0"), // filtered out // entityFromCache("operatorhub/prometheus/0.37.0"), @@ -138,7 +161,9 @@ var _ = Describe("OLMVariableSource", func() { }) It("should produce GlobalConstraints variables", func() { - olmVariableSource := olm.NewOLMVariableSource(operator("prometheus"), operator("packageA")) + cl := FakeClient(operator("prometheus"), operator("packageA")) + + olmVariableSource := olm.NewOLMVariableSource(cl) variables, err := olmVariableSource.GetVariables(context.Background(), testEntitySource) Expect(err).ToNot(HaveOccurred()) @@ -166,7 +191,9 @@ var _ = Describe("OLMVariableSource", func() { }) It("should return an errors when they occur", func() { - olmVariableSource := olm.NewOLMVariableSource(operator("prometheus"), operator("packageA")) + cl := FakeClient(operator("prometheus"), operator("packageA")) + + olmVariableSource := olm.NewOLMVariableSource(cl) _, err := olmVariableSource.GetVariables(context.Background(), FailEntitySource{}) Expect(err).To(HaveOccurred()) })