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

chore: Remove Channel from ModuleConfig #89

Merged
merged 3 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cmd/modulectl/create/long.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ The module config file is a YAML file used to configure the following attributes
```yaml
- name: a string, required, the name of the module
- version: a string, required, the version of the module
- channel: a string, required, channel that should be used in the ModuleTemplate CR
- manifest: a string, required, reference to the manifest, must be a URL

- defaultCR: a string, optional, reference to a YAML file containing the default CR for the module, must be a URL
Expand Down
4 changes: 0 additions & 4 deletions cmd/modulectl/scaffold/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ func Test_Execute_ParsesOptions(t *testing.T) {
securityConfigFile := testutils.RandomName(10)
moduleName := testutils.RandomName(10)
moduleVersion := "1.1.1"
moduleChannel := testutils.RandomName(10)
os.Args = []string{
"scaffold",
"--directory", directory,
Expand All @@ -63,7 +62,6 @@ func Test_Execute_ParsesOptions(t *testing.T) {
"--gen-security-config=" + securityConfigFile,
"--module-name", moduleName,
"--module-version", moduleVersion,
"--module-channel", moduleChannel,
}
svc := &scaffoldServiceStub{}
cmd, _ := scaffoldcmd.NewCmd(svc)
Expand All @@ -79,7 +77,6 @@ func Test_Execute_ParsesOptions(t *testing.T) {
assert.Equal(t, defaultCRFile, svc.opts.DefaultCRFileName)
assert.Equal(t, securityConfigFile, svc.opts.SecurityConfigFileName)
assert.Equal(t, moduleVersion, svc.opts.ModuleVersion)
assert.Equal(t, moduleChannel, svc.opts.ModuleChannel)
}

func Test_Execute_ParsesShortOptions(t *testing.T) {
Expand Down Expand Up @@ -120,7 +117,6 @@ func Test_Execute_ParsesDefaults(t *testing.T) {
assert.Equal(t, scaffoldcmd.DefaultCRFlagDefault, svc.opts.DefaultCRFileName)
assert.Equal(t, scaffoldcmd.SecurityConfigFileFlagDefault, svc.opts.SecurityConfigFileName)
assert.Equal(t, scaffoldcmd.ModuleVersionFlagDefault, svc.opts.ModuleVersion)
assert.Equal(t, scaffoldcmd.ModuleChannelFlagDefault, svc.opts.ModuleChannel)
}

func Test_Execute_ParsesNoOptDefaults(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/modulectl/scaffold/example.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Generate a minimal scaffold for a module - only a blank manifest file and module config file is generated using defaults
modulectl scaffold
Generate a scaffold providing required values explicitly
modulectl scaffold --module-name="kyma-project.io/module/testmodule" --module-version="0.1.1" --module-channel=fast
modulectl scaffold --module-name="kyma-project.io/module/testmodule" --module-version="0.1.1"
Generate a scaffold with a manifest file, default CR and security-scanners config for a module
modulectl scaffold --gen-default-cr --gen-security-config
Generate a scaffold with a manifest file, default CR and security-scanners config for a module, overriding default values
Expand Down
14 changes: 6 additions & 8 deletions cmd/modulectl/scaffold/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,20 @@ const (
ModuleVersionFlagName = "module-version"
ModuleVersionFlagDefault = "0.0.1"
moduleVersionFlagUsage = `Specifies the module version in the generated module config file (default "0.0.1").`

ModuleChannelFlagName = "module-channel"
ModuleChannelFlagDefault = "regular"
moduleChannelFlagUsage = `Specifies the module channel in the generated module config file (default "regular").`
)

func parseFlags(flags *pflag.FlagSet, opts *scaffold.Options) {
flags.StringVarP(&opts.Directory, DirectoryFlagName, directoryFlagShort, DirectoryFlagDefault, directoryFlagUsage)
flags.StringVarP(&opts.ModuleConfigFileName, ModuleConfigFileFlagName, moduleConfigFileFlagShort, ModuleConfigFileFlagDefault, moduleConfigFileFlagUsage)
flags.BoolVarP(&opts.ModuleConfigFileOverwrite, ModuleConfigFileOverwriteFlagName, moduleConfigFileOverwriteFlagShort, ModuleConfigFileOverwriteFlagDefault, moduleConfigFileOverwriteFlagUsage)
flags.StringVarP(&opts.ModuleConfigFileName, ModuleConfigFileFlagName, moduleConfigFileFlagShort,
ModuleConfigFileFlagDefault, moduleConfigFileFlagUsage)
flags.BoolVarP(&opts.ModuleConfigFileOverwrite, ModuleConfigFileOverwriteFlagName,
moduleConfigFileOverwriteFlagShort, ModuleConfigFileOverwriteFlagDefault, moduleConfigFileOverwriteFlagUsage)
flags.StringVar(&opts.ManifestFileName, ManifestFileFlagName, ManifestFileFlagDefault, manifestFileFlagUsage)
flags.StringVar(&opts.DefaultCRFileName, DefaultCRFlagName, DefaultCRFlagDefault, defaultCRFlagUsage)
flags.StringVar(&opts.SecurityConfigFileName, SecurityConfigFileFlagName, SecurityConfigFileFlagDefault, securityConfigFileFlagUsage)
flags.StringVar(&opts.SecurityConfigFileName, SecurityConfigFileFlagName, SecurityConfigFileFlagDefault,
securityConfigFileFlagUsage)
flags.StringVar(&opts.ModuleName, ModuleNameFlagName, ModuleNameFlagDefault, moduleNameFlagUsage)
flags.StringVar(&opts.ModuleVersion, ModuleVersionFlagName, ModuleVersionFlagDefault, moduleVersionFlagUsage)
flags.StringVar(&opts.ModuleChannel, ModuleChannelFlagName, ModuleChannelFlagDefault, moduleChannelFlagUsage)

flags.Lookup(SecurityConfigFileFlagName).NoOptDefVal = SecurityConfigFileFlagNoOptDefault
flags.Lookup(DefaultCRFlagName).NoOptDefVal = DefaultCRFlagNoOptDefault
Expand Down
31 changes: 25 additions & 6 deletions cmd/modulectl/scaffold/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,35 @@ func Test_ScaffoldFlagsDefaults(t *testing.T) {
expected string
}{
{name: scaffoldcmd.DirectoryFlagName, value: scaffoldcmd.DirectoryFlagDefault, expected: "./"},
{name: scaffoldcmd.ModuleConfigFileFlagName, value: scaffoldcmd.ModuleConfigFileFlagDefault, expected: "scaffold-module-config.yaml"},
{name: scaffoldcmd.ModuleConfigFileOverwriteFlagName, value: strconv.FormatBool(scaffoldcmd.ModuleConfigFileOverwriteFlagDefault), expected: "false"},
{
name: scaffoldcmd.ModuleConfigFileFlagName,
value: scaffoldcmd.ModuleConfigFileFlagDefault,
expected: "scaffold-module-config.yaml",
},
{
name: scaffoldcmd.ModuleConfigFileOverwriteFlagName,
value: strconv.FormatBool(scaffoldcmd.ModuleConfigFileOverwriteFlagDefault),
expected: "false",
},
{name: scaffoldcmd.ManifestFileFlagName, value: scaffoldcmd.ManifestFileFlagDefault, expected: "manifest.yaml"},
{name: scaffoldcmd.DefaultCRFlagName, value: scaffoldcmd.DefaultCRFlagDefault, expected: ""},
{name: scaffoldcmd.DefaultCRFlagName, value: scaffoldcmd.DefaultCRFlagNoOptDefault, expected: "default-cr.yaml"},
{
name: scaffoldcmd.DefaultCRFlagName,
value: scaffoldcmd.DefaultCRFlagNoOptDefault,
expected: "default-cr.yaml",
},
{name: scaffoldcmd.SecurityConfigFileFlagName, value: scaffoldcmd.SecurityConfigFileFlagDefault, expected: ""},
{name: scaffoldcmd.SecurityConfigFileFlagName, value: scaffoldcmd.SecurityConfigFileFlagNoOptDefault, expected: "sec-scanners-config.yaml"},
{name: scaffoldcmd.ModuleNameFlagName, value: scaffoldcmd.ModuleNameFlagDefault, expected: "kyma-project.io/module/mymodule"},
{
name: scaffoldcmd.SecurityConfigFileFlagName,
value: scaffoldcmd.SecurityConfigFileFlagNoOptDefault,
expected: "sec-scanners-config.yaml",
},
{
name: scaffoldcmd.ModuleNameFlagName,
value: scaffoldcmd.ModuleNameFlagDefault,
expected: "kyma-project.io/module/mymodule",
},
{name: scaffoldcmd.ModuleVersionFlagName, value: scaffoldcmd.ModuleVersionFlagDefault, expected: "0.0.1"},
{name: scaffoldcmd.ModuleChannelFlagName, value: scaffoldcmd.ModuleChannelFlagDefault, expected: "regular"},
}

for _, testcase := range tests {
Expand Down
1 change: 0 additions & 1 deletion cmd/modulectl/scaffold/long.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ Only the module config file may be force-overwritten when the --overwrite=true f
You can specify the required fields of the module config using the following CLI flags:
--module-name=NAME
--module-version=VERSION
--module-channel=CHANNEL

**NOTE:** If the required fields aren't provided, the defaults are applied and the module-config.yaml is not ready to be used. You must manually edit the file to make it usable.
Also, edit the sec-scanners-config.yaml to be able to use it.
2 changes: 1 addition & 1 deletion cmd/modulectl/scaffold/use.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
scaffold [--module-name MODULE_NAME --module-version MODULE_VERSION --module-channel CHANNEL] [--directory MODULE_DIRECTORY] [flags]
scaffold [--module-name MODULE_NAME --module-version MODULE_VERSION] [--directory MODULE_DIRECTORY] [flags]
1 change: 0 additions & 1 deletion docs/gen-docs/modulectl_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ The module config file is a YAML file used to configure the following attributes
```yaml
- name: a string, required, the name of the module
- version: a string, required, the version of the module
- channel: a string, required, channel that should be used in the ModuleTemplate CR
- manifest: a string, required, reference to the manifest, must be a URL

- defaultCR: a string, optional, reference to a YAML file containing the default CR for the module, must be a URL
Expand Down
6 changes: 2 additions & 4 deletions docs/gen-docs/modulectl_scaffold.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,13 @@ Only the module config file may be force-overwritten when the --overwrite=true f
You can specify the required fields of the module config using the following CLI flags:
--module-name=NAME
--module-version=VERSION
--module-channel=CHANNEL

**NOTE:** If the required fields aren't provided, the defaults are applied and the module-config.yaml is not ready to be used. You must manually edit the file to make it usable.
Also, edit the sec-scanners-config.yaml to be able to use it.


```bash
modulectl scaffold [--module-name MODULE_NAME --module-version MODULE_VERSION --module-channel CHANNEL] [--directory MODULE_DIRECTORY] [flags]
modulectl scaffold [--module-name MODULE_NAME --module-version MODULE_VERSION] [--directory MODULE_DIRECTORY] [flags]
```

## Examples
Expand All @@ -58,7 +57,7 @@ modulectl scaffold [--module-name MODULE_NAME --module-version MODULE_VERSION --
Generate a minimal scaffold for a module - only a blank manifest file and module config file is generated using defaults
modulectl scaffold
Generate a scaffold providing required values explicitly
modulectl scaffold --module-name="kyma-project.io/module/testmodule" --module-version="0.1.1" --module-channel=fast
modulectl scaffold --module-name="kyma-project.io/module/testmodule" --module-version="0.1.1"
Generate a scaffold with a manifest file, default CR and security-scanners config for a module
modulectl scaffold --gen-default-cr --gen-security-config
Generate a scaffold with a manifest file, default CR and security-scanners config for a module, overriding default values
Expand All @@ -75,7 +74,6 @@ Generate a scaffold with a manifest file, default CR and security-scanners confi
--gen-manifest string Specifies the manifest in the generated module config. A blank manifest file is generated if it doesn't exist (default "manifest.yaml").
--gen-security-config string Specifies the security file in the generated module config. A scaffold security config file is generated if it doesn't exist (default "sec-scanners-config.yaml").
-h, --help Provides help for the scaffold command.
--module-channel string Specifies the module channel in the generated module config file (default "regular").
--module-name string Specifies the module name in the generated config file (default "kyma-project.io/module/mymodule").
--module-version string Specifies the module version in the generated module config file (default "0.0.1").
-o, --overwrite Specifies if the command overwrites an existing module configuration file.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ require (
github.com/containers/image/v5 v5.32.2 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.2.0 // indirect
github.com/containers/storage v1.55.0 // indirect
github.com/containers/storage v1.55.1 // indirect
github.com/coreos/go-oidc/v3 v3.11.0 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect
github.com/cyphar/filepath-securejoin v0.3.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,8 @@ github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYgle
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.2.0 h1:X14EgRK3xNFvJEfI5O4Qn4T3E25ANudSOZz/sirVuPM=
github.com/containers/ocicrypt v1.2.0/go.mod h1:ZNviigQajtdlxIZGibvblVuIFBKIuUI2M0QM12SD31U=
github.com/containers/storage v1.55.0 h1:wTWZ3YpcQf1F+dSP4KxG9iqDfpQY1otaUXjPpffuhgg=
github.com/containers/storage v1.55.0/go.mod h1:28cB81IDk+y7ok60Of6u52RbCeBRucbFOeLunhER1RQ=
github.com/containers/storage v1.55.1 h1:ius7angdTqxO56hmTJnAznyEcUnYeLOV3ybwLozA/h8=
github.com/containers/storage v1.55.1/go.mod h1:28cB81IDk+y7ok60Of6u52RbCeBRucbFOeLunhER1RQ=
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
Expand Down
28 changes: 0 additions & 28 deletions internal/common/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ const (
// // taken from "https://github.com/open-component-model/ocm/blob/4473dacca406e4c84c0ac5e6e14393c659384afc/resources/component-descriptor-v2-schema.yaml#L40"
moduleNamePattern = "^[a-z][-a-z0-9]*([.][a-z][-a-z0-9]*)*[.][a-z]{2,}(/[a-z][-a-z0-9_]*([.][a-z][-a-z0-9_]*)*)+$"
moduleNameMaxLength = 255
channelMinLength = 3
channelMaxLength = 32
channelPattern = "^[a-z]+$"
namespaceMaxLength = 253
namespacePattern = "^[a-z0-9]+(?:-[a-z0-9]+)*$"
)
Expand Down Expand Up @@ -55,31 +52,6 @@ func ValidateModuleVersion(version string) error {
return nil
}

func ValidateModuleChannel(channel string) error {
if channel == "" {
return fmt.Errorf("%w: opts.ModuleChannel must not be empty", commonerrors.ErrInvalidOption)
}

if len(channel) > channelMaxLength {
return fmt.Errorf("%w: opts.ModuleChannel length must not exceed %q characters", commonerrors.ErrInvalidOption,
channelMaxLength)
}

if len(channel) < channelMinLength {
return fmt.Errorf("%w: opts.ModuleChannel length must be at least %q characters", commonerrors.ErrInvalidOption,
channelMinLength)
}

if matched, err := regexp.MatchString(channelPattern, channel); err != nil {
return fmt.Errorf("%w: failed to evaluate regex pattern for opts.ModuleChannel", commonerrors.ErrInvalidOption)
} else if !matched {
return fmt.Errorf("%w: opts.ModuleChannel must match the required pattern, only characters from a-z are allowed",
commonerrors.ErrInvalidOption)
}

return nil
}

func ValidateModuleNamespace(namespace string) error {
if namespace == "" {
return fmt.Errorf("%w: opts.ModuleNamespace must not be empty", commonerrors.ErrInvalidOption)
Expand Down
41 changes: 0 additions & 41 deletions internal/common/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,47 +95,6 @@ func TestValidateModuleVersion(t *testing.T) {
}
}

func TestValidateModuleChannel(t *testing.T) {
tests := []struct {
name string
moduleChannel string
wantErr bool
}{
{
name: "valid channel",
moduleChannel: "experimental",
wantErr: false,
},
{
name: "empty channel",
moduleChannel: "",
wantErr: true,
},
{
name: "invalid channel - too short ",
moduleChannel: "a",
wantErr: true,
},
{
name: "invalid channel - too long",
moduleChannel: "thisstringvaluehaslengthof33chars",
wantErr: true,
},
{
name: "invalid channel - contains invalid characters",
moduleChannel: "this value has spaces",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validation.ValidateModuleChannel(tt.moduleChannel); (err != nil) != tt.wantErr {
t.Errorf("ValidateChannel() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestValidateModuleNamespace(t *testing.T) {
tests := []struct {
name string
Expand Down
1 change: 0 additions & 1 deletion internal/service/contentprovider/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package contentprovider
const (
ArgModuleName = "moduleName"
ArgModuleVersion = "moduleVersion"
ArgModuleChannel = "moduleChannel"
ArgManifestFile = "manifestFile"
ArgDefaultCRFile = "defaultCRFile"
ArgSecurityConfigFile = "securityConfigFile"
Expand Down
8 changes: 0 additions & 8 deletions internal/service/contentprovider/moduleconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ func (s *ModuleConfigProvider) getModuleConfig(args types.KeyValueArgs) ModuleCo
return ModuleConfig{
Name: args[ArgModuleName],
Version: args[ArgModuleVersion],
Channel: args[ArgModuleChannel],
Manifest: args[ArgManifestFile],
Security: args[ArgSecurityConfigFile],
DefaultCR: args[ArgDefaultCRFile],
Expand All @@ -64,12 +63,6 @@ func (s *ModuleConfigProvider) validateArgs(args types.KeyValueArgs) error {
return fmt.Errorf("%w: %s must not be empty", ErrInvalidArg, ArgModuleVersion)
}

if value, ok := args[ArgModuleChannel]; !ok {
return fmt.Errorf("%w: %s", ErrMissingArg, ArgModuleChannel)
} else if value == "" {
return fmt.Errorf("%w: %s must not be empty", ErrInvalidArg, ArgModuleChannel)
}

return nil
}

Expand All @@ -82,7 +75,6 @@ type Manager struct {
type ModuleConfig struct {
Name string `yaml:"name" comment:"required, the name of the Module"`
Version string `yaml:"version" comment:"required, the version of the Module"`
Channel string `yaml:"channel" comment:"required, channel that should be used in the ModuleTemplate"`
Manifest string `yaml:"manifest" comment:"required, relative path or remote URL to the manifests"`
Mandatory bool `yaml:"mandatory" comment:"optional, default=false, indicates whether the module is mandatory to be installed on all clusters"`
DefaultCR string `yaml:"defaultCR" comment:"optional, relative path or remote URL to a YAML file containing the default CR for the module"`
Expand Down
22 changes: 1 addition & 21 deletions internal/service/contentprovider/moduleconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,13 @@ func Test_ModuleConfig_GetDefaultContent_ReturnsError_WhenRequiredArgsMissing(t
{
argName: contentprovider.ArgModuleName,
args: types.KeyValueArgs{
contentprovider.ArgModuleChannel: "experimental",
contentprovider.ArgModuleVersion: "0.0.1",
},
},
{
argName: contentprovider.ArgModuleVersion,
args: types.KeyValueArgs{
contentprovider.ArgModuleName: "module-name",
contentprovider.ArgModuleChannel: "experimental",
},
},
{
argName: contentprovider.ArgModuleChannel,
args: types.KeyValueArgs{
contentprovider.ArgModuleName: "module-name",
contentprovider.ArgModuleVersion: "0.0.1",
contentprovider.ArgModuleName: "module-name",
},
},
}
Expand Down Expand Up @@ -84,26 +75,16 @@ func Test_ModuleConfig_GetDefaultContent_ReturnsError_WhenRequiredArgIsEmpty(t *
argName: contentprovider.ArgModuleName,
args: types.KeyValueArgs{
contentprovider.ArgModuleName: "",
contentprovider.ArgModuleChannel: "experimental",
contentprovider.ArgModuleVersion: "0.0.1",
},
},
{
argName: contentprovider.ArgModuleVersion,
args: types.KeyValueArgs{
contentprovider.ArgModuleName: "module-name",
contentprovider.ArgModuleChannel: "experimental",
contentprovider.ArgModuleVersion: "",
},
},
{
argName: contentprovider.ArgModuleChannel,
args: types.KeyValueArgs{
contentprovider.ArgModuleName: "module-name",
contentprovider.ArgModuleChannel: "",
contentprovider.ArgModuleVersion: "0.0.1",
},
},
}

svc, _ := contentprovider.NewModuleConfigProvider(&mcObjectToYAMLConverterStub{})
Expand All @@ -127,7 +108,6 @@ func Test_ModuleConfig_GetDefaultContent_ReturnsConvertedContent(t *testing.T) {

result, err := svc.GetDefaultContent(types.KeyValueArgs{
contentprovider.ArgModuleName: "module-name",
contentprovider.ArgModuleChannel: "regular",
contentprovider.ArgModuleVersion: "0.0.1",
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ func (*fileExistsStub) ReadFile(_ string) ([]byte, error) {
moduleConfig := contentprovider.ModuleConfig{
Name: "module-name",
Version: "0.0.1",
Channel: "regular",
Manifest: "path/to/manifests",
Mandatory: false,
DefaultCR: "path/to/defaultCR",
Expand Down
Loading
Loading