diff --git a/internal/controllers/operator_controller_test.go b/internal/controllers/operator_controller_test.go index 9c3b67511..f769aa2b8 100644 --- a/internal/controllers/operator_controller_test.go +++ b/internal/controllers/operator_controller_test.go @@ -1062,7 +1062,7 @@ func TestOperatorUpgrade(t *testing.T) { Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)), } - t.Run("semver upgrade constraints", func(t *testing.T) { + t.Run("semver upgrade constraints enforcement of upgrades within major version", func(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true)() defer func() { require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) @@ -1155,7 +1155,7 @@ func TestOperatorUpgrade(t *testing.T) { assert.Equal(t, `resolved to "quay.io/operatorhubio/prometheus@fake1.2.0"`, cond.Message) }) - t.Run("legacy semantics upgrade constraints", func(t *testing.T) { + t.Run("legacy semantics upgrade constraints enforcement", func(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)() defer func() { require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) @@ -1247,6 +1247,262 @@ func TestOperatorUpgrade(t *testing.T) { assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason) assert.Equal(t, `resolved to "quay.io/operatorhubio/prometheus@fake1.0.1"`, cond.Message) }) + + t.Run("ignore upgrade constraints", func(t *testing.T) { + for _, tt := range []struct { + name string + flagState bool + }{ + { + name: "ForceSemverUpgradeConstraints feature gate enabled", + flagState: true, + }, + { + name: "ForceSemverUpgradeConstraints feature gate disabled", + flagState: false, + }, + } { + t.Run(tt.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)() + defer func() { + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) + }() + + 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: "prometheus", + Version: "1.0.0", + Channel: "beta", + UpgradeConstraintPolicy: operatorsv1alpha1.UpgradeConstraintPolicyIgnore, + }, + } + // 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) + + // We can go to the next major version when using semver + // as well as to the version which is not next in the channel + // when using legacy constraints + 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.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@fake2.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@fake2.0.0"`, cond.Message) + }) + } + }) +} + +func TestOperatorDowngrade(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("enforce upgrade constraints", func(t *testing.T) { + for _, tt := range []struct { + name string + flagState bool + }{ + { + name: "ForceSemverUpgradeConstraints feature gate enabled", + flagState: true, + }, + { + name: "ForceSemverUpgradeConstraints feature gate disabled", + flagState: false, + }, + } { + t.Run(tt.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)() + defer func() { + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) + }() + + 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: "prometheus", + Version: "1.0.1", + Channel: "beta", + }, + } + // 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.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) + + // Invalid operation: can not downgrade + operator.Spec.Version = "1.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;") + }) + } + }) + + t.Run("ignore upgrade constraints", func(t *testing.T) { + for _, tt := range []struct { + name string + flagState bool + }{ + { + name: "ForceSemverUpgradeConstraints feature gate enabled", + flagState: true, + }, + { + name: "ForceSemverUpgradeConstraints feature gate disabled", + flagState: false, + }, + } { + t.Run(tt.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)() + defer func() { + require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{})) + require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{})) + }() + + 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: "prometheus", + Version: "2.0.0", + Channel: "beta", + UpgradeConstraintPolicy: operatorsv1alpha1.UpgradeConstraintPolicyIgnore, + }, + } + // 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@fake2.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@fake2.0.0"`, cond.Message) + + // We downgrade + operator.Spec.Version = "1.0.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.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) + }) + } + }) } var ( diff --git a/internal/controllers/variable_source.go b/internal/controllers/variable_source.go index c1976a1cc..75d647a9b 100644 --- a/internal/controllers/variable_source.go +++ b/internal/controllers/variable_source.go @@ -71,7 +71,7 @@ func (v *VariableSource) GetVariables(ctx context.Context) ([]deppy.Variable, er return variablesources.NewOperatorVariableSource(operatorList.Items, allBundles, inputVariableSource), nil }, func(inputVariableSource input.VariableSource) (input.VariableSource, error) { - return variablesources.NewBundleDeploymentVariableSource(bundleDeploymentList.Items, allBundles, inputVariableSource), nil + return variablesources.NewBundleDeploymentVariableSource(operatorList.Items, bundleDeploymentList.Items, allBundles, inputVariableSource), nil }, func(inputVariableSource input.VariableSource) (input.VariableSource, error) { return variablesources.NewBundlesAndDepsVariableSource(allBundles, inputVariableSource), nil diff --git a/internal/resolution/variablesources/bundle_deployment.go b/internal/resolution/variablesources/bundle_deployment.go index 8b994c25c..c4ceafb18 100644 --- a/internal/resolution/variablesources/bundle_deployment.go +++ b/internal/resolution/variablesources/bundle_deployment.go @@ -7,19 +7,22 @@ import ( "github.com/operator-framework/deppy/pkg/deppy/input" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" + operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" ) var _ input.VariableSource = &BundleDeploymentVariableSource{} type BundleDeploymentVariableSource struct { + operators []operatorsv1alpha1.Operator bundleDeployments []rukpakv1alpha1.BundleDeployment allBundles []*catalogmetadata.Bundle inputVariableSource input.VariableSource } -func NewBundleDeploymentVariableSource(bundleDeployments []rukpakv1alpha1.BundleDeployment, allBundles []*catalogmetadata.Bundle, inputVariableSource input.VariableSource) *BundleDeploymentVariableSource { +func NewBundleDeploymentVariableSource(operators []operatorsv1alpha1.Operator, bundleDeployments []rukpakv1alpha1.BundleDeployment, allBundles []*catalogmetadata.Bundle, inputVariableSource input.VariableSource) *BundleDeploymentVariableSource { return &BundleDeploymentVariableSource{ + operators: operators, bundleDeployments: bundleDeployments, allBundles: allBundles, inputVariableSource: inputVariableSource, @@ -37,7 +40,7 @@ func (o *BundleDeploymentVariableSource) GetVariables(ctx context.Context) ([]de return nil, err } - installedPackages, err := MakeInstalledPackageVariables(o.allBundles, o.bundleDeployments) + installedPackages, err := MakeInstalledPackageVariables(o.allBundles, o.operators, o.bundleDeployments) if err != nil { return nil, err } diff --git a/internal/resolution/variablesources/bundle_deployment_test.go b/internal/resolution/variablesources/bundle_deployment_test.go index 1010d0743..a5e356cf1 100644 --- a/internal/resolution/variablesources/bundle_deployment_test.go +++ b/internal/resolution/variablesources/bundle_deployment_test.go @@ -10,18 +10,32 @@ import ( "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" + operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" olmvariables "github.com/operator-framework/operator-controller/internal/resolution/variables" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" "github.com/operator-framework/deppy/pkg/deppy" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" ) -func bundleDeployment(name, image string) rukpakv1alpha1.BundleDeployment { - return rukpakv1alpha1.BundleDeployment{ +func fakeOperator(name, packageName string, upgradeConstraintPolicy operatorsv1alpha1.UpgradeConstraintPolicy) operatorsv1alpha1.Operator { + return operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: packageName, + UpgradeConstraintPolicy: upgradeConstraintPolicy, + }, + } +} + +func bundleDeployment(name, image string, owner *operatorsv1alpha1.Operator) rukpakv1alpha1.BundleDeployment { + bd := rukpakv1alpha1.BundleDeployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, @@ -39,6 +53,21 @@ func bundleDeployment(name, image string) rukpakv1alpha1.BundleDeployment { }, }, } + + if owner != nil { + bd.SetOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: operatorsv1alpha1.GroupVersion.String(), + Kind: "Operator", + Name: owner.Name, + UID: owner.UID, + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + }, + }) + } + + return bd } var _ = Describe("BundleDeploymentVariableSource", func() { @@ -102,11 +131,13 @@ var _ = Describe("BundleDeploymentVariableSource", func() { }) It("should produce RequiredPackage variables", func() { + fakeOperator := fakeOperator("test-operator", "test-prometheus", operatorsv1alpha1.UpgradeConstraintPolicyEnforce) + operators := []operatorsv1alpha1.Operator{fakeOperator} bundleDeployments := []rukpakv1alpha1.BundleDeployment{ - bundleDeployment("prometheus", "quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35"), + bundleDeployment("prometheus", "quay.io/operatorhubio/prometheus@sha256:3e281e587de3d03011440685fc4fb782672beab044c1ebadc42788ce05a21c35", &fakeOperator), } - bdVariableSource := variablesources.NewBundleDeploymentVariableSource(bundleDeployments, testBundleList, &MockRequiredPackageSource{}) + bdVariableSource := variablesources.NewBundleDeploymentVariableSource(operators, bundleDeployments, testBundleList, &MockRequiredPackageSource{}) variables, err := bdVariableSource.GetVariables(context.Background()) Expect(err).ToNot(HaveOccurred()) @@ -125,11 +156,13 @@ var _ = Describe("BundleDeploymentVariableSource", func() { }))) }) It("should return an error if the bundleDeployment image doesn't match any operator resource", func() { + fakeOperator := fakeOperator("test-operator", "test-prometheus", operatorsv1alpha1.UpgradeConstraintPolicyEnforce) + operators := []operatorsv1alpha1.Operator{fakeOperator} bundleDeployments := []rukpakv1alpha1.BundleDeployment{ - bundleDeployment("prometheus", "quay.io/operatorhubio/prometheus@sha256:nonexistent"), + bundleDeployment("prometheus", "quay.io/operatorhubio/prometheus@sha256:nonexistent", &fakeOperator), } - bdVariableSource := variablesources.NewBundleDeploymentVariableSource(bundleDeployments, testBundleList, &MockRequiredPackageSource{}) + bdVariableSource := variablesources.NewBundleDeploymentVariableSource(operators, bundleDeployments, testBundleList, &MockRequiredPackageSource{}) _, err := bdVariableSource.GetVariables(context.Background()) Expect(err.Error()).To(Equal("bundleImage \"quay.io/operatorhubio/prometheus@sha256:nonexistent\" not found")) }) diff --git a/internal/resolution/variablesources/installed_package.go b/internal/resolution/variablesources/installed_package.go index 1f1f3792e..ae409f052 100644 --- a/internal/resolution/variablesources/installed_package.go +++ b/internal/resolution/variablesources/installed_package.go @@ -5,10 +5,12 @@ import ( "sort" mmsemver "github.com/Masterminds/semver/v3" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1" + operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" catalogfilter "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" catalogsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort" @@ -22,6 +24,7 @@ import ( // has own variable. func MakeInstalledPackageVariables( allBundles []*catalogmetadata.Bundle, + operators []operatorsv1alpha1.Operator, bundleDeployments []rukpakv1alpha1.BundleDeployment, ) ([]*olmvariables.InstalledPackageVariable, error) { var successors successorsFunc = legacySemanticsSuccessors @@ -29,9 +32,22 @@ func MakeInstalledPackageVariables( successors = semverSuccessors } - result := make([]*olmvariables.InstalledPackageVariable, 0, len(bundleDeployments)) + ownerIDToBundleDeployment := mapOwnerIDToBundleDeployment(bundleDeployments) + + result := make([]*olmvariables.InstalledPackageVariable, 0, len(operators)) processed := sets.Set[string]{} - for _, bundleDeployment := range bundleDeployments { + for _, operator := range operators { + if operator.Spec.UpgradeConstraintPolicy == operatorsv1alpha1.UpgradeConstraintPolicyIgnore { + continue + } + + bundleDeployment, ok := ownerIDToBundleDeployment[operator.UID] + if !ok { + // This can happen when an Operator is requested, + // but not yet installed (e.g. no BundleDeployment created for it) + continue + } + if bundleDeployment.Spec.Template == nil { continue } @@ -119,3 +135,15 @@ func semverSuccessors(allBundles []*catalogmetadata.Bundle, installedBundle *cat return upgradeEdges, nil } + +func mapOwnerIDToBundleDeployment(bundleDeployments []rukpakv1alpha1.BundleDeployment) map[types.UID]*rukpakv1alpha1.BundleDeployment { + result := map[types.UID]*rukpakv1alpha1.BundleDeployment{} + + for idx := range bundleDeployments { + for _, ref := range bundleDeployments[idx].OwnerReferences { + result[ref.UID] = &bundleDeployments[idx] + } + } + + return result +} diff --git a/internal/resolution/variablesources/installed_package_test.go b/internal/resolution/variablesources/installed_package_test.go index 15937681d..73d0cc3b9 100644 --- a/internal/resolution/variablesources/installed_package_test.go +++ b/internal/resolution/variablesources/installed_package_test.go @@ -12,7 +12,9 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/utils/pointer" + operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata" "github.com/operator-framework/operator-controller/internal/resolution/variablesources" "github.com/operator-framework/operator-controller/pkg/features" @@ -201,8 +203,20 @@ func TestMakeInstalledPackageVariables(t *testing.T) { }, } - fakeBundleDeployment := func(name, bundleImage string) rukpakv1alpha1.BundleDeployment { - return rukpakv1alpha1.BundleDeployment{ + fakeOperator := func(name, packageName string, upgradeConstraintPolicy operatorsv1alpha1.UpgradeConstraintPolicy) operatorsv1alpha1.Operator { + return operatorsv1alpha1.Operator{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: operatorsv1alpha1.OperatorSpec{ + PackageName: packageName, + UpgradeConstraintPolicy: upgradeConstraintPolicy, + }, + } + } + + fakeBundleDeployment := func(name, bundleImage string, owner *operatorsv1alpha1.Operator) rukpakv1alpha1.BundleDeployment { + bd := rukpakv1alpha1.BundleDeployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, @@ -218,6 +232,21 @@ func TestMakeInstalledPackageVariables(t *testing.T) { }, }, } + + if owner != nil { + bd.SetOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: operatorsv1alpha1.GroupVersion.String(), + Kind: "Operator", + Name: owner.Name, + UID: owner.UID, + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + }, + }) + } + + return bd } t.Run("with ForceSemverUpgradeConstraints feature gate enabled", func(t *testing.T) { @@ -225,10 +254,12 @@ func TestMakeInstalledPackageVariables(t *testing.T) { t.Run("with non-zero major version", func(t *testing.T) { const bundleImage = "registry.io/repo/test-package@v2.0.0" + fakeOperator := fakeOperator("test-operator", "test-package", operatorsv1alpha1.UpgradeConstraintPolicyEnforce) installedPackages, err := variablesources.MakeInstalledPackageVariables( allBundles, + []operatorsv1alpha1.Operator{fakeOperator}, []rukpakv1alpha1.BundleDeployment{ - fakeBundleDeployment("test-bd", bundleImage), + fakeBundleDeployment("test-package-bd", bundleImage, &fakeOperator), }, ) require.NoError(t, err) @@ -248,10 +279,12 @@ func TestMakeInstalledPackageVariables(t *testing.T) { t.Run("with zero major version", func(t *testing.T) { t.Run("with zero minor version", func(t *testing.T) { const bundleImage = "registry.io/repo/test-package@v0.0.1" + fakeOperator := fakeOperator("test-operator", "test-package", operatorsv1alpha1.UpgradeConstraintPolicyEnforce) installedPackages, err := variablesources.MakeInstalledPackageVariables( allBundles, + []operatorsv1alpha1.Operator{fakeOperator}, []rukpakv1alpha1.BundleDeployment{ - fakeBundleDeployment("test-bd", bundleImage), + fakeBundleDeployment("test-package-bd", bundleImage, &fakeOperator), }, ) require.NoError(t, err) @@ -268,10 +301,12 @@ func TestMakeInstalledPackageVariables(t *testing.T) { t.Run("with non-zero minor version", func(t *testing.T) { const bundleImage = "registry.io/repo/test-package@v0.1.0" + fakeOperator := fakeOperator("test-operator", "test-package", operatorsv1alpha1.UpgradeConstraintPolicyEnforce) installedPackages, err := variablesources.MakeInstalledPackageVariables( allBundles, + []operatorsv1alpha1.Operator{fakeOperator}, []rukpakv1alpha1.BundleDeployment{ - fakeBundleDeployment("test-bd", bundleImage), + fakeBundleDeployment("test-package-bd", bundleImage, &fakeOperator), }, ) require.NoError(t, err) @@ -293,10 +328,12 @@ func TestMakeInstalledPackageVariables(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)() const bundleImage = "registry.io/repo/test-package@v2.0.0" + fakeOperator := fakeOperator("test-operator", "test-package", operatorsv1alpha1.UpgradeConstraintPolicyEnforce) installedPackages, err := variablesources.MakeInstalledPackageVariables( allBundles, + []operatorsv1alpha1.Operator{fakeOperator}, []rukpakv1alpha1.BundleDeployment{ - fakeBundleDeployment("test-bd", bundleImage), + fakeBundleDeployment("test-package-bd", bundleImage, &fakeOperator), }, ) require.NoError(t, err) @@ -312,12 +349,39 @@ func TestMakeInstalledPackageVariables(t *testing.T) { assert.Equal(t, "test-package.v2.0.0", packageVariable.Bundles()[1].Name) }) + t.Run("UpgradeConstraintPolicy is set to Ignore", func(t *testing.T) { + const bundleImage = "registry.io/repo/test-package@v2.0.0" + fakeOperator := fakeOperator("test-operator", "test-package", operatorsv1alpha1.UpgradeConstraintPolicyIgnore) + installedPackages, err := variablesources.MakeInstalledPackageVariables( + allBundles, + []operatorsv1alpha1.Operator{fakeOperator}, + []rukpakv1alpha1.BundleDeployment{ + fakeBundleDeployment("test-package-bd", bundleImage, &fakeOperator), + }, + ) + assert.NoError(t, err) + assert.Empty(t, installedPackages) + }) + + t.Run("no BundleDeployment for an Operator", func(t *testing.T) { + fakeOperator := fakeOperator("test-operator", "test-package", operatorsv1alpha1.UpgradeConstraintPolicyEnforce) + installedPackages, err := variablesources.MakeInstalledPackageVariables( + allBundles, + []operatorsv1alpha1.Operator{fakeOperator}, + []rukpakv1alpha1.BundleDeployment{}, + ) + assert.NoError(t, err) + assert.Empty(t, installedPackages) + }) + t.Run("installed bundle not found", func(t *testing.T) { const bundleImage = "registry.io/repo/test-package@v9.0.0" + fakeOperator := fakeOperator("test-operator", "test-package", operatorsv1alpha1.UpgradeConstraintPolicyEnforce) installedPackages, err := variablesources.MakeInstalledPackageVariables( allBundles, + []operatorsv1alpha1.Operator{fakeOperator}, []rukpakv1alpha1.BundleDeployment{ - fakeBundleDeployment("test-bd", bundleImage), + fakeBundleDeployment("test-package-bd", bundleImage, &fakeOperator), }, ) assert.Nil(t, installedPackages)