diff --git a/.github/actions/deploy-template-operator-with-modulereleasemeta/action.yml b/.github/actions/deploy-template-operator-with-modulereleasemeta/action.yml index 5a2aa849e1..bc6a87e1a7 100644 --- a/.github/actions/deploy-template-operator-with-modulereleasemeta/action.yml +++ b/.github/actions/deploy-template-operator-with-modulereleasemeta/action.yml @@ -64,7 +64,8 @@ runs: matrix.e2e-test == 'modulereleasemeta-module-upgrade-new-version' || matrix.e2e-test == 'modulereleasemeta-upgrade-under-deletion' || matrix.e2e-test == 'modulereleasemeta-sync' || - matrix.e2e-test == 'module-status-on-skr-connection-lost' + matrix.e2e-test == 'module-status-on-skr-connection-lost' || + matrix.e2e-test == 'modulereleasemeta-not-allowed-installation' }} shell: bash run: | diff --git a/.github/workflows/test-e2e-with-modulereleasemeta.yml b/.github/workflows/test-e2e-with-modulereleasemeta.yml index a02b057280..be91c3547c 100644 --- a/.github/workflows/test-e2e-with-modulereleasemeta.yml +++ b/.github/workflows/test-e2e-with-modulereleasemeta.yml @@ -65,6 +65,7 @@ jobs: - modulereleasemeta-sync - module-status-on-skr-connection-lost - modulereleasemeta-watch-trigger + - modulereleasemeta-not-allowed-installation runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b5ec4c9ec5..bee97fa6aa 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -95,7 +95,8 @@ "mandatory-module-metrics", "misconfigured-kyma-secret", "ocm-compatible-module-template", - "modulereleasemeta-sync" + "modulereleasemeta-sync", + "modulereleasemeta-not-allowed-installation", ] }, { diff --git a/internal/remote/remote_catalog.go b/internal/remote/remote_catalog.go index d5899eb66c..af3c602d46 100644 --- a/internal/remote/remote_catalog.go +++ b/internal/remote/remote_catalog.go @@ -144,21 +144,30 @@ func (c *RemoteCatalog) GetModuleReleaseMetasToSync( moduleReleaseMetas := []v1beta2.ModuleReleaseMeta{} for _, moduleReleaseMeta := range moduleReleaseMetaList.Items { - if moduleReleaseMeta.IsBeta() && !kyma.IsBeta() { - continue - } - if moduleReleaseMeta.IsInternal() && !kyma.IsInternal() { - continue + if IsAllowedModuleReleaseMeta(moduleReleaseMeta, kyma) { + moduleReleaseMetas = append(moduleReleaseMetas, moduleReleaseMeta) } - moduleReleaseMetas = append(moduleReleaseMetas, moduleReleaseMeta) } return moduleReleaseMetas, nil } +// IsAllowedModuleReleaseMeta determines whether the given ModuleReleaseMeta is allowed for the given Kyma. +// If the ModuleReleaseMeta is Beta, it is allowed only if the Kyma is also Beta. +// If the ModuleReleaseMeta is Internal, it is allowed only if the Kyma is also Internal. +func IsAllowedModuleReleaseMeta(moduleReleaseMeta v1beta2.ModuleReleaseMeta, kyma *v1beta2.Kyma) bool { + if moduleReleaseMeta.IsBeta() && !kyma.IsBeta() { + return false + } + if moduleReleaseMeta.IsInternal() && !kyma.IsInternal() { + return false + } + return true +} + // GetModuleTemplatesToSync returns a list of ModuleTemplates that should be synced to the SKR. -// A ModuleTemplate is synced if it is not mandatory and does not have sync disabled. In addition, -// it must be referenced by a ModuleReleaseMeta that is synced. +// A ModuleTemplate is synced if it is not mandatory and does not have sync disabled, and if +// it is referenced by a ModuleReleaseMeta that is synced. func (c *RemoteCatalog) GetModuleTemplatesToSync( ctx context.Context, moduleReleaseMetas []v1beta2.ModuleReleaseMeta, @@ -168,10 +177,13 @@ func (c *RemoteCatalog) GetModuleTemplatesToSync( return nil, fmt.Errorf("failed to list ModuleTemplates: %w", err) } - return c.FilterModuleTemplatesToSync(moduleTemplateList.Items, moduleReleaseMetas), nil + return FilterAllowedModuleTemplates(moduleTemplateList.Items, moduleReleaseMetas), nil } -func (c *RemoteCatalog) FilterModuleTemplatesToSync( +// FilterAllowedModuleTemplates filters out ModuleTemplates that are not allowed. +// A ModuleTemplate is allowed if it is not mandatory, does not have sync disabled, and if +// it is referenced by a ModuleReleaseMeta that is synced. +func FilterAllowedModuleTemplates( moduleTemplates []v1beta2.ModuleTemplate, moduleReleaseMetas []v1beta2.ModuleReleaseMeta, ) []v1beta2.ModuleTemplate { diff --git a/internal/remote/remote_catalog_test.go b/internal/remote/remote_catalog_test.go index ece3aa5d4c..9a282f7e38 100644 --- a/internal/remote/remote_catalog_test.go +++ b/internal/remote/remote_catalog_test.go @@ -105,10 +105,8 @@ func Test_GetModuleTemplatesToSync_ReturnsMTsThatAreReferencedInMRMAndNotMandato assert.Equal(t, "regular-module-2.0.0", mts[1].ObjectMeta.Name) } -func Test_FilterModuleTemplatesToSync_ReturnsMTsThatAreReferencedInMRMAndNotMandatoryNotSyncDisabled(t *testing.T) { - remoteCatalog := remote.NewRemoteCatalogFromKyma(fakeClient(), nil, "kyma-system") - - mts := remoteCatalog.FilterModuleTemplatesToSync(moduleTemplates().Items, []v1beta2.ModuleReleaseMeta{ +func Test_FilterAllowedModuleTemplates_ReturnsMTsThatAreReferencedInMRMAndNotMandatoryNotSyncDisabled(t *testing.T) { + mts := remote.FilterAllowedModuleTemplates(moduleTemplates().Items, []v1beta2.ModuleReleaseMeta{ *newModuleReleaseMetaBuilder(). withName("regular-module"). withChannelVersion("regular", "1.0.0"). @@ -181,6 +179,123 @@ func Test_GetOldModuleTemplatesToSync_ReturnsBetaInternalNonSyncDisabledNonManda assert.Equal(t, "old-module-regular", mts[3].ObjectMeta.Name) } +func Test_IsAllowedModuleReleaseMeta_ReturnsTrue_ForNonBetaNonInternalMRMAndNonBetaNonInternalKyma(t *testing.T) { + mrm := newModuleReleaseMetaBuilder().build() + kyma := newKymaBuilder().build() + + assert.True(t, remote.IsAllowedModuleReleaseMeta(*mrm, kyma)) +} + +func Test_IsAllowedModuleReleaseMeta(t *testing.T) { + testCases := []struct { + name string + moduleBeta bool + moduleInternal bool + kymaBeta bool + kymaInternal bool + expected bool + }{ + { + name: "Given Module{Beta: false, Internal: false}; Kyma{Beta: false, Internal: false}; Expect Installation: true", + moduleBeta: false, + moduleInternal: false, + kymaBeta: false, + kymaInternal: false, + expected: true, + }, + { + name: "Given Module{Beta: true, Internal: false}; Kyma{Beta: false, Internal: false}; Expect Installation: false", + moduleBeta: true, + moduleInternal: false, + kymaBeta: false, + kymaInternal: false, + expected: false, + }, + { + name: "Given Module{Beta: false, Internal: true}; Kyma{Beta: false, Internal: false}; Expect Installation: false", + moduleBeta: false, + moduleInternal: true, + kymaBeta: false, + kymaInternal: false, + expected: false, + }, + { + name: "Given Module{Beta: true, Internal: false}; Kyma{Beta: true, Internal: false}; Expect Installation: true", + moduleBeta: true, + moduleInternal: false, + kymaBeta: true, + kymaInternal: false, + expected: true, + }, + { + name: "Given Module{Beta: false, Internal: true}; Kyma{Beta: false, Internal: true}; Expect Installation: true", + moduleBeta: false, + moduleInternal: true, + kymaBeta: false, + kymaInternal: true, + expected: true, + }, + { + name: "Given Module{Beta: true, Internal: true}; Kyma{Beta: true, Internal: false}; Expect Installation: false", + moduleBeta: true, + moduleInternal: true, + kymaBeta: true, + kymaInternal: false, + expected: false, + }, + { + name: "Given Module{Beta: true, Internal: true}; Kyma{Beta: false, Internal: true}; Expect Installation: false", + moduleBeta: true, + moduleInternal: true, + kymaBeta: false, + kymaInternal: true, + expected: false, + }, + { + name: "Given Module{Beta: true, Internal: true}; Kyma{Beta: true, Internal: true}; Expect Installation: true", + moduleBeta: true, + moduleInternal: true, + kymaBeta: true, + kymaInternal: true, + expected: true, + }, + { + name: "Given Module{Beta: false, Internal: false}; Kyma{Beta: true, Internal: false}; Expect Installation: true", + moduleBeta: false, + moduleInternal: false, + kymaBeta: true, + kymaInternal: false, + expected: true, + }, + { + name: "Given Module{Beta: false, Internal: false}; Kyma{Beta: false, Internal: true}; Expect Installation: true", + moduleBeta: false, + moduleInternal: false, + kymaBeta: false, + kymaInternal: true, + expected: true, + }, + { + name: "Given Module{Beta: false, Internal: false}; Kyma{Beta: true, Internal: true}; Expect Installation: true", + moduleBeta: false, + moduleInternal: false, + kymaBeta: true, + kymaInternal: true, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mrm := newModuleReleaseMetaBuilder().withBeta(tc.moduleBeta).withInternal(tc.moduleInternal).build() + kyma := newKymaBuilder().withBeta(tc.kymaBeta).withInternal(tc.kymaInternal).build() + + result := remote.IsAllowedModuleReleaseMeta(*mrm, kyma) + assert.Equal(t, tc.expected, result) + }) + } +} + func moduleReleaseMetas() v1beta2.ModuleReleaseMetaList { mrm1 := newModuleReleaseMetaBuilder(). withName("regular-module"). @@ -354,12 +469,21 @@ func (b *moduleReleaseMetaBuilder) withBetaEnabled() *moduleReleaseMetaBuilder { b.moduleReleaseMeta.Spec.Beta = true return b } +func (b *moduleReleaseMetaBuilder) withBeta(beta bool) *moduleReleaseMetaBuilder { + b.moduleReleaseMeta.Spec.Beta = beta + return b +} func (b *moduleReleaseMetaBuilder) withInternalEnabled() *moduleReleaseMetaBuilder { b.moduleReleaseMeta.Spec.Internal = true return b } +func (b *moduleReleaseMetaBuilder) withInternal(internal bool) *moduleReleaseMetaBuilder { + b.moduleReleaseMeta.Spec.Internal = internal + return b +} + type moduleTemplateBuilder struct { moduleTemplate *v1beta2.ModuleTemplate } @@ -442,11 +566,29 @@ func (b *kymaBuilder) withBetaEnabled() *kymaBuilder { return b } +func (b *kymaBuilder) withBeta(beta bool) *kymaBuilder { + if beta { + b.kyma.Labels[shared.BetaLabel] = shared.EnableLabelValue + } else { + b.kyma.Labels[shared.BetaLabel] = shared.DisableLabelValue + } + return b +} + func (b *kymaBuilder) withInternalEnabled() *kymaBuilder { b.kyma.Labels[shared.InternalLabel] = shared.EnableLabelValue return b } +func (b *kymaBuilder) withInternal(internal bool) *kymaBuilder { + if internal { + b.kyma.Labels[shared.InternalLabel] = shared.EnableLabelValue + } else { + b.kyma.Labels[shared.InternalLabel] = shared.DisableLabelValue + } + return b +} + type errorClient struct { client.Client } diff --git a/pkg/templatelookup/regular.go b/pkg/templatelookup/regular.go index b118f9b5ba..9cee44261c 100644 --- a/pkg/templatelookup/regular.go +++ b/pkg/templatelookup/regular.go @@ -12,6 +12,7 @@ import ( "github.com/kyma-project/lifecycle-manager/api/shared" "github.com/kyma-project/lifecycle-manager/api/v1beta2" "github.com/kyma-project/lifecycle-manager/internal/descriptor/provider" + "github.com/kyma-project/lifecycle-manager/internal/remote" "github.com/kyma-project/lifecycle-manager/pkg/log" "github.com/kyma-project/lifecycle-manager/pkg/util" ) @@ -58,7 +59,7 @@ func (t *TemplateLookup) GetRegularTemplates(ctx context.Context, kyma *v1beta2. } templateInfo := t.PopulateModuleTemplateInfo(ctx, module, kyma.Namespace, kyma.Spec.Channel) - templateInfo = ValidateTemplateMode(templateInfo, kyma) + templateInfo = t.ValidateTemplateMode(ctx, templateInfo, kyma) if templateInfo.Err != nil { templates[module.Name] = &templateInfo continue @@ -135,10 +136,21 @@ func (t *TemplateLookup) populateModuleTemplateInfoUsingModuleReleaseMeta(ctx co return templateInfo } -func ValidateTemplateMode(template ModuleTemplateInfo, kyma *v1beta2.Kyma) ModuleTemplateInfo { +func (t *TemplateLookup) ValidateTemplateMode(ctx context.Context, template ModuleTemplateInfo, kyma *v1beta2.Kyma) ModuleTemplateInfo { if template.Err != nil { return template } + + moduleReleaseMeta, err := GetModuleReleaseMeta(ctx, t, template.Spec.ModuleName, template.Namespace) + + if util.IsNotFound(err) { + return validateTemplateModeWithoutModuleReleaseMeta(template, kyma) + } + + return validateTemplateModeWithModuleReleaseMeta(template, kyma, moduleReleaseMeta) +} + +func validateTemplateModeWithoutModuleReleaseMeta(template ModuleTemplateInfo, kyma *v1beta2.Kyma) ModuleTemplateInfo { if template.IsInternal() && !kyma.IsInternal() { template.Err = fmt.Errorf("%w: internal module", ErrTemplateNotAllowed) return template @@ -150,6 +162,15 @@ func ValidateTemplateMode(template ModuleTemplateInfo, kyma *v1beta2.Kyma) Modul return template } +func validateTemplateModeWithModuleReleaseMeta(template ModuleTemplateInfo, kyma *v1beta2.Kyma, + moduleReleaseMeta *v1beta2.ModuleReleaseMeta) ModuleTemplateInfo { + if !remote.IsAllowedModuleReleaseMeta(*moduleReleaseMeta, kyma) { + template.Err = fmt.Errorf("%w: module is beta or internal", ErrTemplateNotAllowed) + } + + return template +} + func (t *TemplateLookup) getTemplateByVersion(ctx context.Context, moduleName, moduleVersion, namespace string, ) (*v1beta2.ModuleTemplate, error) { diff --git a/pkg/templatelookup/regular_test.go b/pkg/templatelookup/regular_test.go index a8118d156f..260502d38f 100644 --- a/pkg/templatelookup/regular_test.go +++ b/pkg/templatelookup/regular_test.go @@ -10,12 +10,16 @@ import ( "github.com/stretchr/testify/require" apierrors "k8s.io/apimachinery/pkg/api/errors" apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + machineryruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + machineryutilruntime "k8s.io/apimachinery/pkg/util/runtime" "ocm.software/ocm/api/ocm/compdesc" ocmmetav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" compdescv2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/kyma-project/lifecycle-manager/api" "github.com/kyma-project/lifecycle-manager/api/shared" "github.com/kyma-project/lifecycle-manager/api/v1beta2" "github.com/kyma-project/lifecycle-manager/internal/descriptor/provider" @@ -82,7 +86,7 @@ func (f *FakeModuleTemplateReader) Get(_ context.Context, objKey client.ObjectKe return nil } -func TestValidateTemplateMode(t *testing.T) { +func TestValidateTemplateMode_ForOldModuleTemplates(t *testing.T) { tests := []struct { name string template templatelookup.ModuleTemplateInfo @@ -121,7 +125,8 @@ func TestValidateTemplateMode(t *testing.T) { } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { - if got := templatelookup.ValidateTemplateMode(testCase.template, testCase.kyma); !errors.Is(got.Err, + tl := templatelookup.NewTemplateLookup(mrmFakeClient(), provider.NewCachedDescriptorProvider()) + if got := tl.ValidateTemplateMode(context.Background(), testCase.template, testCase.kyma); !errors.Is(got.Err, testCase.wantErr) { t.Errorf("ValidateTemplateMode() = %v, want %v", got, testCase.wantErr) } @@ -129,6 +134,147 @@ func TestValidateTemplateMode(t *testing.T) { } } +func Test_ValidateTemplateMode_ForNewModuleTemplatesWithModuleReleaseMeta(t *testing.T) { + testCases := []struct { + name string + moduleBeta bool + moduleInternal bool + kymaBeta bool + kymaInternal bool + expectInstallation bool + }{ + { + name: "Given Module{Beta: false, Internal: false}; Kyma{Beta: false, Internal: false}; Expect Installation: true", + moduleBeta: false, + moduleInternal: false, + kymaBeta: false, + kymaInternal: false, + expectInstallation: true, + }, + { + name: "Given Module{Beta: true, Internal: false}; Kyma{Beta: false, Internal: false}; Expect Installation: false", + moduleBeta: true, + moduleInternal: false, + kymaBeta: false, + kymaInternal: false, + expectInstallation: false, + }, + { + name: "Given Module{Beta: false, Internal: true}; Kyma{Beta: false, Internal: false}; Expect Installation: false", + moduleBeta: false, + moduleInternal: true, + kymaBeta: false, + kymaInternal: false, + expectInstallation: false, + }, + { + name: "Given Module{Beta: true, Internal: false}; Kyma{Beta: true, Internal: false}; Expect Installation: true", + moduleBeta: true, + moduleInternal: false, + kymaBeta: true, + kymaInternal: false, + expectInstallation: true, + }, + { + name: "Given Module{Beta: false, Internal: true}; Kyma{Beta: false, Internal: true}; Expect Installation: true", + moduleBeta: false, + moduleInternal: true, + kymaBeta: false, + kymaInternal: true, + expectInstallation: true, + }, + { + name: "Given Module{Beta: true, Internal: true}; Kyma{Beta: true, Internal: false}; Expect Installation: false", + moduleBeta: true, + moduleInternal: true, + kymaBeta: true, + kymaInternal: false, + expectInstallation: false, + }, + { + name: "Given Module{Beta: true, Internal: true}; Kyma{Beta: false, Internal: true}; Expect Installation: false", + moduleBeta: true, + moduleInternal: true, + kymaBeta: false, + kymaInternal: true, + expectInstallation: false, + }, + { + name: "Given Module{Beta: true, Internal: true}; Kyma{Beta: true, Internal: true}; Expect Installation: true", + moduleBeta: true, + moduleInternal: true, + kymaBeta: true, + kymaInternal: true, + expectInstallation: true, + }, + { + name: "Given Module{Beta: false, Internal: false}; Kyma{Beta: true, Internal: false}; Expect Installation: true", + moduleBeta: false, + moduleInternal: false, + kymaBeta: true, + kymaInternal: false, + expectInstallation: true, + }, + { + name: "Given Module{Beta: false, Internal: false}; Kyma{Beta: false, Internal: true}; Expect Installation: true", + moduleBeta: false, + moduleInternal: false, + kymaBeta: false, + kymaInternal: true, + expectInstallation: true, + }, + { + name: "Given Module{Beta: false, Internal: false}; Kyma{Beta: true, Internal: true}; Expect Installation: true", + moduleBeta: false, + moduleInternal: false, + kymaBeta: true, + kymaInternal: true, + expectInstallation: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mrm := builder.NewModuleReleaseMetaBuilder(). + WithName("test-module"). + WithModuleName("test-module"). + WithBeta(tc.moduleBeta). + WithInternal(tc.moduleInternal). + Build() + mti := templatelookup.ModuleTemplateInfo{ + ModuleTemplate: builder.NewModuleTemplateBuilder(). + WithModuleName("test-module"). + Build(), + } + kyma := builder.NewKymaBuilder(). + WithName("test-kyma"). + WithBeta(tc.kymaBeta). + WithInternal(tc.kymaInternal). + Build() + + tl := templatelookup.NewTemplateLookup(mrmFakeClient(*mrm), provider.NewCachedDescriptorProvider()) + got := tl.ValidateTemplateMode(context.Background(), mti, kyma) + if tc.expectInstallation { + require.NoError(t, got.Err) + } else { + require.ErrorIs(t, got.Err, templatelookup.ErrTemplateNotAllowed) + } + }) + } +} + +func mrmFakeClient(mrms ...v1beta2.ModuleReleaseMeta) client.Reader { + scheme := machineryruntime.NewScheme() + machineryutilruntime.Must(api.AddToScheme(scheme)) + + var objects []client.Object + for _, mrm := range mrms { + objects = append(objects, &mrm) + } + + return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() +} + func Test_GetRegularTemplates_WhenInvalidModuleProvided(t *testing.T) { tests := []struct { name string diff --git a/pkg/testutils/builder/kyma.go b/pkg/testutils/builder/kyma.go index dadf252081..2b1cbba064 100644 --- a/pkg/testutils/builder/kyma.go +++ b/pkg/testutils/builder/kyma.go @@ -25,6 +25,7 @@ func NewKymaBuilder() KymaBuilder { ObjectMeta: apimetav1.ObjectMeta{ Name: random.Name(), Namespace: apimetav1.NamespaceDefault, + Labels: map[string]string{}, }, Spec: v1beta2.KymaSpec{}, Status: v1beta2.KymaStatus{}, @@ -101,6 +102,26 @@ func (kb KymaBuilder) WithModuleStatus(moduleStatus v1beta2.ModuleStatus) KymaBu return kb } +// WithBeta sets v1beta2.Kyma.Labels[shared.BetaLabel]. +func (kb KymaBuilder) WithBeta(beta bool) KymaBuilder { + if beta { + kb.kyma.Labels[shared.BetaLabel] = shared.EnableLabelValue + } else { + kb.kyma.Labels[shared.BetaLabel] = shared.DisableLabelValue + } + return kb +} + +// WithInternal sets v1beta2.Kyma.Labels[shared.InternalLabel]. +func (kb KymaBuilder) WithInternal(internal bool) KymaBuilder { + if internal { + kb.kyma.Labels[shared.InternalLabel] = shared.EnableLabelValue + } else { + kb.kyma.Labels[shared.InternalLabel] = shared.DisableLabelValue + } + return kb +} + // Build returns the built v1beta2.Kyma. func (kb KymaBuilder) Build() *v1beta2.Kyma { return kb.kyma diff --git a/pkg/testutils/builder/modulereleasemeta.go b/pkg/testutils/builder/modulereleasemeta.go index 8fd980792f..2154c7d195 100644 --- a/pkg/testutils/builder/modulereleasemeta.go +++ b/pkg/testutils/builder/modulereleasemeta.go @@ -51,6 +51,21 @@ func (m ModuleReleaseMetaBuilder) WithModuleName(moduleName string) ModuleReleas return m } +func (m ModuleReleaseMetaBuilder) WithNamespace(namespace string) ModuleReleaseMetaBuilder { + m.moduleReleaseMeta.ObjectMeta.Namespace = namespace + return m +} + +func (m ModuleReleaseMetaBuilder) WithBeta(beta bool) ModuleReleaseMetaBuilder { + m.moduleReleaseMeta.Spec.Beta = beta + return m +} + +func (m ModuleReleaseMetaBuilder) WithInternal(internal bool) ModuleReleaseMetaBuilder { + m.moduleReleaseMeta.Spec.Internal = internal + return m +} + func (m ModuleReleaseMetaBuilder) Build() *v1beta2.ModuleReleaseMeta { return m.moduleReleaseMeta } diff --git a/tests/e2e/Makefile b/tests/e2e/Makefile index 2b6cbf447f..7c4d3f166f 100644 --- a/tests/e2e/Makefile +++ b/tests/e2e/Makefile @@ -159,4 +159,6 @@ modulereleasemeta-sync: module-status-on-skr-connection-lost: go test -timeout 20m -ginkgo.v -ginkgo.focus "KCP Kyma Module status on SKR connection lost" - + +modulereleasemeta-not-allowed-installation: + go test -timeout 20m -ginkgo.v -ginkgo.focus "ModuleReleaseMeta Not Allowed Installation" diff --git a/tests/e2e/modulereleasemeta_not_allowed_installation_test.go b/tests/e2e/modulereleasemeta_not_allowed_installation_test.go new file mode 100644 index 0000000000..dda391ca4f --- /dev/null +++ b/tests/e2e/modulereleasemeta_not_allowed_installation_test.go @@ -0,0 +1,79 @@ +package e2e_test + +import ( + "github.com/kyma-project/lifecycle-manager/api/shared" + "github.com/kyma-project/lifecycle-manager/api/v1beta2" + + . "github.com/kyma-project/lifecycle-manager/pkg/testutils" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ModuleReleaseMeta Not Allowed Installation", Ordered, func() { + kyma := NewKymaWithSyncLabel("kyma-sample", ControlPlaneNamespace, v1beta2.DefaultChannel) + module := NewTemplateOperator(v1beta2.DefaultChannel) + InitEmptyKymaBeforeAll(kyma) + + Context("Given SKR Cluster with ModuleTemplate and ModuleReleaseMeta", func() { + It("When ModuleTemplate and ModuleReleaseMeta are applied in KCP cluster", func() { + By("The the ModuleTemplate exists in the KCP Cluster") + Eventually(ModuleTemplateExists). + WithContext(ctx). + WithArguments(kcpClient, module, v1beta2.DefaultChannel, ControlPlaneNamespace). + Should(Succeed()) + + By("And the ModuleReleaseMeta exists on the KCP Cluster") + Eventually(ModuleReleaseMetaExists). + WithContext(ctx). + WithArguments(module.Name, ControlPlaneNamespace, kcpClient). + Should(Succeed()) + }) + + It("When the ModuleReleaseMeta is set to beta", func() { + Eventually(SetModuleReleaseMetaBeta). + WithContext(ctx). + WithArguments(true, module.Name, ControlPlaneNamespace, kcpClient). + Should(Succeed()) + }) + + It("When enabling the not allowed module", func() { + Eventually(EnableModule). + WithContext(ctx). + WithArguments(skrClient, shared.DefaultRemoteKymaName, RemoteNamespace, module). + Should(Succeed()) + + By("Then the module is in error state") + Eventually(CheckModuleState). + WithContext(ctx). + WithArguments(kcpClient, kyma.Name, ControlPlaneNamespace, module.Name, shared.StateError). + Should(Succeed()) + }) + + It("When the beta is removed from ModuleReleaseMeta", func() { + Eventually(SetModuleReleaseMetaBeta). + WithContext(ctx). + WithArguments(false, module.Name, ControlPlaneNamespace, kcpClient). + Should(Succeed()) + + By("Then the module is in ready state") + Eventually(CheckModuleState). + WithContext(ctx). + WithArguments(kcpClient, kyma.Name, ControlPlaneNamespace, module.Name, shared.StateReady). + Should(Succeed()) + }) + + It("When the ModuleReleaseMeta is set to internal", func() { + Eventually(SetModuleReleaseMetaInternal). + WithContext(ctx). + WithArguments(true, module.Name, ControlPlaneNamespace, kcpClient). + Should(Succeed()) + + By("Then the module is in error state") + Eventually(CheckModuleState). + WithContext(ctx). + WithArguments(kcpClient, kyma.Name, ControlPlaneNamespace, module.Name, shared.StateError). + Should(Succeed()) + }) + }) +}) diff --git a/tests/integration/controller/kcp/modulete_installation_test.go b/tests/integration/controller/kcp/modulete_installation_test.go new file mode 100644 index 0000000000..68843adf4b --- /dev/null +++ b/tests/integration/controller/kcp/modulete_installation_test.go @@ -0,0 +1,155 @@ +package kcp_test + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + compdescv2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kyma-project/lifecycle-manager/api/shared" + "github.com/kyma-project/lifecycle-manager/api/v1beta2" + "github.com/kyma-project/lifecycle-manager/internal/remote" + "github.com/kyma-project/lifecycle-manager/pkg/testutils/builder" + "github.com/kyma-project/lifecycle-manager/pkg/testutils/random" + "github.com/kyma-project/lifecycle-manager/pkg/watcher" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + . "github.com/kyma-project/lifecycle-manager/pkg/testutils" +) + +// version is the same as configured for the ModuleTemplate by using "WithOCM()" +const moduleVersion = "1.1.1-e2e-test" + +var _ = Describe("Module installation", func() { + DescribeTable("Verify module installation for different beta/internal configurations", + func(moduleBeta, moduleInternal, kymaBeta, kymaInternal, shouldHaveInstallation bool) { + kymaName := "installation-test-kyma-" + random.Name() + moduleName := "installation-test-module-" + random.Name() + + Eventually(configureKCPKyma, Timeout, Interval).WithArguments(kymaName, moduleName, kymaBeta, kymaInternal).Should(Succeed()) + Eventually(configureKCPModuleTemplates, Timeout, Interval).WithArguments(moduleName).Should(Succeed()) + Eventually(configureKCPModuleReleaseMeta, Timeout, Interval).WithArguments(moduleName, moduleBeta, moduleInternal).Should(Succeed()) + + var skrClient client.Client + var err error + Eventually(func() error { + skrClient, err = testSkrContextFactory.Get(types.NamespacedName{Name: kymaName, Namespace: ControlPlaneNamespace}) + return err + }, Timeout, Interval).Should(Succeed()) + + Eventually(configureSKRKyma, Timeout, Interval).WithArguments(kymaName, moduleName, skrClient).Should(Succeed()) + + if shouldHaveInstallation { + Eventually(expectInstallation, Timeout, Interval).WithArguments(kymaName, moduleName).Should(Succeed()) + } else { + // we use Consistently here as the installation may require multiple reconciliation runs to be installed + // otherwise, we may get a false positive after the first reconcilation where no module is installed yet, but in a consecutive run it will be + Consistently(expectNoInstallation, Timeout, Interval).WithArguments(kymaName, moduleName).Should(Succeed()) + } + }, + Entry("Given Module{Beta: false, Internal: false}; Kyma{Beta: false, Internal: false}; Expect Installation: true", false, false, false, false, true), + Entry("Given Module{Beta: true, Internal: false}; Kyma{Beta: false, Internal: false}; Expect Installation: false", true, false, false, false, false), + Entry("Given Module{Beta: false, Internal: true}; Kyma{Beta: false, Internal: false}; Expect Installation: false", false, true, false, false, false), + Entry("Given Module{Beta: true, Internal: false}; Kyma{Beta: true, Internal: false}; Expect Installation: true", true, false, true, false, true), + Entry("Given Module{Beta: false, Internal: true}; Kyma{Beta: false, Internal: true}; Expect Installation: true", false, true, false, true, true), + Entry("Given Module{Beta: true, Internal: true}; Kyma{Beta: true, Internal: false}; Expect Installation: false", true, true, true, false, false), + Entry("Given Module{Beta: true, Internal: true}; Kyma{Beta: false, Internal: true}; Expect Installation: false", true, true, false, true, false), + Entry("Given Module{Beta: true, Internal: true}; Kyma{Beta: true, Internal: true}; Expect Installation: true", true, true, true, true, true), + Entry("Given Module{Beta: false, Internal: false}; Kyma{Beta: true, Internal: false}; Expect Installation: true", false, false, true, false, true), + Entry("Given Module{Beta: false, Internal: false}; Kyma{Beta: false, Internal: true}; Expect Installation: true", false, false, false, true, true), + Entry("Given Module{Beta: false, Internal: false}; Kyma{Beta: true, Internal: true}; Expect Installation: true", false, false, false, false, true)) +}) + +func configureKCPKyma(kymaName, moduleName string, beta, internal bool) error { + kyma := builder.NewKymaBuilder(). + WithName(kymaName). + WithNamespace(ControlPlaneNamespace). + WithAnnotation(watcher.DomainAnnotation, "example.domain.com"). + WithLabel(shared.InstanceIDLabel, "test-instance"). + WithChannel(v1beta2.DefaultChannel). + Build() + + if beta { + kyma.Labels[shared.BetaLabel] = shared.EnableLabelValue + } + if internal { + kyma.Labels[shared.InternalLabel] = shared.EnableLabelValue + } + + Eventually(kcpClient.Create, Timeout, Interval).WithContext(ctx). + WithArguments(kyma). + Should(Succeed()) + + return nil +} + +func configureSKRKyma(kymaName, moduleName string, skrClient *remote.SkrContext) error { + kyma := v1beta2.Kyma{} + err := skrClient.Get(context.Background(), types.NamespacedName{Name: shared.DefaultRemoteKymaName, Namespace: shared.DefaultRemoteNamespace}, &kyma) + if err != nil { + return err + } + + kyma.Spec.Modules = append(kyma.Spec.Modules, NewTestModuleWithFixName(moduleName, v1beta2.DefaultChannel, "")) + err = skrClient.Update(context.Background(), &kyma) + if err != nil { + return err + } + + return nil +} + +func configureKCPModuleTemplates(moduleName string) error { + mt := builder.NewModuleTemplateBuilder(). + WithNamespace(ControlPlaneNamespace). + WithName(fmt.Sprintf("%s-%s", moduleName, moduleVersion)). + WithModuleName(moduleName). + WithVersion(moduleVersion). + WithOCM(compdescv2.SchemaVersion). + Build() + + Eventually(kcpClient.Create, Timeout, Interval). + WithContext(ctx). + WithArguments(mt). + Should(Succeed()) + + return nil +} + +func configureKCPModuleReleaseMeta(moduleName string, beta, internal bool) error { + mrm := builder.NewModuleReleaseMetaBuilder(). + WithNamespace(ControlPlaneNamespace). + WithModuleName(moduleName). + WithBeta(beta). + WithInternal(internal). + WithSingleModuleChannelAndVersions(v1beta2.DefaultChannel, moduleVersion). + Build() + + Eventually(kcpClient.Create, Timeout, Interval).WithContext(ctx). + WithArguments(mrm). + Should(Succeed()) + + return nil +} + +func expectInstallation(kymaName, moduleName string) error { + manifest, _ := GetManifest(ctx, kcpClient, kymaName, ControlPlaneNamespace, moduleName) + if manifest != nil { + return nil + } else { + return fmt.Errorf("expected manifest to be installed, but it was not") + } +} + +func expectNoInstallation(kymaName, moduleName string) error { + manifest, _ := GetManifest(ctx, kcpClient, kymaName, ControlPlaneNamespace, moduleName) + if manifest == nil { + return nil + } else { + return fmt.Errorf("expected manifest to not be installed, but it was") + } +}