Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce Mandatory Spec field for ModuleTemplate #1866

Merged
merged 15 commits into from
Nov 30, 2023
26 changes: 15 additions & 11 deletions cmd/kyma/alpha/create/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ Build a Kubebuilder module my-domain/modC in version 3.2.1 and push it to a loca
"Uses the host filesystem instead of in-memory archiving to build the module.",
)

cmd.Flags().BoolVar(&o.ArchiveVersionOverwrite, "module-archive-version-overwrite", false, "Overwrites existing component's versions of the module. If set to false, the push is a No-Op.")
cmd.Flags().BoolVar(&o.ArchiveVersionOverwrite, "module-archive-version-overwrite", false,
"Overwrites existing component's versions of the module. If set to false, the push is a No-Op.")

cmd.Flags().StringVar(
&o.GitRemote, "git-remote", "origin",
Expand Down Expand Up @@ -184,7 +185,8 @@ Build a Kubebuilder module my-domain/modC in version 3.2.1 and push it to a loca
&o.PrivateKeyPath, "key", "", "Specifies the path where a private key is used for signing.",
)

cmd.Flags().BoolVar(&o.KubebuilderProject, "kubebuilder-project", false, "Specifies provided module is a Kubebuilder Project.")
cmd.Flags().BoolVar(&o.KubebuilderProject, "kubebuilder-project", false,
"Specifies provided module is a Kubebuilder Project.")

configureLegacyFlags(cmd, o)

Expand Down Expand Up @@ -213,7 +215,8 @@ func configureLegacyFlags(cmd *cobra.Command, o *Options) *cobra.Command {

cmd.Flags().StringVar(&o.Channel, "channel", "regular", "Channel to use for the module template.")

cmd.Flags().StringVar(&o.Namespace, "namespace", kcpSystemNamespace, "Specifies the namespace where the ModuleTemplate is deployed.")
cmd.Flags().StringVar(&o.Namespace, "namespace", kcpSystemNamespace,
"Specifies the namespace where the ModuleTemplate is deployed.")

return cmd
}
Expand Down Expand Up @@ -379,25 +382,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 Expand Up @@ -447,7 +450,8 @@ func (cmd *command) getModuleTemplateAnnotations(modCnf *Config, crValidator val
return annotations
}

func (cmd *command) validateDefaultCR(ctx context.Context, modDef *module.Definition, l *zap.SugaredLogger) (validator, error) {
func (cmd *command) validateDefaultCR(ctx context.Context, modDef *module.Definition, l *zap.SugaredLogger) (validator,
error) {
cmd.NewStep("Validating Default CR")

var crValidator validator
Expand Down Expand Up @@ -509,7 +513,7 @@ func (cmd *command) moduleDefinitionFromOptions() (*module.Definition, *Config,
np := nice.Nice{}
np.PrintImportant("WARNING: The Kubebuilder support is DEPRECATED. Use the simple mode by providing the \"--module-config-file\" flag instead.")

//legacy approach, flag-based
// legacy approach, flag-based
def = &module.Definition{
Name: cmd.opts.Name,
Version: cmd.opts.Version,
Expand All @@ -523,7 +527,7 @@ func (cmd *command) moduleDefinitionFromOptions() (*module.Definition, *Config,
return def, cnf, nil
}

//new approach, config-file based
// new approach, config-file based
moduleConfig, err := ParseConfig(cmd.opts.ModuleConfigFile)
if err != nil {
return nil, nil, 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