diff --git a/internal/controllers/admission_test.go b/internal/controllers/admission_test.go index 60b106664..6f0c41c93 100644 --- a/internal/controllers/admission_test.go +++ b/internal/controllers/admission_test.go @@ -2,9 +2,9 @@ package controllers_test import ( "context" + "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" @@ -13,165 +13,211 @@ import ( func operator(spec operatorsv1alpha1.OperatorSpec) *operatorsv1alpha1.Operator { return &operatorsv1alpha1.Operator{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-operator", + GenerateName: "test-operator", }, Spec: spec, } } -var _ = Describe("Operator Spec Validations", func() { - var ( - ctx context.Context - cancel context.CancelFunc - ) - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - }) - AfterEach(func() { - cancel() - }) - It("should fail if the spec is empty", func() { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{})) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("spec.packageName in body should match '^[a-z0-9]+(-[a-z0-9]+)*$'")) - }) - It("should fail if package name length is greater than 48 characters", func() { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ +var operatorData = []struct { + spec *operatorsv1alpha1.Operator + comment string + errMsg string +}{ + { + operator(operatorsv1alpha1.OperatorSpec{}), + "operator spec is empty", + "spec.packageName in body should match '^[a-z0-9]+(-[a-z0-9]+)*$'", + }, + { + operator(operatorsv1alpha1.OperatorSpec{ PackageName: "this-is-a-really-long-package-name-that-is-greater-than-48-characters", - })) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Too long: may not be longer than 48")) - }) - It("should fail if version is valid semver but length is greater than 64 characters", func() { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ + }), + "long package name", + "spec.packageName: Too long: may not be longer than 48", + }, + { + operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", Version: "1234567890.1234567890.12345678901234567890123456789012345678901234", - })) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Too long: may not be longer than 64")) - }) - It("should fail if an invalid semver is given", func() { - invalidSemvers := []string{ - "1.2.3.4", - "1.02.3", - "1.2.03", - "1.2.3-beta!", - "1.2.3.alpha", - "1..2.3", - "1.2.3-pre+bad_metadata", - "1.2.-3", - ".1.2.3", - "<<1.2.3", - ">>1.2.3", - ">~1.2.3", - "==1.2.3", - "=!1.2.3", - "!1.2.3", - "1.Y", - ">1.2.3 && <2.3.4", - ">1.2.3;<2.3.4", - "1.2.3 - 2.3.4", - } - for _, invalidSemver := range invalidSemvers { + }), + "long valid semver", + "spec.version: Too long: may not be longer than 64", + }, + { + operator(operatorsv1alpha1.OperatorSpec{ + PackageName: "package", + Channel: "longname01234567890123456789012345678901234567890", + }), + "long channel name", + "spec.channel: Too long: may not be longer than 48", + }, +} + +func TestOperatorSpecs(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + for _, od := range operatorData { + d := od + t.Run(d.comment, func(t *testing.T) { + t.Parallel() + cl := newClient(t) + err := cl.Create(ctx, d.spec) + require.Error(t, err) + require.ErrorContains(t, err, d.errMsg) + }) + } +} + +func TestOperatorInvalidSemver(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + invalidSemvers := []string{ + "1.2.3.4", + "1.02.3", + "1.2.03", + "1.2.3-beta!", + "1.2.3.alpha", + "1..2.3", + "1.2.3-pre+bad_metadata", + "1.2.-3", + ".1.2.3", + "<<1.2.3", + ">>1.2.3", + ">~1.2.3", + "==1.2.3", + "=!1.2.3", + "!1.2.3", + "1.Y", + ">1.2.3 && <2.3.4", + ">1.2.3;<2.3.4", + "1.2.3 - 2.3.4", + } + for _, sm := range invalidSemvers { + d := sm + t.Run(d, func(t *testing.T) { + t.Parallel() + cl := newClient(t) err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", - Version: invalidSemver, + Version: d, })) - - Expect(err).To(HaveOccurred(), "expected error for invalid semver %q", invalidSemver) + require.Errorf(t, err, "expected error for invalid semver %q", d) // Don't need to include the whole regex, this should be enough to match the MasterMinds regex - Expect(err.Error()).To(ContainSubstring("spec.version in body should match '^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)")) - } - }) - It("should pass if a valid semver range given", func() { - validSemvers := []string{ - ">=1.2.3", - "=>1.2.3", - ">= 1.2.3", - ">=v1.2.3", - ">= v1.2.3", - "<=1.2.3", - "=<1.2.3", - "=1.2.3", - "!=1.2.3", - "<1.2.3", - ">1.2.3", - "~1.2.2", - "~>1.2.3", - "^1.2.3", - "1.2.3", - "v1.2.3", - "1.x", - "1.X", - "1.*", - "1.2.x", - "1.2.X", - "1.2.*", - ">=1.2.3 <2.3.4", - ">=1.2.3,<2.3.4", - ">=1.2.3, <2.3.4", - "<1.2.3||>2.3.4", - "<1.2.3|| >2.3.4", - "<1.2.3 ||>2.3.4", - "<1.2.3 || >2.3.4", - ">1.0.0,<1.2.3 || >2.1.0", - "<1.2.3-abc >2.3.4-def", - "<1.2.3-abc+def >2.3.4-ghi+jkl", - } - for _, validSemver := range validSemvers { + require.ErrorContains(t, err, "spec.version in body should match '^(\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\^)") + }) + } +} + +func TestOperatorValidSemver(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + validSemvers := []string{ + ">=1.2.3", + "=>1.2.3", + ">= 1.2.3", + ">=v1.2.3", + ">= v1.2.3", + "<=1.2.3", + "=<1.2.3", + "=1.2.3", + "!=1.2.3", + "<1.2.3", + ">1.2.3", + "~1.2.2", + "~>1.2.3", + "^1.2.3", + "1.2.3", + "v1.2.3", + "1.x", + "1.X", + "1.*", + "1.2.x", + "1.2.X", + "1.2.*", + ">=1.2.3 <2.3.4", + ">=1.2.3,<2.3.4", + ">=1.2.3, <2.3.4", + "<1.2.3||>2.3.4", + "<1.2.3|| >2.3.4", + "<1.2.3 ||>2.3.4", + "<1.2.3 || >2.3.4", + ">1.0.0,<1.2.3 || >2.1.0", + "<1.2.3-abc >2.3.4-def", + "<1.2.3-abc+def >2.3.4-ghi+jkl", + } + for _, smx := range validSemvers { + d := smx + t.Run(d, func(t *testing.T) { + t.Parallel() + cl := newClient(t) op := operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", - Version: validSemver, + Version: d, }) err := cl.Create(ctx, op) - Expect(err).NotTo(HaveOccurred(), "expected success for semver range '%q': %w", validSemver, err) - err = cl.Delete(ctx, op) - Expect(err).NotTo(HaveOccurred(), "unexpected error deleting valid semver '%q': %w", validSemver, err) - } - }) - It("should fail if an invalid channel name is given", func() { - invalidChannels := []string{ - "spaces spaces", - "Capitalized", - "camelCase", - "many/invalid$characters+in_name", - "-start-with-hyphen", - "end-with-hyphen-", - ".start-with-period", - "end-with-period.", - } - for _, invalidChannel := range invalidChannels { + require.NoErrorf(t, err, "unexpected error for semver range %q: %w", d, err) + }) + } +} + +func TestOperatorInvalidChannel(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + invalidChannels := []string{ + "spaces spaces", + "Capitalized", + "camelCase", + "many/invalid$characters+in_name", + "-start-with-hyphen", + "end-with-hyphen-", + ".start-with-period", + "end-with-period.", + } + for _, ch := range invalidChannels { + d := ch + t.Run(d, func(t *testing.T) { + t.Parallel() + cl := newClient(t) err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", - Channel: invalidChannel, + Channel: d, })) - Expect(err).To(HaveOccurred(), "expected error for invalid channel '%q'", invalidChannel) - Expect(err.Error()).To(ContainSubstring("spec.channel in body should match '^[a-z0-9]+([\\.-][a-z0-9]+)*$'")) - } - }) - It("should pass if a valid channel name is given", func() { - validChannels := []string{ - "hyphenated-name", - "dotted.name", - "channel-has-version-1.0.1", - } - for _, validChannel := range validChannels { + require.Errorf(t, err, "expected error for invalid channel %q", d) + require.ErrorContains(t, err, "spec.channel in body should match '^[a-z0-9]+([\\.-][a-z0-9]+)*$'") + }) + } +} + +func TestOperatorValidChannel(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + validChannels := []string{ + "hyphenated-name", + "dotted.name", + "channel-has-version-1.0.1", + } + for _, ch := range validChannels { + d := ch + t.Run(d, func(t *testing.T) { + t.Parallel() + cl := newClient(t) op := operator(operatorsv1alpha1.OperatorSpec{ PackageName: "package", - Channel: validChannel, + Channel: d, }) err := cl.Create(ctx, op) - Expect(err).NotTo(HaveOccurred(), "unexpected error creating valid channel '%q': %w", validChannel, err) - err = cl.Delete(ctx, op) - Expect(err).NotTo(HaveOccurred(), "unexpected error deleting valid channel '%q': %w", validChannel, err) - } - }) - It("should fail if an invalid channel name length", func() { - err := cl.Create(ctx, operator(operatorsv1alpha1.OperatorSpec{ - PackageName: "package", - Channel: "longname01234567890123456789012345678901234567890", - })) - Expect(err).To(HaveOccurred(), "expected error for invalid channel length") - Expect(err.Error()).To(ContainSubstring("spec.channel: Too long: may not be longer than 48")) - }) -}) + require.NoErrorf(t, err, "unexpected error creating valid channel %q: %w", d, err) + }) + } +} diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index ef0a8f54c..bff247b5f 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -6,9 +6,6 @@ import ( "fmt" "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "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" @@ -27,1040 +24,1107 @@ import ( 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" "github.com/operator-framework/operator-controller/pkg/features" - testutil "github.com/operator-framework/operator-controller/test/util" ) -var _ = Describe("Operator Controller Test", func() { - var ( - 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(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } - }) - When("the operator does not exist", func() { - It("returns no error", func() { - res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "non-existent"}}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - }) - }) - When("the operator exists", func() { - var ( - operator *operatorsv1alpha1.Operator - opKey types.NamespacedName - ) - BeforeEach(func() { - opKey = types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} - }) - AfterEach(func() { - verifyInvariants(ctx, reconciler.Client, operator) - Expect(cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})).To(Succeed()) - Expect(cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})).To(Succeed()) - }) - When("the operator specifies a non-existent package", func() { - var pkgName string - BeforeEach(func() { - By("initializing cluster state") - pkgName = fmt.Sprintf("non-existent-%s", rand.String(6)) - 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", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf("no package %q found", pkgName))) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) +// Describe: Operator Controller Test +func TestOperatorDoesNotExist(t *testing.T) { + _, reconciler := newClientAndReconciler(t) - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + t.Log("When the operator does not exist") + t.Log("It returns no error") + res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "non-existent"}}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) +} - 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(Equal(fmt.Sprintf("no package %q found", pkgName))) - }) - }) - When("the operator specifies a version that does not exist", func() { - var pkgName string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: "0.50.0", // this version of the package does not exist - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName))) +func TestOperatorNonExistantPackage(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a non-existent package") + t.Log("By initializing cluster state") + pkgName := fmt.Sprintf("non-existent-%s", rand.String(6)) + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q found", pkgName)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q found", pkgName), cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) +func TestOperatorNonExistantVersion(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a version that does not exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: "0.50.0", // this version of the package does not exist + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName), cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +func TestOperatorBundleDeploymentDoesNotExist(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a valid available package") + t.Log("By initializing cluster state") + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("When the BundleDeployment does not exist") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("It results in the expected BundleDeployment") + bd := &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + t.Log("It sets the resolvedBundleResource status field") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", operator.Status.ResolvedBundleResource) + + t.Log("It sets the InstalledBundleResource status field") + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("It sets the status on operator") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - 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(Equal(fmt.Sprintf(`no package %q matching version "0.50.0" found`, pkgName))) - 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 valid available package", func() { - const pkgName = "prometheus" - 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()) - }) - When("the BundleDeployment does not exist", func() { - BeforeEach(func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - }) - It("results in the expected BundleDeployment", func() { - bd := &rukpakv1alpha1.BundleDeployment{} - err := cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd) - Expect(err).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - It("sets the resolvedBundleResource status field", func() { - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - It("sets the InstalledBundleResource status field", func() { - Expect(operator.Status.InstalledBundleResource).To(Equal("")) - }) - It("sets the status on operator", func() { - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - 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("bundledeployment status is unknown")) - }) - }) - When("the expected BundleDeployment already exists", func() { - var bd *rukpakv1alpha1.BundleDeployment - BeforeEach(func() { - By("patching the existing BD") - bd = &rukpakv1alpha1.BundleDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: opKey.Name, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: operatorsv1alpha1.GroupVersion.String(), - Kind: "Operator", - Name: operator.Name, - UID: operator.UID, - Controller: pointer.Bool(true), - BlockOwnerDeletion: pointer.Bool(true), - }, - }, - }, - Spec: rukpakv1alpha1.BundleDeploymentSpec{ - ProvisionerClassName: "core-rukpak-io-plain", - Template: &rukpakv1alpha1.BundleTemplate{ - Spec: rukpakv1alpha1.BundleSpec{ - ProvisionerClassName: "core-rukpak-io-registry", - Source: rukpakv1alpha1.BundleSource{ - Type: rukpakv1alpha1.SourceTypeImage, - Image: &rukpakv1alpha1.ImageSource{ - Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", - }, - }, - }, - }, - }, - } - }) - - When("the BundleDeployment spec is out of date", func() { - BeforeEach(func() { - By("modifying the BD spec and creating the object") - bd.Spec.ProvisionerClassName = "core-rukpak-io-helm" - err := cl.Create(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - }) - It("results in the expected BundleDeployment", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - - By("checking the expected BD spec") - bd := &rukpakv1alpha1.BundleDeployment{} - err = cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd) - Expect(err).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected status conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - 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("bundledeployment status is unknown")) - }) - }) - - When("The BundleDeployment spec is up-to-date", func() { - BeforeEach(func() { - err := cl.Create(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - bd.Status.ObservedGeneration = bd.GetGeneration() - }) - - When("the BundleDeployment is not patched", func() { - PIt("does not patch the BundleDeployment", func() { - // TODO: verify that no patch call is made. - }) - }) - - When("The BundleDeployment status is mapped to the expected Operator status", func() { - It("verify operator status when bundle deployment is waiting to be created", func() { - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.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("bundledeployment status is unknown")) - }) - - It("verify operator status when `HasValidBundle` condition of rukpak is false", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeHasValidBundle, - Status: metav1.ConditionFalse, - Message: "failed to unpack", - Reason: rukpakv1alpha1.ReasonUnpackFailed, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.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("bundledeployment status is unknown")) - }) - - It("verify operator status when `InstallReady` condition of rukpak is false", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionFalse, - Message: "failed to install", - Reason: rukpakv1alpha1.ReasonInstallFailed, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(ContainSubstring(`failed to install`)) - }) - - It("verify operator status when `InstallReady` condition of rukpak is true", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionTrue, - Message: "operator installed successfully", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("installed from \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - }) - - It("verify any other unknown status of bundledeployment", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeHasValidBundle, - Status: metav1.ConditionUnknown, - Message: "unpacking", - Reason: rukpakv1alpha1.ReasonUnpackSuccessful, - }) - - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionUnknown, - Message: "installing", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(Equal("bundledeployment not ready: installing")) - }) - - It("verify operator status when bundleDeployment installation status is unknown", func() { - apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ - Type: rukpakv1alpha1.TypeInstalled, - Status: metav1.ConditionUnknown, - Message: "installing", - Reason: rukpakv1alpha1.ReasonInstallationSucceeded, - }) - - By("updating the status of bundleDeployment") - err := cl.Status().Update(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching the updated operator after reconcile") - op := &operatorsv1alpha1.Operator{} - err = cl.Get(ctx, opKey, op) - Expect(err).NotTo(HaveOccurred()) - - By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(op.Status.InstalledBundleResource).To(Equal("")) - - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(Equal("bundledeployment not ready: installing")) - }) - - }) - - }) - }) - When("an out-of-date BundleDeployment exists", func() { - var bd *rukpakv1alpha1.BundleDeployment - BeforeEach(func() { - By("creating the expected BD") - bd = &rukpakv1alpha1.BundleDeployment{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: rukpakv1alpha1.BundleDeploymentSpec{ - ProvisionerClassName: "foo", - Template: &rukpakv1alpha1.BundleTemplate{ - Spec: rukpakv1alpha1.BundleSpec{ - ProvisionerClassName: "bar", - Source: rukpakv1alpha1.BundleSource{ - Type: rukpakv1alpha1.SourceTypeHTTP, - HTTP: &rukpakv1alpha1.HTTPSource{ - URL: "http://localhost:8080/", - }, - }, - }, - }, +func TestOperatorBundleDeploymentOutOfDate(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a valid available package") + t.Log("By initializing cluster state") + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("When the expected BundleDeployment already exists") + t.Log("When the BundleDeployment spec is out of date") + t.Log("By patching the existing BD") + bd := &rukpakv1alpha1.BundleDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: opKey.Name, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: operatorsv1alpha1.GroupVersion.String(), + Kind: "Operator", + Name: operator.Name, + UID: operator.UID, + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + }, + }, + }, + Spec: rukpakv1alpha1.BundleDeploymentSpec{ + ProvisionerClassName: "core-rukpak-io-plain", + Template: &rukpakv1alpha1.BundleTemplate{ + Spec: rukpakv1alpha1.BundleSpec{ + ProvisionerClassName: "core-rukpak-io-registry", + Source: rukpakv1alpha1.BundleSource{ + Type: rukpakv1alpha1.SourceTypeImage, + Image: &rukpakv1alpha1.ImageSource{ + Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", }, - } - err := cl.Create(ctx, bd) - Expect(err).NotTo(HaveOccurred()) - - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) - }) - It("results in the expected BundleDeployment", func() { - bd := &rukpakv1alpha1.BundleDeployment{} - err := cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd) - Expect(err).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - It("sets the resolvedBundleResource status field", func() { - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - It("sets the InstalledBundleResource status field", func() { - Expect(operator.Status.InstalledBundleResource).To(Equal("")) - }) - It("sets resolution to unknown status", func() { - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - 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("bundledeployment status is unknown")) - }) - }) - }) - When("the operator specifies a duplicate package", func() { - const pkgName = "prometheus" - var dupOperator *operatorsv1alpha1.Operator - - BeforeEach(func() { - By("initializing cluster state") - dupOperator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("orig-%s", opKey.Name)}, - Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, - } - - err := cl.Create(ctx, dupOperator) - Expect(err).NotTo(HaveOccurred()) - - 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", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(Equal(`duplicate identifier "required package prometheus" in input`))) - - 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(Equal(`duplicate identifier "required package prometheus" in input`)) - 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 channel with version that exist", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgVer = "1.0.0" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution success status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - 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("quay.io/operatorhubio/prometheus@fake1.0.0")) - 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.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"")) - 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("bundledeployment status is unknown")) - - By("fetching the bundled deployment") - bd := &rukpakv1alpha1.BundleDeployment{} - Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake1.0.0")) - }) - }) - When("the operator specifies a package that exists within a channel but no version specified", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution success status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - - 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("quay.io/operatorhubio/prometheus@fake2.0.0")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + t.Log("By modifying the BD spec and creating the object") + bd.Spec.ProvisionerClassName = "core-rukpak-io-helm" + require.NoError(t, cl.Create(ctx, bd)) + + t.Log("It results in the expected BundleDeployment") + + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the expected BD spec") + bd = &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected status conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) - 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("bundledeployment status is unknown")) - - By("fetching the bundled deployment") - bd := &rukpakv1alpha1.BundleDeployment{} - Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-registry")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) - }) - }) - When("the operator specifies a package version in a channel that does not exist", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgVer = "0.47.0" - pkgChan = "alpha" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, +func TestOperatorBundleDeploymentUpToDate(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a valid available package") + t.Log("By initializing cluster state") + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("When the expected BundleDeployment already exists") + t.Log("When the BundleDeployment spec is up-to-date") + t.Log("By patching the existing BD") + bd := &rukpakv1alpha1.BundleDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: opKey.Name, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: operatorsv1alpha1.GroupVersion.String(), + Kind: "Operator", + Name: operator.Name, + UID: operator.UID, + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + }, + }, + }, + Spec: rukpakv1alpha1.BundleDeploymentSpec{ + ProvisionerClassName: "core-rukpak-io-plain", + Template: &rukpakv1alpha1.BundleTemplate{ + Spec: rukpakv1alpha1.BundleSpec{ + ProvisionerClassName: "core-rukpak-io-registry", + Source: rukpakv1alpha1.BundleSource{ + Type: rukpakv1alpha1.SourceTypeImage, + Image: &rukpakv1alpha1.ImageSource{ + Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", + }, }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan))) - - 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("")) + require.NoError(t, cl.Create(ctx, bd)) + bd.Status.ObservedGeneration = bd.GetGeneration() + + t.Log("When the BundleDeployment status is mapped to the expected Operator status") + t.Log("It verifies operator status when bundle deployment is waiting to be created") + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op := &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Empty(t, op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("It verifies operator status when `HasValidBundle` condition of rukpak is false") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionFalse, + Message: "failed to unpack", + Reason: rukpakv1alpha1.ReasonUnpackFailed, + }) - 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(Equal(fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan))) - 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 package in a channel that does not exist", func() { - var pkgName string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgChan = "non-existent" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Channel: pkgChan, - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan))) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Equal(t, "", op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("It verifies operator status when `InstallReady` condition of rukpak is false") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionFalse, + Message: "failed to install", + Reason: rukpakv1alpha1.ReasonInstallFailed, + }) - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + err = cl.Get(ctx, opKey, op) + require.NoError(t, err) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Empty(t, op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationFailed, cond.Reason) + require.Contains(t, cond.Message, `failed to install`) + + t.Log("It verifies operator status when `InstallReady` condition of rukpak is true") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionTrue, + Message: "operator installed successfully", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "installed from \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + + t.Log("It verifies any other unknown status of bundledeployment") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeHasValidBundle, + Status: metav1.ConditionUnknown, + Message: "unpacking", + Reason: rukpakv1alpha1.ReasonUnpackSuccessful, + }) - 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(Equal(fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan))) - 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 package version that does not exist in the channel", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "prometheus" - pkgVer = "0.57.0" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution failure status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(MatchError(fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan))) + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionUnknown, + Message: "installing", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("By running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Empty(t, op.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationFailed, cond.Reason) + require.Equal(t, "bundledeployment not ready: installing", cond.Message) + + t.Log("It verifies operator status when bundleDeployment installation status is unknown") + apimeta.SetStatusCondition(&bd.Status.Conditions, metav1.Condition{ + Type: rukpakv1alpha1.TypeInstalled, + Status: metav1.ConditionUnknown, + Message: "installing", + Reason: rukpakv1alpha1.ReasonInstallationSucceeded, + }) - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) + t.Log("By updating the status of bundleDeployment") + require.NoError(t, cl.Status().Update(ctx, bd)) + + t.Log("running reconcile") + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching the updated operator after reconcile") + op = &operatorsv1alpha1.Operator{} + require.NoError(t, cl.Get(ctx, opKey, op)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", op.Status.ResolvedBundleResource) + require.Empty(t, op.Status.InstalledBundleResource) + + t.Log("By cchecking the expected conditions") + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(op.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationFailed, cond.Reason) + require.Equal(t, "bundledeployment not ready: installing", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - 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(Equal(fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan))) - 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 package with a plain+v0 bundle", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "plain" - pkgVer = "0.1.0" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, +func TestOperatorExpectedBundleDeployment(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a valid available package") + t.Log("By initializing cluster state") + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("When an out-of-date BundleDeployment exists") + t.Log("By creating the expected BD") + bd := &rukpakv1alpha1.BundleDeployment{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: rukpakv1alpha1.BundleDeploymentSpec{ + ProvisionerClassName: "foo", + Template: &rukpakv1alpha1.BundleTemplate{ + Spec: rukpakv1alpha1.BundleSpec{ + ProvisionerClassName: "bar", + Source: rukpakv1alpha1.BundleSource{ + Type: rukpakv1alpha1.SourceTypeHTTP, + HTTP: &rukpakv1alpha1.HTTPSource{ + URL: "http://localhost:8080/", + }, }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution success status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).NotTo(HaveOccurred()) - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) + }, + }, + }, + } + require.NoError(t, cl.Create(ctx, bd)) + + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("It results in the expected BundleDeployment") + bd = &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + t.Log("It sets the resolvedBundleResource status field") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", operator.Status.ResolvedBundleResource) + + t.Log("It sets the InstalledBundleResource status field") + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("It sets resolution to unknown status") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhub/plain@sha256:plain")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +func TestOperatorDuplicatePackage(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + const pkgName = "prometheus" + + t.Log("When the operator specifies a duplicate package") + t.Log("By initializing cluster state") + dupOperator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("orig-%s", opKey.Name)}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, dupOperator)) - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhub/plain@sha256:plain\"")) - 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("bundledeployment status is unknown")) - - By("fetching the bundled deployment") - bd := &rukpakv1alpha1.BundleDeployment{} - Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred()) - Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain")) - Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage)) - Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil()) - Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhub/plain@sha256:plain")) - }) - }) - When("the operator specifies a package with a bad bundle mediatype", func() { - var pkgName string - var pkgVer string - var pkgChan string - BeforeEach(func() { - By("initializing cluster state") - pkgName = "badmedia" - pkgVer = "0.1.0" - pkgChan = "beta" - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: pkgVer, - Channel: pkgChan, - }, - } - err := cl.Create(ctx, operator) - Expect(err).NotTo(HaveOccurred()) - }) - It("sets resolution success status", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("unknown bundle mediatype: badmedia+v1")) + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{PackageName: pkgName}, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, `duplicate identifier "required package prometheus" in input`) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, `duplicate identifier "required package prometheus" in input`, cond.Message) + + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("fetching updated operator after reconcile") - Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) +func TestOperatorChannelVersionExists(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a channel with version that exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + err := cl.Create(ctx, operator) + require.NoError(t, err) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake1.0.0", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake1.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("By fetching the bundled deployment") + bd := &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake1.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhub/badmedia@sha256:badmedia")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +func TestOperatorChannelExistsNoVersion(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package that exists within a channel but no version specified") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "" + pkgChan := "beta" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhubio/prometheus@fake2.0.0\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("By fetching the bundledeployment") + bd := &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-registry", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhubio/prometheus@fake2.0.0", bd.Spec.Template.Spec.Source.Image.Ref) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess)) - Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhub/badmedia@sha256:badmedia\"")) - cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed)) - Expect(cond.Message).To(Equal("unknown bundle mediatype: badmedia+v1")) - }) - }) - When("an invalid semver is provided that bypasses the regex validation", func() { - var ( - pkgName string - fakeClient client.Client - ) - BeforeEach(func() { - opKey = types.NamespacedName{Name: fmt.Sprintf("operator-validation-test-%s", rand.String(8))} - - By("injecting creating a client with the bad operator CR") - pkgName = fmt.Sprintf("exists-%s", rand.String(6)) - operator = &operatorsv1alpha1.Operator{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: operatorsv1alpha1.OperatorSpec{ - PackageName: pkgName, - Version: "1.2.3-123abc_def", // bad semver that matches the regex on the CR validation - }, - } +func TestOperatorVersionNoChannel(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package version in a channel that does not exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "0.47.0" + pkgChan := "alpha" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan), cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - // this bypasses client/server-side CR validation and allows us to test the reconciler's validation - fakeClient = fake.NewClientBuilder().WithScheme(sch).WithObjects(operator).WithStatusSubresource(operator).Build() +func TestOperatorNoChannel(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package in a channel that does not exist") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgChan := "non-existent" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q found in channel %q", pkgName, pkgChan), cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("changing the reconciler client to the fake client") - reconciler.Client = fakeClient - }) +func TestOperatorNoVersion(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package version that does not exist in the channel") + t.Log("By initializing cluster state") + pkgName := "prometheus" + pkgVer := "0.57.0" + pkgChan := "non-existent" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution failure status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.EqualError(t, err, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan)) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + require.Equal(t, fmt.Sprintf("no package %q matching version %q found in channel %q", pkgName, pkgVer, pkgChan), cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as resolution failed", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - It("should add an invalid spec condition and *not* re-enqueue for reconciliation", func() { - By("running reconcile") - res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) - Expect(res).To(Equal(ctrl.Result{})) - Expect(err).ToNot(HaveOccurred()) +func TestOperatorPlainV0Bundle(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package with a plain+v0 bundle") + t.Log("By initializing cluster state") + pkgName := "plain" + pkgVer := "0.1.0" + pkgChan := "beta" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhub/plain@sha256:plain", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhub/plain@sha256:plain\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "bundledeployment status is unknown", cond.Message) + + t.Log("By fetching the bundled deployment") + bd := &rukpakv1alpha1.BundleDeployment{} + require.NoError(t, cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.ProvisionerClassName) + require.Equal(t, "core-rukpak-io-plain", bd.Spec.Template.Spec.ProvisionerClassName) + require.Equal(t, rukpakv1alpha1.SourceTypeImage, bd.Spec.Template.Spec.Source.Type) + require.NotNil(t, bd.Spec.Template.Spec.Source.Image) + require.Equal(t, "quay.io/operatorhub/plain@sha256:plain", bd.Spec.Template.Spec.Source.Image.Ref) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("fetching updated operator after reconcile") - Expect(fakeClient.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) +func TestOperatorBadBundleMediaType(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + + t.Log("When the operator specifies a package with a bad bundle mediatype") + t.Log("By initializing cluster state") + pkgName := "badmedia" + pkgVer := "0.1.0" + pkgChan := "beta" + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + require.NoError(t, cl.Create(ctx, operator)) + + t.Log("It sets resolution success status") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.Error(t, err) + require.ErrorContains(t, err, "unknown bundle mediatype: badmedia+v1") + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, cl.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Equal(t, "quay.io/operatorhub/badmedia@sha256:badmedia", operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionTrue, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + require.Equal(t, "resolved to \"quay.io/operatorhub/badmedia@sha256:badmedia\"", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionFalse, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationFailed, cond.Reason) + require.Equal(t, "unknown bundle mediatype: badmedia+v1", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} - By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("")) - Expect(operator.Status.InstalledBundleResource).To(Equal("")) +func TestOperatorInvalidSemverPastRegex(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) + ctx := context.Background() + t.Log("When an invalid semver is provided that bypasses the regex validation") + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-validation-test-%s", rand.String(8))} + + t.Log("By injecting creating a client with the bad operator CR") + pkgName := fmt.Sprintf("exists-%s", rand.String(6)) + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: "1.2.3-123abc_def", // bad semver that matches the regex on the CR validation + }, + } - By("checking the expected conditions") - cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) - Expect(cond).NotTo(BeNil()) - Expect(cond.Status).To(Equal(metav1.ConditionUnknown)) - Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonResolutionUnknown)) - Expect(cond.Message).To(Equal("validation has not been attempted as spec is invalid")) - 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 spec is invalid")) - }) - }) - }) -}) + // this bypasses client/server-side CR validation and allows us to test the reconciler's validation + fakeClient := fake.NewClientBuilder().WithScheme(sch).WithObjects(operator).WithStatusSubresource(operator).Build() + + t.Log("By changing the reconciler client to the fake client") + reconciler.Client = fakeClient + + t.Log("It should add an invalid spec condition and *not* re-enqueue for reconciliation") + t.Log("By running reconcile") + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Equal(t, ctrl.Result{}, res) + require.NoError(t, err) + + t.Log("By fetching updated operator after reconcile") + require.NoError(t, fakeClient.Get(ctx, opKey, operator)) + + t.Log("By checking the status fields") + require.Empty(t, operator.Status.ResolvedBundleResource) + require.Empty(t, operator.Status.InstalledBundleResource) + + t.Log("By checking the expected conditions") + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonResolutionUnknown, cond.Reason) + require.Equal(t, "validation has not been attempted as spec is invalid", cond.Message) + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled) + require.NotNil(t, cond) + require.Equal(t, metav1.ConditionUnknown, cond.Status) + require.Equal(t, operatorsv1alpha1.ReasonInstallationStatusUnknown, cond.Reason) + require.Equal(t, "installation has not been attempted as spec is invalid", cond.Message) + + verifyInvariants(ctx, t, reconciler.Client, operator) + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) +} -func verifyInvariants(ctx context.Context, c client.Client, op *operatorsv1alpha1.Operator) { +func verifyInvariants(ctx context.Context, t *testing.T, c client.Client, op *operatorsv1alpha1.Operator) { key := client.ObjectKeyFromObject(op) - Expect(c.Get(ctx, key, op)).To(Succeed()) + require.NoError(t, c.Get(ctx, key, op)) - verifyConditionsInvariants(op) + verifyConditionsInvariants(t, op) } -func verifyConditionsInvariants(op *operatorsv1alpha1.Operator) { +func verifyConditionsInvariants(t *testing.T, op *operatorsv1alpha1.Operator) { // Expect that the operator's set of conditions contains all defined // condition types for the Operator API. Every reconcile should always // ensure every condition type's status/reason/message reflects the state // read during _this_ reconcile call. - Expect(op.Status.Conditions).To(HaveLen(len(conditionsets.ConditionTypes))) - for _, t := range conditionsets.ConditionTypes { - cond := apimeta.FindStatusCondition(op.Status.Conditions, t) - Expect(cond).To(Not(BeNil())) - Expect(cond.Status).NotTo(BeEmpty()) - Expect(cond.Reason).To(BeElementOf(conditionsets.ConditionReasons)) - Expect(cond.ObservedGeneration).To(Equal(op.GetGeneration())) + require.Len(t, op.Status.Conditions, len(conditionsets.ConditionTypes)) + for _, tt := range conditionsets.ConditionTypes { + cond := apimeta.FindStatusCondition(op.Status.Conditions, tt) + require.NotNil(t, cond) + require.NotEmpty(t, cond.Status) + require.Contains(t, conditionsets.ConditionReasons, cond.Reason) + require.Equal(t, op.GetGeneration(), cond.ObservedGeneration) } } func TestOperatorUpgrade(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } t.Run("semver upgrade constraints enforcement of upgrades within major version", func(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true)() @@ -1333,13 +1397,8 @@ func TestOperatorUpgrade(t *testing.T) { } func TestOperatorDowngrade(t *testing.T) { + cl, reconciler := newClientAndReconciler(t) ctx := context.Background() - fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) - reconciler := &controllers.OperatorReconciler{ - Client: cl, - Scheme: sch, - Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), - } t.Run("enforce upgrade constraints", func(t *testing.T) { for _, tt := range []struct { diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index 835b37070..3b10a032d 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -17,46 +17,50 @@ limitations under the License. package controllers_test import ( - "fmt" + "log" "os" "path/filepath" "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" + "github.com/operator-framework/deppy/pkg/deppy/solver" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" + "github.com/stretchr/testify/require" operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + "github.com/operator-framework/operator-controller/internal/controllers" + testutil "github.com/operator-framework/operator-controller/test/util" ) +func newClient(t *testing.T) client.Client { + cl, err := client.New(cfg, client.Options{Scheme: sch}) + require.NoError(t, err) + require.NotNil(t, cl) + return cl +} + +func newClientAndReconciler(t *testing.T) (client.Client, *controllers.OperatorReconciler) { + cl := newClient(t) + fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList) + reconciler := &controllers.OperatorReconciler{ + Client: cl, + Scheme: sch, + Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), + } + return cl, reconciler +} + var ( - cl client.Client sch *runtime.Scheme + cfg *rest.Config ) -// Some of the tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -// We plan phase Ginkgo out for unit tests. -// See: https://github.com/operator-framework/operator-controller/issues/189 -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Controller Suite") -} - -// This setup allows for Ginkgo and standard Go tests to co-exist -// and use the same setup and teardown. func TestMain(m *testing.M) { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - // bootstrapping test environment testEnv := &envtest.Environment{ CRDDirectoryPaths: []string{ filepath.Join("..", "..", "config", "crd", "bases"), @@ -64,30 +68,18 @@ func TestMain(m *testing.M) { ErrorIfCRDPathMissing: true, } - cfg, err := testEnv.Start() - if err != nil { - fmt.Println(err) - os.Exit(1) + var err error + cfg, err = testEnv.Start() + utilruntime.Must(err) + if cfg == nil { + log.Panic("expected cfg to not be nil") } sch = runtime.NewScheme() utilruntime.Must(operatorsv1alpha1.AddToScheme(sch)) utilruntime.Must(rukpakv1alpha1.AddToScheme(sch)) - cl, err = client.New(cfg, client.Options{Scheme: sch}) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - code := m.Run() - - // tearing down the test environment - err = testEnv.Stop() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - + utilruntime.Must(testEnv.Stop()) os.Exit(code) }