Skip to content

Commit

Permalink
feat: Introduce Mandatory Spec field for ModuleTemplate (#1866)
Browse files Browse the repository at this point in the history
* Quick safe

* Add test

* Adapt e2e test

* Use shared pkg instead of api

* Use shared pkg instead of api

* Refactor unit test after upgrading KLM deps

* Use working dir in action

* Use working dir in action

* Fix nil pointer reference

* revert action changes

* fix wrong if condition

* set mandatory to optional

* Move mandatory to higher place
  • Loading branch information
jeremyharisch authored Nov 30, 2023
1 parent c4cc821 commit 505c3f1
Show file tree
Hide file tree
Showing 16 changed files with 297 additions and 202 deletions.
10 changes: 5 additions & 5 deletions cmd/kyma/alpha/create/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,25 +408,25 @@ func (cmd *command) Run(ctx context.Context) error {

cmd.NewStep("Generating module template...")
var resourceName = ""
if modCnf != nil {
resourceName = modCnf.ResourceName
}

mandatoryModule := false
var channel = cmd.opts.Channel
if modCnf != nil {
resourceName = modCnf.ResourceName
channel = modCnf.Channel
mandatoryModule = modCnf.Mandatory
}

var namespace = cmd.opts.Namespace
if modCnf != nil && modCnf.Namespace != "" {
namespace = modCnf.Namespace

}

labels := cmd.getModuleTemplateLabels(modCnf)
annotations := cmd.getModuleTemplateAnnotations(modCnf, crValidator)

template, err := module.Template(componentVersionAccess, resourceName, namespace,
channel, modDef.DefaultCR, labels, annotations, modDef.CustomStateChecks)
channel, modDef.DefaultCR, labels, annotations, modDef.CustomStateChecks, mandatoryModule)
if err != nil {
cmd.CurrentStep.Failure()
return err
Expand Down
48 changes: 27 additions & 21 deletions cmd/kyma/alpha/create/module/moduleconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,37 @@ package module

import (
"fmt"
"github.com/kyma-project/cli/pkg/module"
"github.com/kyma-project/lifecycle-manager/api/v1beta2"
"os"
"regexp"
"strings"

"github.com/kyma-project/lifecycle-manager/api/v1beta2"

"github.com/kyma-project/cli/pkg/module"

"github.com/blang/semver/v4"
"gopkg.in/yaml.v3"
)

type Config struct {
Name string `yaml:"name"` //required, the name of the Module
Version string `yaml:"version"` //required, the version of the Module
Channel string `yaml:"channel"` //required, channel that should be used in the ModuleTemplate
ManifestPath string `yaml:"manifest"` //required, reference to the manifests, must be a relative file name.
DefaultCRPath string `yaml:"defaultCR"` //optional, reference to a YAML file containing the default CR for the module, must be a relative file name.
ResourceName string `yaml:"resourceName"` //optional, default={NAME}-{CHANNEL}, the name for the ModuleTemplate that will be created
Namespace string `yaml:"namespace"` //optional, default=kcp-system, the namespace where the ModuleTemplate will be deployed
Security string `yaml:"security"` //optional, name of the security scanners config file
Internal bool `yaml:"internal"` //optional, default=false, determines whether the ModuleTemplate should have the internal flag or not
Beta bool `yaml:"beta"` //optional, default=false, determines whether the ModuleTemplate should have the beta flag or not
Labels map[string]string `yaml:"labels"` //optional, additional labels for the ModuleTemplate
Annotations map[string]string `yaml:"annotations"` //optional, additional annotations for the ModuleTemplate
CustomStateChecks []v1beta2.CustomStateCheck `yaml:"customStateCheck"` //optional, specifies custom state check for module
Name string `yaml:"name"` // required, the name of the Module
Version string `yaml:"version"` // required, the version of the Module
Channel string `yaml:"channel"` // required, channel that should be used in the ModuleTemplate
ManifestPath string `yaml:"manifest"` // required, reference to the manifests, must be a relative file name.
Mandatory bool `yaml:"mandatory"` // optional, default=false, indicates whether the module is mandatory to be installed on all clusters.
DefaultCRPath string `yaml:"defaultCR"` // optional, reference to a YAML file containing the default CR for the module, must be a relative file name.
ResourceName string `yaml:"resourceName"` // optional, default={NAME}-{CHANNEL}, the name for the ModuleTemplate that will be created
Namespace string `yaml:"namespace"` // optional, default=kcp-system, the namespace where the ModuleTemplate will be deployed
Security string `yaml:"security"` // optional, name of the security scanners config file
Internal bool `yaml:"internal"` // optional, default=false, determines whether the ModuleTemplate should have the internal flag or not
Beta bool `yaml:"beta"` // optional, default=false, determines whether the ModuleTemplate should have the beta flag or not
Labels map[string]string `yaml:"labels"` // optional, additional labels for the ModuleTemplate
Annotations map[string]string `yaml:"annotations"` // optional, additional annotations for the ModuleTemplate
CustomStateChecks []v1beta2.CustomStateCheck `yaml:"customStateCheck"` // optional, specifies custom state check for module
}

const (
//taken from "github.com/open-component-model/ocm/resources/component-descriptor-v2-schema.yaml"
// taken from "github.com/open-component-model/ocm/resources/component-descriptor-v2-schema.yaml"
moduleNamePattern = "^[a-z][-a-z0-9]*([.][a-z][-a-z0-9]*)*[.][a-z]{2,}(/[a-z][-a-z0-9_]*([.][a-z][-a-z0-9_]*)*)+$"
namespacePattern = "^[a-z0-9]+(?:-[a-z0-9]+)*$"
moduleNameMaxLen = 255
Expand Down Expand Up @@ -94,7 +97,8 @@ func (cv *configValidator) validateName() *configValidator {
return fmt.Errorf("failed to evaluate regex for module name pattern: %w", err)
}
if !matched {
return fmt.Errorf("%w for input %q, name must match the required pattern, e.g: 'github.com/path-to/your-repo'", ErrNameValidation, cv.config.Name)
return fmt.Errorf("%w for input %q, name must match the required pattern, e.g: 'github.com/path-to/your-repo'",
ErrNameValidation, cv.config.Name)
}

return nil
Expand All @@ -116,7 +120,8 @@ func (cv *configValidator) validateNamespace() *configValidator {
return fmt.Errorf("failed to evaluate regex for module namespace pattern: %w", err)
}
if !matched {
return fmt.Errorf("%w for input %q, namespace must contain only small alphanumeric characters and hyphens", ErrNamespaceValidation, cv.config.Namespace)
return fmt.Errorf("%w for input %q, namespace must contain only small alphanumeric characters and hyphens",
ErrNamespaceValidation, cv.config.Namespace)
}

return nil
Expand All @@ -131,7 +136,7 @@ func (cv *configValidator) validateVersion() *configValidator {
prefix := ""
val := strings.TrimSpace(cv.config.Version)

//strip the leading "v", if any, because it's not part of a proper semver
// strip the leading "v", if any, because it's not part of a proper semver
if strings.HasPrefix(val, "v") {
prefix = "v"
val = val[1:]
Expand All @@ -142,7 +147,7 @@ func (cv *configValidator) validateVersion() *configValidator {
return fmt.Errorf("%w for input %q, %w", ErrVersionValidation, cv.config.Version, err)
}

//restore "v" prefix, if any
// restore "v" prefix, if any
correct := prefix + sv.String()

if correct != cv.config.Version {
Expand All @@ -166,7 +171,8 @@ func (cv *configValidator) validateChannel() *configValidator {
return fmt.Errorf("failed to evaluate regex for channel: %w", err)
}
if !matched {
return fmt.Errorf("%w for input %q, invalid channel format, only allow characters from a-z", ErrChannelValidation, cv.config.Channel)
return fmt.Errorf("%w for input %q, invalid channel format, only allow characters from a-z",
ErrChannelValidation, cv.config.Channel)
}
return nil

Expand Down
27 changes: 16 additions & 11 deletions cmd/kyma/alpha/create/module/moduleconfig_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package module

import (
"github.com/kyma-project/lifecycle-manager/api/v1beta2"
"testing"

"github.com/kyma-project/lifecycle-manager/api/shared"
"github.com/kyma-project/lifecycle-manager/api/v1beta2"
)

func TestValidateVersion(t *testing.T) {
Expand Down Expand Up @@ -44,7 +46,8 @@ func TestValidateVersion(t *testing.T) {
do()

if !tt.isValid && err == nil {
t.Errorf("Version validation failed for input %q: Expected an error but success is reported", tt.version)
t.Errorf("Version validation failed for input %q: Expected an error but success is reported",
tt.version)
}
if tt.isValid && err != nil {
t.Error(err)
Expand Down Expand Up @@ -162,7 +165,8 @@ func TestValidateNamespace(t *testing.T) {
do()

if !tt.isValid && err == nil {
t.Errorf("Namespace validation failed for input %q: Expected an error but success is reported", tt.namespace)
t.Errorf("Namespace validation failed for input %q: Expected an error but success is reported",
tt.namespace)
}
if tt.isValid && err != nil {
t.Error(err)
Expand Down Expand Up @@ -216,7 +220,8 @@ func TestValidateChannel(t *testing.T) {
do()

if !tt.isValid && err == nil {
t.Errorf("Channel validation failed for input %q: Expected an error but success is reported", tt.channel)
t.Errorf("Channel validation failed for input %q: Expected an error but success is reported",
tt.channel)
}
if tt.isValid && err != nil {
t.Error(err)
Expand Down Expand Up @@ -246,7 +251,7 @@ func TestValidateCustomStateChecks(t *testing.T) {
{
JSONPath: "status.health",
Value: "green",
MappedState: v1beta2.StateReady,
MappedState: shared.StateReady,
},
},
},
Expand All @@ -259,12 +264,12 @@ func TestValidateCustomStateChecks(t *testing.T) {
{
JSONPath: "status.health",
Value: "red",
MappedState: v1beta2.StateError,
MappedState: shared.StateError,
},
{
JSONPath: "",
Value: "green",
MappedState: v1beta2.StateReady,
MappedState: shared.StateReady,
},
},
},
Expand All @@ -277,7 +282,7 @@ func TestValidateCustomStateChecks(t *testing.T) {
{
JSONPath: "status.health",
Value: "green",
MappedState: v1beta2.StateReady,
MappedState: shared.StateReady,
},
{
JSONPath: "status.health",
Expand All @@ -295,17 +300,17 @@ func TestValidateCustomStateChecks(t *testing.T) {
{
JSONPath: "status.health",
Value: "green",
MappedState: v1beta2.StateReady,
MappedState: shared.StateReady,
},
{
JSONPath: "status.health",
Value: "red",
MappedState: v1beta2.StateError,
MappedState: shared.StateError,
},
{
JSONPath: "status.health",
Value: "yellow",
MappedState: v1beta2.StateWarning,
MappedState: shared.StateWarning,
},
},
},
Expand Down
7 changes: 5 additions & 2 deletions cmd/kyma/alpha/enable/module/mock/mock_interactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mock

import (
"context"

"github.com/kyma-project/lifecycle-manager/api/v1beta2"
"github.com/stretchr/testify/mock"
)
Expand All @@ -26,7 +27,9 @@ func (m *Interactor) WaitUntilReady(ctx context.Context) error {
return args.Error(0)
}

func (m *Interactor) GetAllModuleTemplates(ctx context.Context) (v1beta2.ModuleTemplateList, error) {
// nolint:revive
func (m *Interactor) GetFilteredModuleTemplates(ctx context.Context, moduleIdentifier string) ([]v1beta2.ModuleTemplate,
error) {
args := m.Called(ctx)
return args.Get(0).(v1beta2.ModuleTemplateList), args.Error(1)
return args.Get(0).([]v1beta2.ModuleTemplate), args.Error(1)
}
37 changes: 5 additions & 32 deletions cmd/kyma/alpha/enable/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import (
"os"
"time"

"github.com/kyma-project/cli/internal/cli/alpha/module"
"github.com/kyma-project/lifecycle-manager/api/v1beta2"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/types"

"github.com/kyma-project/cli/internal/cli"
"github.com/kyma-project/cli/internal/cli/alpha/module"

"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/kyma-project/cli/internal/cli"
)

type command struct {
Expand Down Expand Up @@ -159,15 +161,11 @@ func (cmd *command) run(ctx context.Context, l *zap.SugaredLogger, moduleName st

func validateChannel(ctx context.Context, moduleInteractor module.Interactor,
moduleIdentifier string, channel string, kymaChannel string) error {
allTemplates, err := moduleInteractor.GetAllModuleTemplates(ctx)
filteredModuleTemplates, err := moduleInteractor.GetFilteredModuleTemplates(ctx, moduleIdentifier)
if err != nil {
return fmt.Errorf("could not retrieve module templates in cluster to determine valid channels: %v", err)
}

filteredModuleTemplates, err := filterModuleTemplates(allTemplates, moduleIdentifier)
if err != nil {
return fmt.Errorf("could not process module templates in the cluster: %v", err)
}
if channel == "" {
channel = kymaChannel
}
Expand All @@ -179,31 +177,6 @@ func validateChannel(ctx context.Context, moduleInteractor module.Interactor,
return nil
}

func filterModuleTemplates(allTemplates v1beta2.ModuleTemplateList,
moduleIdentifier string) ([]v1beta2.ModuleTemplate, error) {
var filteredModuleTemplates []v1beta2.ModuleTemplate

for _, mt := range allTemplates.Items {
if mt.Labels[v1beta2.ModuleName] == moduleIdentifier {
filteredModuleTemplates = append(filteredModuleTemplates, mt)
continue
}
if mt.ObjectMeta.Name == moduleIdentifier {
filteredModuleTemplates = append(filteredModuleTemplates, mt)
continue
}
descriptor, err := mt.GetDescriptor()
if err != nil {
return nil, fmt.Errorf("invalid ModuleTemplate descriptor: %v", err)
}
if descriptor.Name == moduleIdentifier {
filteredModuleTemplates = append(filteredModuleTemplates, mt)
continue
}
}
return filteredModuleTemplates, nil
}

func enableModule(modules []v1beta2.Module, name, channel string, customResourcePolicy string) []v1beta2.Module {
for idx := range modules {
if modules[idx].Name == name {
Expand Down
Loading

0 comments on commit 505c3f1

Please sign in to comment.