diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index 9dcf4258f..05d070367 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -11,10 +12,13 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -24,6 +28,7 @@ import ( "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" ) @@ -165,10 +170,10 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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("")) @@ -178,7 +183,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -212,7 +217,7 @@ var _ = Describe("Operator Controller Test", func() { Source: rukpakv1alpha1.BundleSource{ Type: rukpakv1alpha1.SourceTypeImage, Image: &rukpakv1alpha1.ImageSource{ - Ref: "quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed", + Ref: "quay.io/operatorhubio/prometheus@fake2.0.0", }, }, }, @@ -245,10 +250,10 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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") @@ -256,7 +261,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -296,7 +301,7 @@ var _ = Describe("Operator Controller Test", func() { Expect(err).NotTo(HaveOccurred()) By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) Expect(op.Status.InstalledBundleResource).To(Equal("")) By("checking the expected conditions") @@ -304,7 +309,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -335,7 +340,7 @@ var _ = Describe("Operator Controller Test", func() { Expect(err).NotTo(HaveOccurred()) By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) Expect(op.Status.InstalledBundleResource).To(Equal("")) By("checking the expected conditions") @@ -343,7 +348,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -374,7 +379,7 @@ var _ = Describe("Operator Controller Test", func() { Expect(err).NotTo(HaveOccurred()) By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) Expect(op.Status.InstalledBundleResource).To(Equal("")) By("checking the expected conditions") @@ -382,7 +387,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -413,20 +418,20 @@ var _ = Describe("Operator Controller Test", func() { Expect(err).NotTo(HaveOccurred()) By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) - Expect(op.Status.InstalledBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + Expect(cond.Message).To(Equal("installed from \"quay.io/operatorhubio/prometheus@fake2.0.0\"")) }) It("verify any other unknown status of bundledeployment", func() { @@ -459,7 +464,7 @@ var _ = Describe("Operator Controller Test", func() { Expect(err).NotTo(HaveOccurred()) By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) Expect(op.Status.InstalledBundleResource).To(Equal("")) By("checking the expected conditions") @@ -467,7 +472,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -498,7 +503,7 @@ var _ = Describe("Operator Controller Test", func() { Expect(err).NotTo(HaveOccurred()) By("Checking the status fields") - Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + Expect(op.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) Expect(op.Status.InstalledBundleResource).To(Equal("")) By("checking the expected conditions") @@ -506,7 +511,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -558,10 +563,10 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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("")) @@ -571,7 +576,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -634,7 +639,7 @@ var _ = Describe("Operator Controller Test", func() { BeforeEach(func() { By("initializing cluster state") pkgName = "prometheus" - pkgVer = "0.47.0" + pkgVer = "1.0.0" pkgChan = "beta" operator = &operatorsv1alpha1.Operator{ ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, @@ -657,7 +662,7 @@ var _ = Describe("Operator Controller Test", func() { Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake1.0.0")) Expect(operator.Status.InstalledBundleResource).To(Equal("")) By("checking the expected conditions") @@ -665,7 +670,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -679,7 +684,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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() { @@ -711,7 +716,7 @@ var _ = Describe("Operator Controller Test", func() { Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred()) By("Checking the status fields") - Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhubio/prometheus@fake2.0.0")) Expect(operator.Status.InstalledBundleResource).To(Equal("")) By("checking the expected conditions") @@ -719,7 +724,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed\"")) + 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)) @@ -733,7 +738,7 @@ var _ = Describe("Operator Controller Test", func() { 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@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed")) + 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() { @@ -1048,6 +1053,202 @@ func verifyConditionsInvariants(op *operatorsv1alpha1.Operator) { } } +func TestOperatorUpgrade(t *testing.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", func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true)() + defer func() { + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) + }() + + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + // Create an operator + err := cl.Create(ctx, operator) + require.NoError(t, err) + + // Run reconcile + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.NoError(t, err) + assert.Equal(t, ctrl.Result{}, res) + + // Refresh the operator after reconcile + err = cl.Get(ctx, opKey, operator) + require.NoError(t, err) + + // Checking the status fields + assert.Equal(t, "quay.io/operatorhubio/prometheus@fake1.0.0", operator.Status.ResolvedBundleResource) + + // checking the expected conditions + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + assert.Equal(t, `resolved to "quay.io/operatorhubio/prometheus@fake1.0.0"`, cond.Message) + + // Invalid update: can not go to the next major version + operator.Spec.Version = "2.0.0" + err = cl.Update(ctx, operator) + require.NoError(t, err) + + // Run reconcile again + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Error(t, err) + assert.Equal(t, ctrl.Result{}, res) + + // Refresh the operator after reconcile + err = cl.Get(ctx, opKey, operator) + require.NoError(t, err) + + // Checking the status fields + // TODO: https://github.com/operator-framework/operator-controller/issues/320 + assert.Equal(t, "", operator.Status.ResolvedBundleResource) + + // checking the expected conditions + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + assert.Equal(t, metav1.ConditionFalse, cond.Status) + assert.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + assert.Contains(t, cond.Message, "constraints not satisfiable") + assert.Contains(t, cond.Message, "installed package prometheus requires at least one of fake-catalog-prometheus-operatorhub/prometheus/beta/1.2.0, fake-catalog-prometheus-operatorhub/prometheus/beta/1.0.1, fake-catalog-prometheus-operatorhub/prometheus/beta/1.0.0;") + + // Valid update skipping one version + operator.Spec.Version = "1.2.0" + err = cl.Update(ctx, operator) + require.NoError(t, err) + + // Run reconcile again + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.NoError(t, err) + assert.Equal(t, ctrl.Result{}, res) + + // Refresh the operator after reconcile + err = cl.Get(ctx, opKey, operator) + require.NoError(t, err) + + // Checking the status fields + assert.Equal(t, "quay.io/operatorhubio/prometheus@fake1.2.0", operator.Status.ResolvedBundleResource) + + // checking the expected conditions + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + assert.Equal(t, `resolved to "quay.io/operatorhubio/prometheus@fake1.2.0"`, cond.Message) + }) + + t.Run("legacy semantics upgrade constraints", func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)() + defer func() { + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) + }() + + pkgName := "prometheus" + pkgVer := "1.0.0" + pkgChan := "beta" + opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))} + operator := &operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: pkgName, + Version: pkgVer, + Channel: pkgChan, + }, + } + // Create an operator + err := cl.Create(ctx, operator) + require.NoError(t, err) + + // Run reconcile + res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.NoError(t, err) + assert.Equal(t, ctrl.Result{}, res) + + // Refresh the operator after reconcile + err = cl.Get(ctx, opKey, operator) + require.NoError(t, err) + + // Checking the status fields + assert.Equal(t, "quay.io/operatorhubio/prometheus@fake1.0.0", operator.Status.ResolvedBundleResource) + + // checking the expected conditions + cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + assert.Equal(t, `resolved to "quay.io/operatorhubio/prometheus@fake1.0.0"`, cond.Message) + + // Invalid update: can not upgrade by skipping a version in the replaces chain + operator.Spec.Version = "1.2.0" + err = cl.Update(ctx, operator) + require.NoError(t, err) + + // Run reconcile again + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.Error(t, err) + assert.Equal(t, ctrl.Result{}, res) + + // Refresh the operator after reconcile + err = cl.Get(ctx, opKey, operator) + require.NoError(t, err) + + // Checking the status fields + // TODO: https://github.com/operator-framework/operator-controller/issues/320 + assert.Equal(t, "", operator.Status.ResolvedBundleResource) + + // checking the expected conditions + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + assert.Equal(t, metav1.ConditionFalse, cond.Status) + assert.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason) + assert.Contains(t, cond.Message, "constraints not satisfiable") + assert.Contains(t, cond.Message, "installed package prometheus requires at least one of fake-catalog-prometheus-operatorhub/prometheus/beta/1.0.1, fake-catalog-prometheus-operatorhub/prometheus/beta/1.0.0;") + + // Valid update skipping one version + operator.Spec.Version = "1.0.1" + err = cl.Update(ctx, operator) + require.NoError(t, err) + + // Run reconcile again + res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey}) + require.NoError(t, err) + assert.Equal(t, ctrl.Result{}, res) + + // Refresh the operator after reconcile + err = cl.Get(ctx, opKey, operator) + require.NoError(t, err) + + // Checking the status fields + assert.Equal(t, "quay.io/operatorhubio/prometheus@fake1.0.1", operator.Status.ResolvedBundleResource) + + // checking the expected conditions + cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved) + require.NotNil(t, cond) + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) + assert.Equal(t, `resolved to "quay.io/operatorhubio/prometheus@fake1.0.1"`, cond.Message) + }) +} + var ( prometheusAlphaChannel = catalogmetadata.Channel{ Channel: declcfg.Channel{ @@ -1061,11 +1262,19 @@ var ( Package: "prometheus", Entries: []declcfg.ChannelEntry{ { - Name: "operatorhub/prometheus/beta/0.37.0", + Name: "operatorhub/prometheus/beta/1.0.0", + }, + { + Name: "operatorhub/prometheus/beta/1.0.1", + Replaces: "operatorhub/prometheus/beta/1.0.0", + }, + { + Name: "operatorhub/prometheus/beta/1.2.0", + Replaces: "operatorhub/prometheus/beta/1.0.1", }, { - Name: "operatorhub/prometheus/beta/0.47.0", - Replaces: "operatorhub/prometheus/beta/0.37.0", + Name: "operatorhub/prometheus/beta/2.0.0", + Replaces: "operatorhub/prometheus/beta/1.2.0", }, }, }, @@ -1095,31 +1304,60 @@ var testBundleList = []*catalogmetadata.Bundle{ {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, }, }, - InChannels: []*catalogmetadata.Channel{&prometheusAlphaChannel}, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusAlphaChannel}, }, { Bundle: declcfg.Bundle{ - Name: "operatorhub/prometheus/beta/0.37.0", + Name: "operatorhub/prometheus/beta/1.0.0", Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35", + Image: "quay.io/operatorhubio/prometheus@fake1.0.0", Properties: []property.Property{ - {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"0.37.0"}`)}, + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"1.0.0"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + }, + { + Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/beta/1.0.1", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.0.1", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"1.0.1"}`)}, + {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, + }, + }, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + }, + { + Bundle: declcfg.Bundle{ + Name: "operatorhub/prometheus/beta/1.2.0", + Package: "prometheus", + Image: "quay.io/operatorhubio/prometheus@fake1.2.0", + Properties: []property.Property{ + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"1.2.0"}`)}, {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, }, }, - InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, }, { Bundle: declcfg.Bundle{ - Name: "operatorhub/prometheus/beta/0.47.0", + Name: "operatorhub/prometheus/beta/2.0.0", Package: "prometheus", - Image: "quay.io/operatorhubio/prometheus@sha256:5b04c49d8d3eff6a338b56ec90bdf491d501fe301c9cdfb740e5bff6769a21ed", + Image: "quay.io/operatorhubio/prometheus@fake2.0.0", Properties: []property.Property{ - {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"0.47.0"}`)}, + {Type: property.TypePackage, Value: json.RawMessage(`{"packageName":"prometheus","version":"2.0.0"}`)}, {Type: property.TypeGVK, Value: json.RawMessage(`[]`)}, }, }, - InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&prometheusBetaChannel}, }, { Bundle: declcfg.Bundle{ @@ -1132,7 +1370,8 @@ var testBundleList = []*catalogmetadata.Bundle{ {Type: "olm.bundle.mediatype", Value: json.RawMessage(`"plain+v0"`)}, }, }, - InChannels: []*catalogmetadata.Channel{&plainBetaChannel}, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&plainBetaChannel}, }, { Bundle: declcfg.Bundle{ @@ -1145,6 +1384,7 @@ var testBundleList = []*catalogmetadata.Bundle{ {Type: "olm.bundle.mediatype", Value: json.RawMessage(`"badmedia+v1"`)}, }, }, - InChannels: []*catalogmetadata.Channel{&badmediaBetaChannel}, + CatalogName: "fake-catalog", + InChannels: []*catalogmetadata.Channel{&badmediaBetaChannel}, }, }