diff --git a/.github/workflows/pull-cli-build.yaml b/.github/workflows/pull-cli-build.yaml new file mode 100644 index 000000000..23f5c73e0 --- /dev/null +++ b/.github/workflows/pull-cli-build.yaml @@ -0,0 +1,21 @@ +name: pull-cli-build +on: + pull_request: + branches: + - main + - 'release-**' + workflow_dispatch: +jobs: + cli-build: + name: build + runs-on: ubuntu-latest + steps: + - name: Checkout Kyma CLI + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + cache-dependency-path: 'go.sum' + - name: Run CLI Build + run: make build diff --git a/.github/workflows/pull-cli-docs.yaml b/.github/workflows/pull-cli-docs.yaml new file mode 100644 index 000000000..24987db9d --- /dev/null +++ b/.github/workflows/pull-cli-docs.yaml @@ -0,0 +1,21 @@ +name: pull-cli-docs +on: + pull_request: + branches: + - main + - 'release-**' + workflow_dispatch: +jobs: + validate-docs: + name: docs validation + runs-on: ubuntu-latest + steps: + - name: Checkout Kyma CLI + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + cache-dependency-path: 'go.sum' + - name: Run Docs Validation + run: make validate diff --git a/.github/workflows/pull-cli-unit-test.yaml b/.github/workflows/pull-cli-unit-test.yaml new file mode 100644 index 000000000..2afe782b6 --- /dev/null +++ b/.github/workflows/pull-cli-unit-test.yaml @@ -0,0 +1,21 @@ +name: pull-cli-unit-test +on: + pull_request: + branches: + - main + - 'release-**' + workflow_dispatch: +jobs: + unit-tests: + name: unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout Kyma CLI + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + cache-dependency-path: 'go.sum' + - name: Run Unit Tests + run: make test diff --git a/.github/workflows/report-sprint-commits.yml b/.github/workflows/report-sprint-commits.yml index 2ab15bca8..513d62050 100644 --- a/.github/workflows/report-sprint-commits.yml +++ b/.github/workflows/report-sprint-commits.yml @@ -22,6 +22,6 @@ jobs: chmod a+x $reporter python -m pip install --upgrade pip pip install -r $pip_requirements - - name: Genarate a report + - name: Generate a report run: | - $reporter --repo https://github.com/kyma-project/cli.git --days 14 --e2e-path tests/ + $reporter --repo-url https://github.com/kyma-project/cli.git --days 14 --e2e tests/e2e diff --git a/.github/workflows/test-e2e-create-scaffold.yml b/.github/workflows/test-e2e-create-scaffold.yml new file mode 100644 index 000000000..156655dcc --- /dev/null +++ b/.github/workflows/test-e2e-create-scaffold.yml @@ -0,0 +1,38 @@ +name: TestSuite E2E. Scaffold Creation + +on: + push: + branches: + - main + - 'release-**' + pull_request: + branches: + - main + - 'release-**' + paths: + - 'go.mod' + - 'go.sum' + - '**.go' +jobs: + e2e: + name: "Run E2E tests" + runs-on: ubuntu-latest + steps: + - name: Checkout Kyma CLI + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + cache-dependency-path: 'go.sum' + - name: Build Kyma CLI + run: | + make resolve validate build-linux + chmod +x ./bin/kyma-linux + ls -la ./bin + mv ./bin/kyma-linux /usr/local/bin/kyma + timeout-minutes: 10 + - name: Run create scaffold test + run: | + make -C tests/e2e test-create-scaffold + timeout-minutes: 3 diff --git a/cmd/kyma/alpha/create/create.go b/cmd/kyma/alpha/create/create.go index b7f81ce02..81c6f58ea 100755 --- a/cmd/kyma/alpha/create/create.go +++ b/cmd/kyma/alpha/create/create.go @@ -2,6 +2,7 @@ package create import ( "github.com/kyma-project/cli/cmd/kyma/alpha/create/module" + "github.com/kyma-project/cli/cmd/kyma/alpha/create/scaffold" "github.com/kyma-project/cli/internal/cli" "github.com/spf13/cobra" ) @@ -16,6 +17,7 @@ func NewCmd(o *cli.Options) *cobra.Command { } cmd.AddCommand(module.NewCmd(module.NewOptions(o))) + cmd.AddCommand(scaffold.NewCmd(scaffold.NewOptions(o))) return cmd } diff --git a/cmd/kyma/alpha/create/module/module.go b/cmd/kyma/alpha/create/module/module.go index 11187fe09..26a83ed4f 100644 --- a/cmd/kyma/alpha/create/module/module.go +++ b/cmd/kyma/alpha/create/module/module.go @@ -1,7 +1,6 @@ package module import ( - "context" "errors" "fmt" "maps" @@ -30,14 +29,16 @@ import ( type command struct { cli.Command - opts *Options + opts *Options + tmpFiles *module.TmpFilesManager } // NewCmd creates a new Kyma CLI command func NewCmd(o *Options) *cobra.Command { c := command{ - Command: cli.Command{Options: o.Options}, - opts: o, + Command: cli.Command{Options: o.Options}, + opts: o, + tmpFiles: module.NewTmpFilesManager(), } cmd := &cobra.Command{ @@ -65,6 +66,7 @@ The module config file is a YAML file used to configure the following attributes - 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 +- mandatory: a boolean, optional, default=false, indicates whether the module is mandatory to be installed on all clusters - manifest: a string, required, reference to the manifest, must be a relative file name - defaultCR: a string, optional, reference to a YAML file containing the default CR for the module, must be a relative file name - resourceName: a string, optional, default={NAME}-{CHANNEL}, the name for the ModuleTemplate CR that will be created @@ -111,7 +113,7 @@ Build a Kubebuilder module my-domain/modC in version 3.2.1 and push it to a loca kyma alpha create module --name my-domain/modC --version 3.2.1 --path /path/to/module --registry http://localhost:5001/unsigned --insecure `, - RunE: func(cobraCmd *cobra.Command, args []string) error { return c.Run(cobraCmd.Context()) }, + RunE: func(cobraCmd *cobra.Command, args []string) error { return c.Run() }, Aliases: []string{"mod"}, } @@ -223,14 +225,9 @@ func configureLegacyFlags(cmd *cobra.Command, o *Options) *cobra.Command { return cmd } -type validator interface { - GetCrd() []byte - Run(ctx context.Context, log *zap.SugaredLogger) error -} - const kcpSystemNamespace = "kcp-system" -func (cmd *command) Run(ctx context.Context) error { +func (cmd *command) Run() error { osFS := osfs.New() if cmd.opts.CI { @@ -253,6 +250,7 @@ func (cmd *command) Run(ctx context.Context) error { } modDef, modCnf, err := cmd.moduleDefinitionFromOptions() + defer cmd.tmpFiles.DeleteTmpFiles() if err != nil { return err @@ -274,9 +272,9 @@ func (cmd *command) Run(ctx context.Context) error { } cmd.CurrentStep.Successf("Module built") - var crValidator validator - if crValidator, err = cmd.validateDefaultCR(ctx, modDef, l); err != nil { - return err + crd, err := module.GetCrdFromModuleDef(cmd.opts.KubebuilderProject, modDef) + if err != nil { + return nil } var archiveFS vfs.FileSystem @@ -423,7 +421,7 @@ func (cmd *command) Run(ctx context.Context) error { } labels := cmd.getModuleTemplateLabels(modCnf) - annotations := cmd.getModuleTemplateAnnotations(modCnf, crValidator) + annotations := cmd.getModuleTemplateAnnotations(modCnf, crd) template, err := module.Template(componentVersionAccess, resourceName, namespace, channel, modDef.DefaultCR, labels, annotations, modDef.CustomStateChecks, mandatoryModule) @@ -457,7 +455,7 @@ func (cmd *command) getModuleTemplateLabels(modCnf *Config) map[string]string { return labels } -func (cmd *command) getModuleTemplateAnnotations(modCnf *Config, crValidator validator) map[string]string { +func (cmd *command) getModuleTemplateAnnotations(modCnf *Config, crd []byte) map[string]string { annotations := map[string]string{} moduleVersion := cmd.opts.Version if modCnf != nil { @@ -466,7 +464,7 @@ func (cmd *command) getModuleTemplateAnnotations(modCnf *Config, crValidator val moduleVersion = modCnf.Version } - isClusterScoped := isCrdClusterScoped(crValidator.GetCrd()) + isClusterScoped := isCrdClusterScoped(crd) if isClusterScoped { annotations[shared.IsClusterScopedAnnotation] = shared.EnableLabelValue } else { @@ -476,28 +474,6 @@ 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) { - cmd.NewStep("Validating Default CR") - - var crValidator validator - if cmd.opts.KubebuilderProject { - crValidator = module.NewDefaultCRValidator(modDef.DefaultCR, modDef.Source) - } else { - crValidator = module.NewSingleManifestFileCRValidator(modDef.DefaultCR, modDef.SingleManifestPath) - } - - if err := crValidator.Run(ctx, l); err != nil { - if errors.Is(err, module.ErrEmptyCR) { - cmd.CurrentStep.Successf("Default CR validation skipped - no default CR") - return crValidator, nil - } - return crValidator, err - } - cmd.CurrentStep.Successf("Default CR validation succeeded") - return crValidator, nil -} - func (cmd *command) getRemote(nameMapping module.NameMapping) (*module.Remote, error) { res := &module.Remote{ Registry: cmd.opts.RegistryURL, @@ -567,13 +543,30 @@ func (cmd *command) moduleDefinitionFromOptions() (*module.Definition, *Config, var defaultCRPath string if moduleConfig.DefaultCRPath != "" { + isURL, defaultCRURL := module.ParseURL(moduleConfig.DefaultCRPath) + if isURL { + moduleConfig.DefaultCRPath, err = cmd.tmpFiles.DownloadRemoteFileToTmpFile(defaultCRURL.String(), + cmd.opts.Path, "kyma-module-default-cr-*.yaml") + if err != nil { + return nil, nil, fmt.Errorf("%w, %w", ErrDefaultCRFetch, err) + } + } defaultCRPath, err = resolveFilePath(moduleConfig.DefaultCRPath, cmd.opts.Path) if err != nil { return nil, nil, fmt.Errorf("%w, %w", ErrDefaultCRPathValidation, err) } } - moduleManifestPath, err := resolveFilePath(moduleConfig.ManifestPath, cmd.opts.Path) + var moduleManifestPath string + isURL, manifestURL := module.ParseURL(moduleConfig.ManifestPath) + if isURL { + moduleConfig.ManifestPath, err = cmd.tmpFiles.DownloadRemoteFileToTmpFile(manifestURL.String(), cmd.opts.Path, + "kyma-module-manifest-*.yaml") + if err != nil { + return nil, nil, fmt.Errorf("%w, %w", ErrManifestFetch, err) + } + } + moduleManifestPath, err = resolveFilePath(moduleConfig.ManifestPath, cmd.opts.Path) if err != nil { return nil, nil, fmt.Errorf("%w, %w", ErrManifestPathValidation, err) } diff --git a/cmd/kyma/alpha/create/module/module_test.go b/cmd/kyma/alpha/create/module/module_test.go index 6feefa24d..52f6e6419 100644 --- a/cmd/kyma/alpha/create/module/module_test.go +++ b/cmd/kyma/alpha/create/module/module_test.go @@ -8,7 +8,6 @@ import ( "github.com/kyma-project/lifecycle-manager/api/shared" "github.com/kyma-project/cli/internal/cli" - "github.com/kyma-project/cli/pkg/module" ) //go:embed testdata/clusterScopedCRD.yaml @@ -120,8 +119,8 @@ func Test_command_getModuleTemplateAnnotations(t *testing.T) { opts *Options } type args struct { - modCnf *Config - crValidator validator + modCnf *Config + crd []byte } tests := []struct { name string @@ -143,9 +142,7 @@ func Test_command_getModuleTemplateAnnotations(t *testing.T) { }, Version: "1.1.1", }, - crValidator: &module.SingleManifestFileCRValidator{ - Crd: namespacedScopedCrd, - }, + crd: namespacedScopedCrd, }, want: map[string]string{ "annotation1": "value1", @@ -167,9 +164,7 @@ func Test_command_getModuleTemplateAnnotations(t *testing.T) { }, Version: "1.1.1", }, - crValidator: &module.SingleManifestFileCRValidator{ - Crd: clusterScopedCrd, - }, + crd: clusterScopedCrd, }, want: map[string]string{ "annotation1": "value1", @@ -184,9 +179,7 @@ func Test_command_getModuleTemplateAnnotations(t *testing.T) { opts: &Options{Version: "1.0.0"}, }, args: args{ - crValidator: &module.SingleManifestFileCRValidator{ - Crd: namespacedScopedCrd, - }, + crd: namespacedScopedCrd, }, want: map[string]string{ shared.ModuleVersionAnnotation: "1.0.0", @@ -200,7 +193,7 @@ func Test_command_getModuleTemplateAnnotations(t *testing.T) { Command: tt.fields.Command, opts: tt.fields.opts, } - if got := cmd.getModuleTemplateAnnotations(tt.args.modCnf, tt.args.crValidator); !reflect.DeepEqual(got, + if got := cmd.getModuleTemplateAnnotations(tt.args.modCnf, tt.args.crd); !reflect.DeepEqual(got, tt.want) { t.Errorf("getModuleTemplateAnnotations() = %v, want %v", got, tt.want) } diff --git a/cmd/kyma/alpha/create/module/moduleconfig.go b/cmd/kyma/alpha/create/module/moduleconfig.go index 6ae8a0c0d..e0a774db2 100644 --- a/cmd/kyma/alpha/create/module/moduleconfig.go +++ b/cmd/kyma/alpha/create/module/moduleconfig.go @@ -10,25 +10,25 @@ import ( "github.com/kyma-project/cli/pkg/module" - "github.com/blang/semver/v4" + "github.com/Masterminds/semver/v3" "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. - 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 + 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"` + ManifestPath 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"` + DefaultCRPath string `yaml:"defaultCR" comment:"optional, relative path or remote URL to a YAML file containing the default CR for the module"` + ResourceName string `yaml:"resourceName" comment:"optional, default={NAME}-{CHANNEL}, the name for the ModuleTemplate that will be created"` + Namespace string `yaml:"namespace" comment:"optional, default=kcp-system, the namespace where the ModuleTemplate will be deployed"` + Security string `yaml:"security" comment:"optional, name of the security scanners config file"` + Internal bool `yaml:"internal" comment:"optional, default=false, determines whether the ModuleTemplate should have the internal flag or not"` + Beta bool `yaml:"beta" comment:"optional, default=false, determines whether the ModuleTemplate should have the beta flag or not"` + Labels map[string]string `yaml:"labels" comment:"optional, additional labels for the ModuleTemplate"` + Annotations map[string]string `yaml:"annotations" comment:"optional, additional annotations for the ModuleTemplate"` + CustomStateChecks []v1beta2.CustomStateCheck `yaml:"customStateCheck" comment:"optional, specifies custom state check for module"` } const ( @@ -142,7 +142,7 @@ func (cv *configValidator) validateVersion() *configValidator { val = val[1:] } - sv, err := semver.Parse(val) + sv, err := semver.StrictNewVersion(val) if err != nil { return fmt.Errorf("%w for input %q, %w", ErrVersionValidation, cv.config.Version, err) } diff --git a/cmd/kyma/alpha/create/module/opts.go b/cmd/kyma/alpha/create/module/opts.go index a6042e357..5af2bd566 100644 --- a/cmd/kyma/alpha/create/module/opts.go +++ b/cmd/kyma/alpha/create/module/opts.go @@ -2,12 +2,13 @@ package module import ( "fmt" - "github.com/kyma-project/cli/internal/nice" "os" "path/filepath" "regexp" - "github.com/blang/semver/v4" + "github.com/kyma-project/cli/internal/nice" + + "github.com/Masterminds/semver/v3" "github.com/pkg/errors" "github.com/kyma-project/cli/internal/cli" @@ -49,7 +50,9 @@ const ( var ( ErrChannelValidation = errors.New("channel validation failed") + ErrManifestFetch = errors.New("remote YAML manifest fetch failed") ErrManifestPathValidation = errors.New("YAML manifest path validation failed") + ErrDefaultCRFetch = errors.New("remote default CR fetch failed") ErrDefaultCRPathValidation = errors.New("default CR path validation failed") ErrNameValidation = errors.New("name validation failed") ErrNamespaceValidation = errors.New("namespace validation failed") @@ -62,7 +65,7 @@ func NewOptions(o *cli.Options) *Options { } func (o *Options) validateVersion() error { - sv, err := semver.ParseTolerant(o.Version) + sv, err := semver.NewVersion(o.Version) if err != nil { return err } diff --git a/cmd/kyma/alpha/create/scaffold/opts.go b/cmd/kyma/alpha/create/scaffold/opts.go new file mode 100644 index 000000000..a283ec6c3 --- /dev/null +++ b/cmd/kyma/alpha/create/scaffold/opts.go @@ -0,0 +1,100 @@ +package scaffold + +import ( + "fmt" + "os" + + "github.com/kyma-project/cli/internal/cli" + "github.com/pkg/errors" +) + +// Options specifies the flags for the scaffold command +type Options struct { + *cli.Options + + Overwrite bool + Directory string + + ModuleConfigFile string + ManifestFile string + SecurityConfigFile string + DefaultCRFile string + + ModuleName string + ModuleVersion string + ModuleChannel string +} + +func (o *Options) securityConfigFileConfigured() bool { + return o.SecurityConfigFile != "" +} + +func (o *Options) defaultCRFileConfigured() bool { + return o.DefaultCRFile != "" +} + +var ( + errDirNotExists = errors.New("provided directory does not exist") + errNotDirectory = errors.New("provided path is not a directory") + errModuleConfigExists = errors.New("module config file already exists. use --overwrite flag to overwrite it") + errModuleNameEmpty = errors.New("--module-name flag must not be empty") + errModuleVersionEmpty = errors.New("--module-version flag must not be empty") + errModuleChannelEmpty = errors.New("--module-channel flag must not be empty") + errManifestFileEmpty = errors.New("--gen-manifest flag must not be empty") + errModuleConfigEmpty = errors.New("--module-config flag must not be empty") + errManifestCreation = errors.New("could not generate manifest") + errDefaultCRCreationFailed = errors.New("could not generate default CR") + errModuleConfigCreationFailed = errors.New("could not generate module config") + errSecurityConfigCreationFailed = errors.New("could not generate security config") +) + +// NewOptions creates options with default values +func NewOptions(o *cli.Options) *Options { + return &Options{Options: o} +} + +func (o *Options) Validate() error { + if o.ModuleName == "" { + return errModuleNameEmpty + } + + if o.ModuleVersion == "" { + return errModuleVersionEmpty + } + + if o.ModuleChannel == "" { + return errModuleChannelEmpty + } + + err := o.validateDirectory() + if err != nil { + return err + } + + if o.ModuleConfigFile == "" { + return errModuleConfigEmpty + } + + if o.ManifestFile == "" { + return errManifestFileEmpty + } + + return nil +} + +func (o *Options) validateDirectory() error { + fi, err := os.Stat(o.Directory) + + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("%w: %s", errDirNotExists, o.Directory) + } + return err + } + + if !fi.IsDir() { + return fmt.Errorf("%w: %s", errNotDirectory, o.Directory) + } + + return nil +} diff --git a/cmd/kyma/alpha/create/scaffold/scaffold.go b/cmd/kyma/alpha/create/scaffold/scaffold.go new file mode 100644 index 000000000..62bf4038c --- /dev/null +++ b/cmd/kyma/alpha/create/scaffold/scaffold.go @@ -0,0 +1,259 @@ +package scaffold + +import ( + "context" + "fmt" + + "github.com/kyma-project/cli/internal/cli" + scaffgen "github.com/kyma-project/cli/pkg/module/scaffold" + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +const ( + defaultCRFlagName = "gen-default-cr" + defaultCRFlagDefault = "default-cr.yaml" + manifestFileFlagName = "gen-manifest" + manifestFileFlagDefault = "manifest.yaml" + moduleConfigFileFlagName = "module-config" + moduleConfigFileFlagDefault = "scaffold-module-config.yaml" + securityConfigFlagName = "gen-security-config" + securityConfigFlagDefault = "sec-scanners-config.yaml" +) + +type command struct { + cli.Command + opts *Options +} + +func NewCmd(o *Options) *cobra.Command { + c := command{ + Command: cli.Command{Options: o.Options}, + opts: o, + } + + cmd := &cobra.Command{ + Use: "scaffold [--module-name MODULE_NAME --module-version MODULE_VERSION --module-channel CHANNEL] [--directory MODULE_DIRECTORY] [flags]", + Short: "Generates necessary files required for module creation", + Long: `Scaffold generates or configures the necessary files for creating a new module in Kyma. This includes setting up +a basic directory structure and creating default files based on the provided flags. + +The command is designed to streamline the module creation process in Kyma, making it easier and more +efficient for developers to get started with new modules. It supports customization through various flags, +allowing for a tailored scaffolding experience according to the specific needs of the module being created. + +The command generates or uses the following files: + - Module Config: + Enabled: Always + Adjustable with flag: --module-config=VALUE + Generated when: The file doesn't exist or the --overwrite=true flag is provided + Default file name: scaffold-module-config.yaml + - Manifest: + Enabled: Always + Adjustable with flag: --gen-manifest=VALUE + Generated when: The file doesn't exist. If the file exists, it's name is used in the generated module configuration file + Default file name: manifest.yaml + - Default CR(s): + Enabled: When the flag --gen-default-cr is provided with or without value + Adjustable with flag: --gen-default-cr[=VALUE], if provided without an explicit VALUE, the default value is used + Generated when: The file doesn't exist. If the file exists, it's name is used in the generated module configuration file + Default file name: default-cr.yaml + - Security Scanners Config: + Enabled: When the flag --gen-security-config is provided with or without value + Adjustable with flag: --gen-security-config[=VALUE], if provided without an explicit VALUE, the default value is used + Generated when: The file doesn't exist. If the file exists, it's name is used in the generated module configuration file + Default file name: sec-scanners-config.yaml + +**NOTE:**: To protect the user from accidental file overwrites, this command by default doesn't overwrite any files. +Only the module config file may be force-overwritten when the --overwrite=true flag is used. + +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. +`, + Example: `Generate a minimal scaffold for a module - only a blank manifest file and module config file is generated using defaults + kyma alpha create scaffold +Generate a scaffold providing required values explicitly + kyma alpha create scaffold --module-name="kyma-project.io/module/testmodule" --module-version="0.1.1" --module-channel=fast +Generate a scaffold with a manifest file, default CR and security-scanners config for a module + kyma alpha create 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 + kyma alpha create scaffold --gen-manifest="my-manifest.yaml" --gen-default-cr="my-cr.yaml" --gen-security-config="my-seccfg.yaml" + +`, + RunE: func(cobraCmd *cobra.Command, args []string) error { return c.Run(cobraCmd.Context()) }, + } + cmd.Flags().StringVar( + &o.ModuleName, "module-name", "kyma-project.io/module/mymodule", + "Specifies the module name in the generated module config file", + ) + cmd.Flags().StringVar( + &o.ModuleVersion, "module-version", "0.0.1", + "Specifies the module version in the generated module config file", + ) + cmd.Flags().StringVar( + &o.ModuleChannel, "module-channel", "regular", + "Specifies the module channel in the generated module config file", + ) + cmd.Flags().StringVar( + &o.ModuleConfigFile, moduleConfigFileFlagName, moduleConfigFileFlagDefault, + "Specifies the name for the generated module configuration file", + ) + cmd.Flags().Lookup(moduleConfigFileFlagName).NoOptDefVal = moduleConfigFileFlagDefault + + cmd.Flags().StringVar( + &o.ManifestFile, manifestFileFlagName, manifestFileFlagDefault, + "Specifies the manifest in the generated module config. A blank manifest file is generated if it doesn't exist", + ) + cmd.Flags().Lookup(manifestFileFlagName).NoOptDefVal = manifestFileFlagDefault + + cmd.Flags().StringVar( + &o.DefaultCRFile, defaultCRFlagName, "", + "Specifies the defaultCR in the generated module config. A blank defaultCR file is generated if it doesn't exist", + ) + cmd.Flags().Lookup(defaultCRFlagName).NoOptDefVal = defaultCRFlagDefault + + cmd.Flags().StringVar( + &o.SecurityConfigFile, securityConfigFlagName, "", + "Specifies the security file in the generated module config. A scaffold security config file is generated if it doesn't exist", + ) + cmd.Flags().Lookup(securityConfigFlagName).NoOptDefVal = securityConfigFlagDefault + + cmd.Flags().BoolVarP( + &o.Overwrite, "overwrite", "o", false, + "Specifies if the command overwrites an existing module configuration file", + ) + cmd.Flags().StringVarP( + &o.Directory, "directory", "d", "./", + "Specifies the directory where the scaffolding shall be generated", + ) + + return cmd +} + +func (cmd *command) Run(_ context.Context) error { + + if cmd.opts.CI { + cmd.Factory.NonInteractive = true + } + if cmd.opts.Verbose { + cmd.Factory.UseLogger = true + } + + l := cli.NewLogger(cmd.opts.Verbose).Sugar() + undo := zap.RedirectStdLog(l.Desugar()) + defer undo() + + if !cmd.opts.NonInteractive { + cli.AlphaWarn() + } + + cmd.NewStep("Validating") + if err := cmd.opts.Validate(); err != nil { + cmd.CurrentStep.Failuref("%s", err.Error()) + return fmt.Errorf("%w", err) + } + + sgen := cmd.scaffoldGeneratorFromOptions() + + moduleConfigExists, err := sgen.ModuleConfigFileExists() + if err != nil { + cmd.CurrentStep.Failuref("%s", err.Error()) + return fmt.Errorf("%w", err) + } + if moduleConfigExists && !cmd.opts.Overwrite { + cmd.CurrentStep.Failuref("%s", errModuleConfigExists.Error()) + return fmt.Errorf("%w", errModuleConfigExists) + } + cmd.CurrentStep.Success() + + manifestFileExists, err := sgen.ManifestFileExists() + if err != nil { + return err + } + cmd.NewStep("Configuring manifest file...\n") + if manifestFileExists { + cmd.CurrentStep.Successf("The manifest file exists, reusing: %s", sgen.ManifestFilePath()) + } else { + cmd.CurrentStep.Status("Generating the manifest file") + err := sgen.GenerateManifest() + if err != nil { + cmd.CurrentStep.Failuref("%s: %s", errManifestCreation.Error(), err.Error()) + return fmt.Errorf("%w: %s", errManifestCreation, err.Error()) + } + + cmd.CurrentStep.Successf("Generated a blank manifest file: %s", sgen.ManifestFilePath()) + } + + if cmd.opts.defaultCRFileConfigured() { + defaultCRFileExists, err := sgen.DefaultCRFileExists() + if err != nil { + return err + } + cmd.NewStep("Configuring defaultCR file...\n") + if defaultCRFileExists { + cmd.CurrentStep.Successf("The defaultCR file exists, reusing: %s", sgen.DefaultCRFilePath()) + } else { + cmd.CurrentStep.Status("Generating the default CR file") + err := sgen.GenerateDefaultCRFile() + if err != nil { + cmd.CurrentStep.Failuref("%s: %s", errDefaultCRCreationFailed.Error(), err.Error()) + return fmt.Errorf("%w: %s", errDefaultCRCreationFailed, err.Error()) + } + + cmd.CurrentStep.Successf("Generated a blank defaultCR file: %s", sgen.DefaultCRFilePath()) + } + } + + if cmd.opts.securityConfigFileConfigured() { + secCfgFileExists, err := sgen.SecurityConfigFileExists() + if err != nil { + return err + } + cmd.NewStep("Configuring security-scanners config file...\n") + if secCfgFileExists { + cmd.CurrentStep.Successf("The security-scanners config file exists, reusing: %s", sgen.SecurityConfigFilePath()) + } else { + cmd.CurrentStep.Status("Generating security-scanners config file") + err := sgen.GenerateSecurityConfigFile() + if err != nil { + cmd.CurrentStep.Failuref("%s: %s", errSecurityConfigCreationFailed.Error(), err.Error()) + return fmt.Errorf("%w: %s", errSecurityConfigCreationFailed, err.Error()) + } + + cmd.CurrentStep.Successf("Generated security-scanners config file - %s", sgen.SecurityConfigFilePath()) + } + } + + cmd.NewStep("Generating module config file...\n") + + err = sgen.GenerateModuleConfigFile() + if err != nil { + cmd.CurrentStep.Failuref("%s: %s", errModuleConfigCreationFailed.Error(), err.Error()) + return fmt.Errorf("%w: %s", errModuleConfigCreationFailed, err.Error()) + } + + cmd.CurrentStep.Successf("Generated module config file: %s", sgen.ModuleConfigFilePath()) + + return nil +} + +func (cmd *command) scaffoldGeneratorFromOptions() *scaffgen.Generator { + + res := scaffgen.Generator{ + ModuleName: cmd.opts.ModuleName, + ModuleVersion: cmd.opts.ModuleVersion, + ModuleChannel: cmd.opts.ModuleChannel, + Directory: cmd.opts.Directory, + ModuleConfigFile: cmd.opts.ModuleConfigFile, + ManifestFile: cmd.opts.ManifestFile, + SecurityConfigFile: cmd.opts.SecurityConfigFile, + DefaultCRFile: cmd.opts.DefaultCRFile, + } + + return &res +} diff --git a/cmd/kyma/provision/cmd.go b/cmd/kyma/provision/cmd.go index 640af8b93..8cb66d1b0 100644 --- a/cmd/kyma/provision/cmd.go +++ b/cmd/kyma/provision/cmd.go @@ -4,9 +4,9 @@ import ( "github.com/spf13/cobra" ) -const DefaultK8sShortVersion = "1.26" //default Kubernetes version for provisioning clusters on hyperscalers -const DefaultK8sFullVersion = DefaultK8sShortVersion + ".6" //default Kubernetes version with the "patch" component (mainly for K3d/K3s) -const DefaultGardenLinuxVersion = "934.9.0" //default Garden Linux version +const DefaultK8sShortVersion = "1.27" // default Kubernetes version for provisioning clusters on hyperscalers +const DefaultK8sFullVersion = DefaultK8sShortVersion + ".9" // default Kubernetes version with the "patch" component (mainly for K3d/K3s) +const DefaultGardenLinuxVersion = "934.9.0" // default Garden Linux version // NewCmd creates a new provision command func NewCmd() *cobra.Command { diff --git a/cmd/kyma/provision/gardener/aws/cmd.go b/cmd/kyma/provision/gardener/aws/cmd.go index 2470caee9..45f8c65ec 100644 --- a/cmd/kyma/provision/gardener/aws/cmd.go +++ b/cmd/kyma/provision/gardener/aws/cmd.go @@ -13,7 +13,7 @@ func NewCmd(o *Options) *cobra.Command { Short: "Provisions a Kubernetes cluster using Gardener on Amazon Web Services (AWS).", Long: `Use this command to provision Kubernetes clusters with Gardener on AWS for Kyma installation. To successfully provision a cluster on AWS, you must first create a service account to pass its details as one of the command parameters. -Check the roles and create a service account using instructions at https://gardener.cloud/050-tutorials/content/howto/gardener_aws/. +Check the roles and create a service account using instructions at https://gardener.cloud/docs/gardener/service-account-manager/. Use service account details to create a Secret and import it in Gardener.`, RunE: func(_ *cobra.Command, _ []string) error { return c.Run() }, diff --git a/cmd/kyma/provision/gardener/gcp/cmd.go b/cmd/kyma/provision/gardener/gcp/cmd.go index 4cb45dc31..a8d8085ac 100644 --- a/cmd/kyma/provision/gardener/gcp/cmd.go +++ b/cmd/kyma/provision/gardener/gcp/cmd.go @@ -14,7 +14,7 @@ func NewCmd(o *Options) *cobra.Command { Short: "Provisions a Kubernetes cluster using Gardener on Google Cloud Platform (GCP).", Long: `Use this command to provision Kubernetes clusters with Gardener on GCP for Kyma installation. To successfully provision a cluster on GCP, you must first create a service account to pass its details as one of the command parameters. -Check the roles and create a service account using instructions at https://gardener.cloud/050-tutorials/content/howto/gardener_gcp/. +Check the roles and create a service account using instructions at https://gardener.cloud/docs/gardener/service-account-manager/. Use service account details to create a Secret and import it in Gardener.`, RunE: func(_ *cobra.Command, _ []string) error { return c.Run() }, diff --git a/docs/gen-docs/kyma_alpha_create.md b/docs/gen-docs/kyma_alpha_create.md index 0e18a1d7c..d5e630632 100644 --- a/docs/gen-docs/kyma_alpha_create.md +++ b/docs/gen-docs/kyma_alpha_create.md @@ -23,4 +23,5 @@ Use this command to create resources on the Kyma cluster. * [kyma alpha](kyma_alpha.md) - Experimental commands * [kyma alpha create module](kyma_alpha_create_module.md) - Creates a module bundled as an OCI artifact +* [kyma alpha create scaffold](kyma_alpha_create_scaffold.md) - Generates necessary files required for module creation diff --git a/docs/gen-docs/kyma_alpha_create_module.md b/docs/gen-docs/kyma_alpha_create_module.md index d0d417619..d9c39f7fd 100644 --- a/docs/gen-docs/kyma_alpha_create_module.md +++ b/docs/gen-docs/kyma_alpha_create_module.md @@ -28,6 +28,7 @@ The module config file is a YAML file used to configure the following attributes - 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 +- mandatory: a boolean, optional, default=false, indicates whether the module is mandatory to be installed on all clusters - manifest: a string, required, reference to the manifest, must be a relative file name - defaultCR: a string, optional, reference to a YAML file containing the default CR for the module, must be a relative file name - resourceName: a string, optional, default={NAME}-{CHANNEL}, the name for the ModuleTemplate CR that will be created diff --git a/docs/gen-docs/kyma_alpha_create_scaffold.md b/docs/gen-docs/kyma_alpha_create_scaffold.md new file mode 100644 index 000000000..cbf8d2b1e --- /dev/null +++ b/docs/gen-docs/kyma_alpha_create_scaffold.md @@ -0,0 +1,96 @@ +--- +title: kyma alpha create scaffold +--- + +Generates necessary files required for module creation + +## Synopsis + +Scaffold generates or configures the necessary files for creating a new module in Kyma. This includes setting up +a basic directory structure and creating default files based on the provided flags. + +The command is designed to streamline the module creation process in Kyma, making it easier and more +efficient for developers to get started with new modules. It supports customization through various flags, +allowing for a tailored scaffolding experience according to the specific needs of the module being created. + +The command generates or uses the following files: + - Module Config: + Enabled: Always + Adjustable with flag: --module-config=VALUE + Generated when: The file doesn't exist or the --overwrite=true flag is provided + Default file name: scaffold-module-config.yaml + - Manifest: + Enabled: Always + Adjustable with flag: --gen-manifest=VALUE + Generated when: The file doesn't exist. If the file exists, it's name is used in the generated module configuration file + Default file name: manifest.yaml + - Default CR(s): + Enabled: When the flag --gen-default-cr is provided with or without value + Adjustable with flag: --gen-default-cr[=VALUE], if provided without an explicit VALUE, the default value is used + Generated when: The file doesn't exist. If the file exists, it's name is used in the generated module configuration file + Default file name: default-cr.yaml + - Security Scanners Config: + Enabled: When the flag --gen-security-config is provided with or without value + Adjustable with flag: --gen-security-config[=VALUE], if provided without an explicit VALUE, the default value is used + Generated when: The file doesn't exist. If the file exists, it's name is used in the generated module configuration file + Default file name: sec-scanners-config.yaml + +**NOTE:**: To protect the user from accidental file overwrites, this command by default doesn't overwrite any files. +Only the module config file may be force-overwritten when the --overwrite=true flag is used. + +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 +kyma alpha create scaffold [--module-name MODULE_NAME --module-version MODULE_VERSION --module-channel CHANNEL] [--directory MODULE_DIRECTORY] [flags] +``` + +## Examples + +```bash +Generate a minimal scaffold for a module - only a blank manifest file and module config file is generated using defaults + kyma alpha create scaffold +Generate a scaffold providing required values explicitly + kyma alpha create scaffold --module-name="kyma-project.io/module/testmodule" --module-version="0.1.1" --module-channel=fast +Generate a scaffold with a manifest file, default CR and security-scanners config for a module + kyma alpha create 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 + kyma alpha create scaffold --gen-manifest="my-manifest.yaml" --gen-default-cr="my-cr.yaml" --gen-security-config="my-seccfg.yaml" + + +``` + +## Flags + +```bash + -d, --directory string Specifies the directory where the scaffolding shall be generated (default "./") + --gen-default-cr string[="default-cr.yaml"] Specifies the defaultCR in the generated module config. A blank defaultCR file is generated if it doesn't exist + --gen-manifest string[="manifest.yaml"] 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[="sec-scanners-config.yaml"] Specifies the security file in the generated module config. A scaffold security config file is generated if it doesn't exist + --module-channel string Specifies the module channel in the generated module config file (default "regular") + --module-config string[="scaffold-module-config.yaml"] Specifies the name for the generated module configuration file (default "scaffold-module-config.yaml") + --module-name string Specifies the module name in the generated module 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 +``` + +## Flags inherited from parent commands + +```bash + --ci Enables the CI mode to run on CI/CD systems. It avoids any user interaction (such as no dialog prompts) and ensures that logs are formatted properly in log files (such as no spinners for CLI steps). + -h, --help Provides command help. + --kubeconfig string Path to theĀ kubeconfigĀ file. If undefined, Kyma CLI uses the KUBECONFIG environment variable, or falls back "/$HOME/.kube/config". + --non-interactive Enables the non-interactive shell mode (no colorized output, no spinner). + -v, --verbose Displays details of actions triggered by the command. +``` + +## See also + +* [kyma alpha create](kyma_alpha_create.md) - Creates resources on the Kyma cluster. + diff --git a/docs/gen-docs/kyma_provision_gardener_aws.md b/docs/gen-docs/kyma_provision_gardener_aws.md index 7c138f207..d875f92b2 100644 --- a/docs/gen-docs/kyma_provision_gardener_aws.md +++ b/docs/gen-docs/kyma_provision_gardener_aws.md @@ -8,7 +8,7 @@ Provisions a Kubernetes cluster using Gardener on Amazon Web Services (AWS). Use this command to provision Kubernetes clusters with Gardener on AWS for Kyma installation. To successfully provision a cluster on AWS, you must first create a service account to pass its details as one of the command parameters. -Check the roles and create a service account using instructions at https://gardener.cloud/050-tutorials/content/howto/gardener_aws/. +Check the roles and create a service account using instructions at https://gardener.cloud/docs/gardener/service-account-manager/. Use service account details to create a Secret and import it in Gardener. ```bash @@ -27,7 +27,7 @@ kyma provision gardener aws [flags] --hibernation-end string Cron expression to configure when the cluster should stop hibernating --hibernation-location string Timezone in which the hibernation schedule should be applied. (default "Europe/Berlin") --hibernation-start string Cron expression to configure when the cluster should start hibernating (default "00 18 * * 1,2,3,4,5") - -k, --kube-version string Kubernetes version of the cluster. (default "1.26") + -k, --kube-version string Kubernetes version of the cluster. (default "1.27") -n, --name string Name of the cluster to provision. (required) -p, --project string Name of the Gardener project where you provision the cluster. (required) -r, --region string Region of the cluster. (default "eu-west-3") diff --git a/docs/gen-docs/kyma_provision_gardener_az.md b/docs/gen-docs/kyma_provision_gardener_az.md index d33ee8448..4cb7812ec 100644 --- a/docs/gen-docs/kyma_provision_gardener_az.md +++ b/docs/gen-docs/kyma_provision_gardener_az.md @@ -26,7 +26,7 @@ kyma provision gardener az [flags] --hibernation-end string Cron expression to configure when the cluster should stop hibernating --hibernation-location string Timezone in which the hibernation schedule should be applied. (default "Europe/Berlin") --hibernation-start string Cron expression to configure when the cluster should start hibernating (default "00 18 * * 1,2,3,4,5") - -k, --kube-version string Kubernetes version of the cluster. (default "1.26") + -k, --kube-version string Kubernetes version of the cluster. (default "1.27") -n, --name string Name of the cluster to provision. (required) -p, --project string Name of the Gardener project where you provision the cluster. (required) -r, --region string Region of the cluster. (default "westeurope") diff --git a/docs/gen-docs/kyma_provision_gardener_gcp.md b/docs/gen-docs/kyma_provision_gardener_gcp.md index bcb7effe6..02e33a71c 100644 --- a/docs/gen-docs/kyma_provision_gardener_gcp.md +++ b/docs/gen-docs/kyma_provision_gardener_gcp.md @@ -8,7 +8,7 @@ Provisions a Kubernetes cluster using Gardener on Google Cloud Platform (GCP). Use this command to provision Kubernetes clusters with Gardener on GCP for Kyma installation. To successfully provision a cluster on GCP, you must first create a service account to pass its details as one of the command parameters. -Check the roles and create a service account using instructions at https://gardener.cloud/050-tutorials/content/howto/gardener_gcp/. +Check the roles and create a service account using instructions at https://gardener.cloud/docs/gardener/service-account-manager/. Use service account details to create a Secret and import it in Gardener. ```bash @@ -27,7 +27,7 @@ kyma provision gardener gcp [flags] --hibernation-end string Cron expression to configure when the cluster should stop hibernating --hibernation-location string Timezone in which the hibernation schedule should be applied. (default "Europe/Berlin") --hibernation-start string Cron expression to configure when the cluster should start hibernating (default "00 18 * * 1,2,3,4,5") - -k, --kube-version string Kubernetes version of the cluster. (default "1.26") + -k, --kube-version string Kubernetes version of the cluster. (default "1.27") -n, --name string Name of the cluster to provision. (required) -p, --project string Name of the Gardener project where you provision the cluster. (required) -r, --region string Region of the cluster. (default "europe-west3") diff --git a/docs/gen-docs/kyma_provision_k3d.md b/docs/gen-docs/kyma_provision_k3d.md index bc4a4c425..5aa8b5f23 100644 --- a/docs/gen-docs/kyma_provision_k3d.md +++ b/docs/gen-docs/kyma_provision_k3d.md @@ -18,7 +18,7 @@ kyma provision k3d [flags] --k3d-arg strings One or more arguments passed to the k3d provisioning command (e.g. --k3d-arg='--no-rollback') --k3d-registry-arg strings One or more arguments passed to the k3d registry create command (e.g. --k3d-registry-arg='--default-network podman') -s, --k3s-arg strings One or more arguments passed from k3d to the k3s command (format: ARG@NODEFILTER[;@NODEFILTER]) - -k, --kube-version string Kubernetes version of the cluster (default "1.26.6") + -k, --kube-version string Kubernetes version of the cluster (default "1.27.9") --name string Name of the Kyma cluster (default "kyma") -p, --port strings Map ports 80 and 443 of K3D loadbalancer (e.g. -p 80:80@loadbalancer -p 443:443@loadbalancer) (default [80:80@loadbalancer,443:443@loadbalancer]) --registry-port string Specify the port on which the k3d registry will be exposed (default "5001") diff --git a/go.mod b/go.mod index 731fb1199..d018e6faa 100644 --- a/go.mod +++ b/go.mod @@ -7,24 +7,23 @@ replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.5 require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/avast/retry-go v3.0.0+incompatible - github.com/blang/semver/v4 v4.0.0 github.com/briandowns/spinner v1.23.0 - github.com/containerd/containerd v1.7.10 + github.com/containerd/containerd v1.7.12 github.com/daviddengcn/go-colortext v1.0.0 github.com/docker/cli v24.0.7+incompatible github.com/docker/docker v24.0.7+incompatible - github.com/docker/go-connections v0.4.0 + github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.16.0 - github.com/go-git/go-git/v5 v5.10.1 - github.com/go-logr/logr v1.3.0 + github.com/go-git/go-git/v5 v5.11.0 + github.com/go-logr/logr v1.4.1 github.com/go-logr/zapr v1.3.0 github.com/imdario/mergo v1.0.0 - github.com/kyma-incubator/reconciler v0.0.0-20231215092926-a44f7293b791 + github.com/kyma-incubator/reconciler v0.0.0-20240122095942-4be5f11f106f github.com/kyma-project/hydroform/function v0.0.0-20230831071441-f3501c89bace github.com/kyma-project/hydroform/provision v0.0.0-20230831071441-f3501c89bace github.com/kyma-project/lifecycle-manager/api v0.0.0-20231212124126-3539b2df72e0 github.com/mandelsoft/vfs v0.4.0 - github.com/onsi/ginkgo/v2 v2.13.2 + github.com/onsi/ginkgo/v2 v2.15.0 github.com/onsi/gomega v1.30.0 github.com/open-component-model/ocm v0.4.0 github.com/opencontainers/go-digest v1.0.0 @@ -36,24 +35,24 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.13.2 - istio.io/client-go v1.20.0 - k8s.io/klog/v2 v2.110.1 - sigs.k8s.io/controller-runtime v0.16.3 - sigs.k8s.io/kustomize/api v0.15.0 - sigs.k8s.io/kustomize/kyaml v0.15.0 + helm.sh/helm/v3 v3.14.0 + istio.io/client-go v1.20.2 + k8s.io/klog/v2 v2.120.1 + sigs.k8s.io/controller-runtime v0.17.0 + sigs.k8s.io/kustomize/api v0.16.0 + sigs.k8s.io/kustomize/kyaml v0.16.0 sigs.k8s.io/yaml v1.4.0 ) require ( - k8s.io/api v0.28.4 - k8s.io/apiextensions-apiserver v0.28.4 - k8s.io/apimachinery v0.28.4 - k8s.io/apiserver v0.28.4 // indirect - k8s.io/cli-runtime v0.28.4 - k8s.io/client-go v0.28.4 - k8s.io/component-base v0.28.4 // indirect - k8s.io/kubectl v0.28.4 // indirect + k8s.io/api v0.29.1 + k8s.io/apiextensions-apiserver v0.29.1 + k8s.io/apimachinery v0.29.1 + k8s.io/apiserver v0.29.1 // indirect + k8s.io/cli-runtime v0.29.1 + k8s.io/client-go v0.29.1 + k8s.io/component-base v0.29.1 // indirect + k8s.io/kubectl v0.29.1 // indirect ) require ( @@ -127,7 +126,7 @@ require ( github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect - github.com/cloudflare/circl v1.3.6 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect @@ -152,10 +151,11 @@ require ( github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.8.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/structs v1.1.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/gardener/gardener v1.78.0 // indirect @@ -201,6 +201,7 @@ require ( github.com/google/uuid v1.4.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -216,6 +217,7 @@ require ( github.com/invopop/jsonschema v0.7.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect + github.com/jellydator/ttlcache/v3 v3.1.1 // indirect github.com/jinzhu/gorm v1.9.16 // indirect github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect @@ -259,6 +261,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/nwaples/rardecode v1.1.3 // indirect github.com/oklog/ulid v1.3.1 // indirect @@ -274,7 +277,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect @@ -329,6 +332,7 @@ require ( github.com/zeebo/errs v1.3.0 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect @@ -336,16 +340,16 @@ require ( go.step.sm/crypto v0.39.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.16.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.16.0 // indirect + golang.org/x/tools v0.16.1 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/api v0.153.0 // indirect google.golang.org/appengine v1.6.8 // indirect @@ -361,7 +365,7 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - istio.io/api v1.20.0 // indirect + istio.io/api v1.20.2-0.20231213020515-8655fab91d5d // indirect k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 // indirect k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect oras.land/oras-go v1.2.4 // indirect diff --git a/go.sum b/go.sum index fbc8ebec3..33878fc9a 100644 --- a/go.sum +++ b/go.sum @@ -371,8 +371,6 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= @@ -434,8 +432,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= -github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cloudfoundry-incubator/candiedyaml v0.0.0-20170901234223-a41693b7b7af h1:6Cpkahw28+gcBdnXQL7LcMTX488+6jl6hfoTMRT6Hm4= github.com/cloudfoundry-incubator/candiedyaml v0.0.0-20170901234223-a41693b7b7af/go.mod h1:dOLSIXcRQJiDS1vlrYFNJicoHNZLsBKideE+70hGdV4= @@ -497,8 +495,8 @@ github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTV github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= -github.com/containerd/containerd v1.7.10 h1:2nfZyT8BV0C3iKu/SsGxKVAf9dp5W7l9nA8JmWmDGuo= -github.com/containerd/containerd v1.7.10/go.mod h1:0/W44LWEYfSHoxBtsHIiNU/duEkgpMokemafHVCpq9Y= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -668,8 +666,9 @@ github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E1 github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= @@ -714,10 +713,10 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= -github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= +github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -772,8 +771,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= -github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -795,8 +794,8 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= @@ -1078,6 +1077,8 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -1212,8 +1213,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= -github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= -github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= +github.com/jellydator/ttlcache/v3 v3.1.1 h1:RCgYJqo3jgvhl+fEWvjNW8thxGWsgxi+TPhRir1Y9y8= +github.com/jellydator/ttlcache/v3 v3.1.1/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= @@ -1305,8 +1306,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/kyma-incubator/reconciler v0.0.0-20231215092926-a44f7293b791 h1:pG9rc3gN7c9HO2dqY2arjc1ThdB4xz+ZU7SuAPIbsJg= -github.com/kyma-incubator/reconciler v0.0.0-20231215092926-a44f7293b791/go.mod h1:3MslgRhxOBC4ndwrU2KotfMw0euVIF+pMoVRPnxrRMw= +github.com/kyma-incubator/reconciler v0.0.0-20240122095942-4be5f11f106f h1:8sXZuYa2Ftfbprdcl49gGL8e33h3x6UdHEQ+cyd6wdo= +github.com/kyma-incubator/reconciler v0.0.0-20240122095942-4be5f11f106f/go.mod h1:nN7iVzUnRnPHR600g7RalS0g8naLq5j86GoGmskr7qg= github.com/kyma-project/hydroform/function v0.0.0-20230831071441-f3501c89bace h1:ffL1aww9HEpYrzdc1nd9BbwcrLD6GB8qfYnJ3ikZy80= github.com/kyma-project/hydroform/function v0.0.0-20230831071441-f3501c89bace/go.mod h1:9URbz36+qhEwa3aF8+DAFVCGrgQJHi87KAaJmrAYKVI= github.com/kyma-project/hydroform/provision v0.0.0-20230831071441-f3501c89bace h1:0MkoaSa4KfIwJWMPcP8t92aGf3GH9TlFXU4WIYxQxAs= @@ -1478,6 +1479,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= @@ -1514,8 +1516,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= -github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1627,8 +1629,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -1973,6 +1975,8 @@ go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUz go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= @@ -2061,8 +2065,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2356,8 +2360,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2481,8 +2485,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2722,8 +2726,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w= -helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0= +helm.sh/helm/v3 v3.14.0 h1:TaZIH6uOchn7L27ptwnnuHJiFrT/BsD4dFdp/HLT2nM= +helm.sh/helm/v3 v3.14.0/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2731,46 +2735,46 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -istio.io/api v1.20.0 h1:heE1eQoMsuZlwWOf7Xm8TKqKLNKVs11G/zMe5QyR1u4= -istio.io/api v1.20.0/go.mod h1:hm1PE/mGdIAsjCDkTIAplP53H7TjO5LUQCiVvF26SVg= -istio.io/client-go v1.20.0 h1:TSSv6A4sYvuBtoKOwyuRmBmPwSb4s++lWlh7RB7+7gY= -istio.io/client-go v1.20.0/go.mod h1:6D76gZsdjz8JtVeIarUYdOn3WA8Zh+j8fIv2+2K3M+Q= +istio.io/api v1.20.2-0.20231213020515-8655fab91d5d h1:Yz1kAzxwyJtzI6FmXiWl95I6KaCKVLXS1leQpv4o/pY= +istio.io/api v1.20.2-0.20231213020515-8655fab91d5d/go.mod h1:hm1PE/mGdIAsjCDkTIAplP53H7TjO5LUQCiVvF26SVg= +istio.io/client-go v1.20.2 h1:FL99qw5f5W+QFPHutLpGOoPmoKgLwNFrGCEemAvLm00= +istio.io/client-go v1.20.2/go.mod h1:mub0nwPDAj98cjns7KYLzbvDk0Fg9rx0k2o+KZ4UIUY= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= -k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU= -k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM= +k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= +k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= +k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= +k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= +k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= -k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg= -k8s.io/apiserver v0.28.4/go.mod h1:Idq71oXugKZoVGUUL2wgBCTHbUR+FYTWa4rq9j4n23w= -k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q= -k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k= +k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g= +k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw= +k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= +k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= +k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= +k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= -k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo= -k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU= +k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= +k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= @@ -2784,16 +2788,16 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 h1:vzKzxN5uyJZLY8HL1/OovW7BJefnsBIWt8T7Gjh2boQ= k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/kubectl v0.28.4 h1:gWpUXW/T7aFne+rchYeHkyB8eVDl5UZce8G4X//kjUQ= -k8s.io/kubectl v0.28.4/go.mod h1:CKOccVx3l+3MmDbkXtIUtibq93nN2hkDR99XDCn7c/c= +k8s.io/kubectl v0.29.1 h1:rWnW3hi/rEUvvg7jp4iYB68qW5un/urKbv7fu3Vj0/s= +k8s.io/kubectl v0.29.1/go.mod h1:SZzvLqtuOJYSvZzPZR9weSuP0wDQ+N37CENJf0FhDF4= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -2835,14 +2839,14 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= -sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= +sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.15.0 h1:6Ca88kEOBVotHDw+y2IsIMYtg9Pvv7MKpW9JMyF/OH4= -sigs.k8s.io/kustomize/api v0.15.0/go.mod h1:p19kb+E14gN7zcIBR/nhByJDAfUa7N8mp6ZdH/mMXbg= -sigs.k8s.io/kustomize/kyaml v0.15.0 h1:ynlLMAxDhrY9otSg5GYE2TcIz31XkGZ2Pkj7SdolD84= -sigs.k8s.io/kustomize/kyaml v0.15.0/go.mod h1:+uMkBahdU1KNOj78Uta4rrXH+iH7wvg+nW7+GULvREA= +sigs.k8s.io/kustomize/api v0.16.0 h1:/zAR4FOQDCkgSDmVzV2uiFbuy9bhu3jEzthrHCuvm1g= +sigs.k8s.io/kustomize/api v0.16.0/go.mod h1:MnFZ7IP2YqVyVwMWoRxPtgl/5hpA+eCCrQR/866cm5c= +sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0= +sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4= sigs.k8s.io/release-utils v0.7.7 h1:JKDOvhCk6zW8ipEOkpTGDH/mW3TI+XqtPp16aaQ79FU= sigs.k8s.io/release-utils v0.7.7/go.mod h1:iU7DGVNi3umZJ8q6aHyUFzsDUIaYwNnNKGHo3YE5E3s= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= diff --git a/internal/cli/setup/envtest/setup.go b/internal/cli/setup/envtest/setup.go deleted file mode 100644 index bd124cc7e..000000000 --- a/internal/cli/setup/envtest/setup.go +++ /dev/null @@ -1,100 +0,0 @@ -package envtest - -import ( - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - - "github.com/kyma-project/cli/internal/envtest" - "github.com/kyma-project/cli/internal/files" -) - -const ( - envtestDefaultVersion = "1.24.x!" //1.24.x! means the latest available patch version for 1.24 branch - envtestVersionEnv = "ENVTEST_K8S_VERSION" - envtestSetupBin = "setup-envtest" -) - -// based on "kubernetes-sigs/controller-runtime/tools/setup-envtest/versions/parse.go", but more strict -var envtestVersionRegexp = regexp.MustCompile(`^(0|[1-9]\d{0,2})\.(0|[1-9]\d{0,2})\.(0|[1-9]\d{0,3})$`) - -func EnvTest() (*envtest.Runner, error) { - p, err := files.KymaHome() - if err != nil { - return nil, err - } - - //Install setup-envtest - if _, err := os.Stat(filepath.Join(p, envtestSetupBin)); os.IsNotExist(err) { - kymaGobinEnv := "GOBIN=" + p - envtestSetupCmd := exec.Command("go", "install", "sigs.k8s.io/controller-runtime/tools/setup-envtest@latest") - envtestSetupCmd.Env = os.Environ() - envtestSetupCmd.Env = append(envtestSetupCmd.Env, kymaGobinEnv) - - //go install is silent when executed successfully - out, err := envtestSetupCmd.CombinedOutput() - if err != nil { - return nil, fmt.Errorf("error installing setup-envtest: %w. Details: %s", err, string(out)) - } - - } - - //Install envtest binaries using setup-envtest - envtestSetupBinPath := filepath.Join(p, envtestSetupBin) - - version, err := resolveEnvtestVersion() - if err != nil { - return nil, err - } - - envtestInstallBinariesCmd := exec.Command(envtestSetupBinPath, "use", version, "--bin-dir", p) - out, err := envtestInstallBinariesCmd.CombinedOutput() - if err != nil { - return nil, fmt.Errorf("error installing envtest binaries: %w. Details: %s", err, string(out)) - } - - envtestBinariesPath, err := extractPath(string(out)) - if err != nil { - return nil, fmt.Errorf("error installing envtest binaries: %w", err) - } - - return envtest.NewRunner(envtestBinariesPath, nil, nil), nil -} - -// resolveEnvtestVersion validates the envtest version provided via the environment variable. It returns the default version if the variable is not found. -func resolveEnvtestVersion() (string, error) { - v, defined := os.LookupEnv(envtestVersionEnv) - if !defined { - return envtestDefaultVersion, nil - } - - trimmed := strings.TrimSpace(v) - if !envtestVersionRegexp.MatchString(trimmed) { - return "", errors.New("Invalid value of \"ENVTEST_K8S_VERSION\" variable, only proper semversions are allowed, e.g: 1.24.2") - } - - return trimmed, nil -} - -// extractPath extracts the envtest binaries path from the "setup-envtest" command output -func extractPath(envtestSetupMsg string) (string, error) { - return parseEnvtestSetupMsg(envtestSetupMsg, `[pP]ath:(.+)`, "envtest binaries path") - -} - -func parseEnvtestSetupMsg(envtestSetupMsg, rgxp, objName string) (string, error) { - r, err := regexp.Compile(rgxp) - if err != nil { - return "", err - } - matches := r.FindStringSubmatch(envtestSetupMsg) - if len(matches) != 2 { - return "", fmt.Errorf("Couldn't find %s in the \"setup-envtest\" command output", objName) - } - - return strings.TrimSpace(matches[1]), nil -} diff --git a/internal/envtest/envtest.go b/internal/envtest/envtest.go deleted file mode 100644 index 8c700ffee..000000000 --- a/internal/envtest/envtest.go +++ /dev/null @@ -1,53 +0,0 @@ -package envtest - -import ( - "fmt" - - "go.uber.org/zap" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/envtest" -) - -func NewRunner(binPath string, env *envtest.Environment, restClient *rest.Config) *Runner { - return &Runner{ - binPath, env, restClient, - } -} - -type Runner struct { - binPath string - env *envtest.Environment - restClient *rest.Config -} - -func (r *Runner) RestClient() *rest.Config { - return r.restClient -} - -func (r *Runner) Start(crdFilePath string, _ *zap.SugaredLogger) (err error) { - - r.env = &envtest.Environment{ - CRDInstallOptions: envtest.CRDInstallOptions{ - Paths: []string{crdFilePath}, - }, - BinaryAssetsDirectory: r.binPath, - ErrorIfCRDPathMissing: true, - } - - r.restClient, err = r.env.Start() - if err != nil { - return fmt.Errorf("could not start the `envtest` envionment: %w", err) - } - if r.restClient == nil { - return fmt.Errorf("could not get the RestConfig for the `envtest` envionment: %w", err) - } - - return nil -} - -func (r *Runner) Stop() error { - if err := r.env.Stop(); err != nil { - return fmt.Errorf("could not stop CR validation: %w", err) - } - return nil -} diff --git a/internal/k3d/k3d.go b/internal/k3d/k3d.go index 0d7ab9eec..63a1687fc 100644 --- a/internal/k3d/k3d.go +++ b/internal/k3d/k3d.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/blang/semver/v4" + "github.com/Masterminds/semver/v3" "github.com/pkg/errors" ) @@ -52,7 +52,8 @@ type client struct { } // NewClient creates a new instance of the Client interface. -func NewClient(cmdRunner CmdRunner, pathLooker PathLooker, clusterName string, verbose bool, timeout time.Duration) Client { +func NewClient(cmdRunner CmdRunner, pathLooker PathLooker, clusterName string, verbose bool, + timeout time.Duration) Client { return &client{ cmdRunner: cmdRunner, pathLooker: pathLooker, @@ -70,13 +71,16 @@ func (c *client) runCmd(args ...string) (string, error) { if err != nil { if c.verbose { - fmt.Printf("Failing command:\n %s %s\nwith output:\n %s\nand error:\n %s\n", binaryName, strings.Join(args, " "), out, err) + fmt.Printf("Failing command:\n %s %s\nwith output:\n %s\nand error:\n %s\n", binaryName, + strings.Join(args, " "), out, err) } - return out, errors.Wrapf(err, "Executing '%s %s' failed with output '%s'", binaryName, strings.Join(args, " "), out) + return out, errors.Wrapf(err, "Executing '%s %s' failed with output '%s'", binaryName, strings.Join(args, " "), + out) } if ctx.Err() == context.DeadlineExceeded { - return out, fmt.Errorf("Executing '%s %s' command with output '%s' timed out. Try running the command manually or increasing the timeout using the 'timeout' flag", binaryName, strings.Join(args, " "), out) + return out, fmt.Errorf("executing '%s %s' command with output '%s' timed out. Try running the command "+ + "manually or increasing the timeout using the 'timeout' flag", binaryName, strings.Join(args, " "), out) } if c.verbose { @@ -103,19 +107,19 @@ func (c *client) checkVersion() error { if len(binaryVersion) < 2 { return fmt.Errorf("Could not extract %s version from command output:\n%s", binaryName, binaryVersionOutput) } - binarySemVersion, err := semver.Parse(binaryVersion[1]) + binarySemVersion, err := semver.StrictNewVersion(binaryVersion[1]) if err != nil { return err } - minRequiredSemVersion, err := semver.Parse(minRequiredVersion) + minRequiredSemVersion, err := semver.StrictNewVersion(minRequiredVersion) if err != nil { return fmt.Errorf("failed to parse semantic version: %w", err) } - if binarySemVersion.Major > minRequiredSemVersion.Major { + if binarySemVersion.Major() > minRequiredSemVersion.Major() { incompatibleMajorVersionMsg := "You are using an unsupported k3d major version '%d'. The supported k3d major version for this command is '%d'." return fmt.Errorf(incompatibleMajorVersionMsg, binarySemVersion.Major, minRequiredSemVersion.Major) - } else if binarySemVersion.LT(minRequiredSemVersion) { + } else if binarySemVersion.LessThan(minRequiredSemVersion) { incompatibleVersionMsg := "You are using an unsupported k3d version '%s'. The supported k3d version for this command is >= '%s'." return fmt.Errorf(incompatibleVersionMsg, binaryVersion, minRequiredSemVersion) } @@ -152,12 +156,13 @@ func (c *client) getRegistryByName(registryName string) (*Registry, error) { // VerifyStatus verifies whether the k3d CLI tool is properly installed func (c *client) VerifyStatus() error { - //ensure k3d is in PATH + // ensure k3d is in PATH if _, err := c.pathLooker.Look(binaryName); err != nil { if c.verbose { fmt.Printf("Command '%s' not found in PATH", binaryName) } - return fmt.Errorf("Command '%s' not found. Please install %s (see https://github.com/rancher/k3d#get)", binaryName, binaryName) + return fmt.Errorf("Command '%s' not found. Please install %s (see https://github.com/rancher/k3d#get)", + binaryName, binaryName) } if err := c.checkVersion(); err != nil { @@ -225,7 +230,7 @@ func (c *client) CreateCluster(settings CreateClusterSettings) error { cmdArgs = append(cmdArgs, getCreateClusterArgs(settings)...) cmdArgs = append(cmdArgs, constructArgs("--port", settings.PortMapping)...) - //add further k3d args which are not offered by the Kyma CLI flags + // add further k3d args which are not offered by the Kyma CLI flags cmdArgs = append(cmdArgs, settings.Args...) _, err = c.runCmd(cmdArgs...) @@ -247,7 +252,7 @@ func (c *client) CreateRegistry(registryPort string, args []string) (string, err return fmt.Sprintf("%s:%s", registryNameMatch[1], registryPort), nil } - //fallback to k3d convention if the regexp fails + // fallback to k3d convention if the regexp fails return fmt.Sprintf("%s:%s", registryName, registryPort), nil } @@ -278,7 +283,7 @@ func getCreateClusterArgs(settings CreateClusterSettings) []string { } func getK3sImage(kubernetesVersion string) (string, error) { - _, err := semver.Parse(kubernetesVersion) + _, err := semver.StrictNewVersion(kubernetesVersion) if err != nil { return "", fmt.Errorf("Invalid Kubernetes version %v: %v", kubernetesVersion, err) } diff --git a/internal/net/net_test.go b/internal/net/net_test.go index 7bbf99056..c4ae121d0 100644 --- a/internal/net/net_test.go +++ b/internal/net/net_test.go @@ -23,7 +23,7 @@ func TestDoGet(t *testing.T) { require.Equal(t, 301, sc) // Non existing URL - _, err = DoGet("http://fake-url.com") + _, err = DoGet("http://totally.fake.url") require.Error(t, err) // BAD URL diff --git a/internal/version/compatibility.go b/internal/version/compatibility.go index e9f8400ca..959a805df 100644 --- a/internal/version/compatibility.go +++ b/internal/version/compatibility.go @@ -3,7 +3,7 @@ package version import ( "fmt" - "github.com/blang/semver/v4" + "github.com/Masterminds/semver/v3" ) var ( @@ -19,7 +19,8 @@ var ( } nextVersionLowerError = versionCompareError{ msg: func(e *versionCompareError) string { - return fmt.Sprintf("Next semanticVersion (%s) is lower than current semanticVersion (%s)", e.nextVersion, e.currentVersion) + return fmt.Sprintf("Next semanticVersion (%s) is lower than current semanticVersion (%s)", e.nextVersion, + e.currentVersion) }, } nextVersionTooGreatError = versionCompareError{ @@ -47,8 +48,8 @@ func (e *versionCompareError) with(currentVersion, nextVersion string) *versionC } func checkCompatibility(current string, next string) error { - curVersion, curVersionErr := semver.Parse(current) - nxtVersion, nxtVersionErr := semver.Parse(next) + curVersion, curVersionErr := semver.StrictNewVersion(current) + nxtVersion, nxtVersionErr := semver.StrictNewVersion(next) if curVersionErr != nil { return currentVersionNoReleaseError.with(current, next) @@ -56,10 +57,10 @@ func checkCompatibility(current string, next string) error { if nxtVersionErr != nil { return nextVersionNoReleaseError.with(current, next) } - if nxtVersion.LT(curVersion) { + if nxtVersion.LessThan(curVersion) { return nextVersionLowerError.with(current, next) } - if nxtVersion.Major > curVersion.Major || (uint64(nxtVersion.Minor)-uint64(curVersion.Minor) > 1) { // only upgrade to the next minor version is guaranteed to work + if nxtVersion.Major() > curVersion.Major() || (nxtVersion.Minor()-curVersion.Minor() > 1) { // only upgrade to the next minor version is guaranteed to work return nextVersionTooGreatError.with(current, next) } return nil diff --git a/pkg/module/crd_reader.go b/pkg/module/crd_reader.go new file mode 100644 index 000000000..f2b9cb0fc --- /dev/null +++ b/pkg/module/crd_reader.go @@ -0,0 +1,237 @@ +package module + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "gopkg.in/yaml.v3" + + "github.com/kyma-project/cli/pkg/module/kubebuilder" +) + +func GetCrdFromModuleDef(kubebuilderProject bool, modDef *Definition) ([]byte, error) { + if len(modDef.DefaultCR) == 0 { + return nil, nil + } + + crMap, err := parseYamlToMap(modDef.DefaultCR) + if err != nil { + return nil, fmt.Errorf("error parsing default CR: %w", err) + } + + group, kind, err := readGroupKind(crMap) + if err != nil { + return nil, err + } + + var crd []byte + if kubebuilderProject { + crdSearchDir := filepath.Join(modDef.Source, kubebuilder.OutputPath) + crd, err = findCRDFileFor(group, kind, crdSearchDir) + if err != nil { + return nil, fmt.Errorf("error finding CRD file in the %q directory: %w", crdSearchDir, err) + } + if crd == nil { + return nil, fmt.Errorf("can't find the CRD for (group: %q, kind %q)", group, kind) + } + } else { + // extract CRD matching group and kind from the multi-document YAML manifest + crd, err = getCRDFromFile(group, kind, modDef.SingleManifestPath) + if err != nil { + return nil, fmt.Errorf("error finding CRD file in the %q file: %w", modDef.SingleManifestPath, err) + } + if crd == nil { + return nil, fmt.Errorf("can't find the CRD for (group: %q, kind %q)", group, kind) + } + } + + return crd, nil +} + +func parseYamlToMap(crData []byte) (map[string]interface{}, error) { + modelMap := make(map[string]interface{}) + err := yaml.Unmarshal(crData, &modelMap) + if err != nil { + return nil, fmt.Errorf("error parsing default CR: %w", err) + } + + return modelMap, nil +} + +func readGroupKind(crMap map[string]interface{}) (group, kind string, retErr error) { + apiVersion, err := mustReadString(crMap, "apiVersion") + if err != nil { + retErr = fmt.Errorf("can't parse default CR data: %w", err) + return + } + + group = strings.Split(apiVersion, "/")[0] // e.g: apiVersion: example.org/v1 + kind, err = mustReadString(crMap, "kind") + if err != nil { + retErr = fmt.Errorf("can't parse default CR data: %w", err) + return + } + + return +} + +// mustReadString reads a value from the given map using the given key. The value must be a string. An error is returned if the key is not in the map, or the value is not a string. +func mustReadString(input map[string]interface{}, key string) (string, error) { + attrVal, ok := input[key] + if !ok { + return "", fmt.Errorf("attribute %q not found", key) + } + + asString, ok := attrVal.(string) + if !ok { + return "", fmt.Errorf("attribute %q is not a string", key) + } + + return asString, nil +} + +// findCRDFileFor returns path to the file with a CRD definition for the given group and kind, if exists. +// It looks in the dirPath directory and all of its subdirectories, recursively. +func findCRDFileFor(group, kind, dirPath string) ([]byte, error) { + // list all files in the dirPath and all it's subdirectories, recursively + files, err := listFiles(dirPath) + if err != nil { + return nil, fmt.Errorf("error listing files in %q directory: %w", dirPath, err) + } + + var crd []byte + for _, f := range files { + crd, err = getCRDFileFor(group, kind, f) + if err != nil { + // Error is expected. Either the file is not YAML, or it's not a CRD, or it's a CRD but not the one we're looking for. + continue + } + if crd != nil { + break + } + } + + return crd, nil +} + +// getCRDFileFor returns the crd if the given file is a CRD for given group and kind. +func getCRDFileFor(group, kind, filePath string) ([]byte, error) { + res, err := getCRDFromFile(group, kind, filePath) + if err != nil { + return nil, err + } + return res, nil +} + +// getCRDFromFile tries to find a CRD for given group and kind in the given multi-document YAML file. Returns a generic map representation of the CRD +func getCRDFromFile(group, kind, filePath string) ([]byte, error) { + { + f, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("error reading \"%q\": %w", filePath, err) + } + defer f.Close() + + yd := yaml.NewDecoder(f) + + isNotEOF := func(err error) bool { + return !errors.Is(err, io.EOF) + } + + // Iteration is necessary, because the file may contain several documents and the CRD we're looking for may not be the first one. + err = nil + for err == nil || isNotEOF(err) { + modelMap := make(map[string]interface{}) + err = yd.Decode(modelMap) + if err != nil { + // fail fast if it's not a proper YAML + return nil, err + } + + // Ensure it's a CRD + rootKindVal, err := mustReadString(modelMap, "kind") + if err != nil { + continue + } + if rootKindVal != "CustomResourceDefinition" { + continue + } + + // Find the Group/Kind of this CRD + specMap, err := mustReadMap(modelMap, "spec") + if err != nil { + continue + } + groupVal, err := mustReadString(specMap, "group") + if err != nil { + continue + } + namesMap, err := mustReadMap(specMap, "names") + if err != nil { + continue + } + kindVal, err := mustReadString(namesMap, "kind") + if err != nil { + continue + } + + // Check if this CRD is the one we're looking for + if groupVal == group && kindVal == kind { + res, err := yaml.Marshal(modelMap) + if err != nil { + return nil, err + } + return res, nil + } + } + + if errors.Is(err, io.EOF) { + // Failure: No document in the file matches our search criteria. + return nil, nil + } + + // We should never get here, because Decode() should return EOF once there's no more data to read. + return nil, err + } +} + +// listFiles returns an unordered slice of all the files within the given directory and all it's subdirectories, recursively. +func listFiles(dirPath string) ([]string, error) { + res := []string{} + + walkFunc := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode().IsRegular() { + res = append(res, path) + } + return nil + } + + err := filepath.Walk(dirPath, walkFunc) + if err != nil { + return nil, fmt.Errorf("error reading directory \"%q\": %w", dirPath, err) + } + + return res, nil +} + +// mustReadMap reads a value from the given map using the given key. The value must be a Map. An error is returned if the key is not in the input map, or the value is not a Map. +func mustReadMap(input map[string]interface{}, key string) (map[string]interface{}, error) { + attrVal, ok := input[key] + if !ok { + return nil, fmt.Errorf("attribute %q not found", key) + } + + asMap, ok := attrVal.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("attribute %q is not a Map", key) + } + + return asMap, nil +} diff --git a/pkg/module/def.go b/pkg/module/def.go index 6a0421962..804fe9efc 100644 --- a/pkg/module/def.go +++ b/pkg/module/def.go @@ -3,8 +3,12 @@ package module import ( "errors" "fmt" + "strings" + "github.com/kyma-project/lifecycle-manager/api/v1beta2" + amv "k8s.io/apimachinery/pkg/util/validation" + "github.com/kyma-project/cli/pkg/module/oci" ) @@ -30,7 +34,7 @@ type Definition struct { // validate checks that the configuration has all required data for a module to be valid. func (cfg *Definition) validate() error { if cfg.Name == "" { - return errors.New("The module name cannot be empty") + return errors.New("the module name cannot be empty") } ref, err := oci.ParseRef(cfg.Name) @@ -38,15 +42,15 @@ func (cfg *Definition) validate() error { return err } - if err := ValidateName(ref.ShortName()); err != nil { + if err := validateName(ref.ShortName()); err != nil { return err } if cfg.Version == "" { - return errors.New("The module version cannot be empty") + return errors.New("the module version cannot be empty") } if cfg.Source == "" { - return errors.New("The module source path cannot be empty") + return errors.New("the module source path cannot be empty") } return nil } @@ -61,3 +65,21 @@ func ParseNameMapping(val string) (NameMapping, error) { "invalid mapping mode: %s, only %s or %s are allowed", val, URLPathNameMapping, DigestNameMapping, ) } + +// ValidateName checks if the name is at least three characters long and if it conforms to the "RFC 1035 Label Names" specification (K8s compatibility requirement) +func validateName(name string) error { + if len(name) < 3 { + return errors.New("invalid module name: name must be at least three characters long") + } + + violations := amv.IsDNS1035Label(name) + if len(violations) == 1 { + return fmt.Errorf("invalid module name: %s", violations[0]) + } + if len(violations) > 1 { + vl := "\n - " + strings.Join(violations, "\n - ") + return fmt.Errorf("invalid module name: %s", vl) + } + + return nil +} diff --git a/pkg/module/scaffold/default_cr.go b/pkg/module/scaffold/default_cr.go new file mode 100644 index 000000000..5827d95cc --- /dev/null +++ b/pkg/module/scaffold/default_cr.go @@ -0,0 +1,30 @@ +package scaffold + +import ( + "fmt" + "os" + "path" +) + +func (g *Generator) DefaultCRFilePath() string { + return path.Join(g.Directory, g.DefaultCRFile) +} + +func (g *Generator) DefaultCRFileExists() (bool, error) { + return g.fileExists(g.DefaultCRFilePath()) +} + +func (g *Generator) GenerateDefaultCRFile() error { + + blankContents := `# This is the file that contains the defaultCR for your module, which is the Custom Resource that will be created upon module enablement. +# Make sure this file contains *ONLY* the Custom Resource (not the Custom Resource Definition, which should be a part of your module manifest) + +` + filePath := g.DefaultCRFilePath() + err := os.WriteFile(filePath, []byte(blankContents), 0600) + if err != nil { + return fmt.Errorf("error while saving %s: %w", filePath, err) + } + + return nil +} diff --git a/pkg/module/scaffold/manifest.go b/pkg/module/scaffold/manifest.go new file mode 100644 index 000000000..118716313 --- /dev/null +++ b/pkg/module/scaffold/manifest.go @@ -0,0 +1,30 @@ +package scaffold + +import ( + "fmt" + "os" + "path" +) + +func (g *Generator) ManifestFilePath() string { + return path.Join(g.Directory, g.ManifestFile) +} + +func (g *Generator) ManifestFileExists() (bool, error) { + return g.fileExists(g.ManifestFilePath()) +} + +func (g *Generator) GenerateManifest() error { + + blankContents := `# This file holds the Manifest of your module, encompassing all resources installed in the cluster once the module is activated. +# It should include the Custom Resource Definition for your module's default CustomResource, if it exists. + +` + filePath := g.ManifestFilePath() + err := os.WriteFile(filePath, []byte(blankContents), 0600) + if err != nil { + return fmt.Errorf("error while saving %s: %w", filePath, err) + } + + return nil +} diff --git a/pkg/module/scaffold/module_config.go b/pkg/module/scaffold/module_config.go new file mode 100644 index 000000000..06301b43a --- /dev/null +++ b/pkg/module/scaffold/module_config.go @@ -0,0 +1,32 @@ +package scaffold + +import ( + "path" + + "github.com/kyma-project/cli/cmd/kyma/alpha/create/module" +) + +func (g *Generator) ModuleConfigFilePath() string { + return path.Join(g.Directory, g.ModuleConfigFile) +} + +func (g *Generator) ModuleConfigFileExists() (bool, error) { + return g.fileExists(g.ModuleConfigFilePath()) +} + +func (g *Generator) GenerateModuleConfigFile() error { + cfg := module.Config{ + Name: g.ModuleName, + Version: g.ModuleVersion, + Channel: g.ModuleChannel, + ManifestPath: g.ManifestFile, + Security: g.SecurityConfigFile, + DefaultCRPath: g.DefaultCRFile, + } + + if err := cfg.Validate(); err != nil { + return err + } + + return g.generateYamlFileFromObject(cfg, g.ModuleConfigFilePath()) +} diff --git a/pkg/module/scaffold/scaffold.go b/pkg/module/scaffold/scaffold.go new file mode 100644 index 000000000..9d425e509 --- /dev/null +++ b/pkg/module/scaffold/scaffold.go @@ -0,0 +1,41 @@ +package scaffold + +import ( + "errors" + "fmt" + "os" +) + +type Generator struct { + ModuleName string + ModuleVersion string + ModuleChannel string + Directory string + ModuleConfigFile string + ManifestFile string + SecurityConfigFile string + DefaultCRFile string +} + +func (g *Generator) fileExists(path string) (bool, error) { + if _, err := os.Stat(path); err == nil { + return true, nil + + } else if errors.Is(err, os.ErrNotExist) { + return false, nil + + } else { + return false, err + } +} + +func (g *Generator) generateYamlFileFromObject(obj interface{}, filePath string) error { + yamlVal := generateYaml(obj) + + err := os.WriteFile(filePath, []byte(yamlVal), 0600) + if err != nil { + return fmt.Errorf("error writing file: %w", err) + } + + return nil +} diff --git a/pkg/module/scaffold/security_config.go b/pkg/module/scaffold/security_config.go new file mode 100644 index 000000000..a55670250 --- /dev/null +++ b/pkg/module/scaffold/security_config.go @@ -0,0 +1,28 @@ +package scaffold + +import ( + "path" + + "github.com/kyma-project/cli/pkg/module" +) + +func (g *Generator) SecurityConfigFilePath() string { + return path.Join(g.Directory, g.SecurityConfigFile) +} + +func (g *Generator) SecurityConfigFileExists() (bool, error) { + return g.fileExists(g.SecurityConfigFilePath()) +} + +func (g *Generator) GenerateSecurityConfigFile() error { + cfg := module.SecurityScanCfg{ + ModuleName: g.ModuleName, + Protecode: []string{"europe-docker.pkg.dev/kyma-project/prod/myimage:1.2.3", + "europe-docker.pkg.dev/kyma-project/prod/external/ghcr.io/mymodule/anotherimage:4.5.6"}, + WhiteSource: module.WhiteSourceSecCfg{ + Exclude: []string{"**/test/**", "**/*_test.go"}, + }, + } + err := g.generateYamlFileFromObject(cfg, g.SecurityConfigFilePath()) + return err +} diff --git a/pkg/module/scaffold/utils.go b/pkg/module/scaffold/utils.go new file mode 100644 index 000000000..a9f9b98e3 --- /dev/null +++ b/pkg/module/scaffold/utils.go @@ -0,0 +1,81 @@ +package scaffold + +import ( + "fmt" + "reflect" + "strings" +) + +func generateYaml(obj interface{}) string { + reflectValue := reflect.ValueOf(obj) + var yamlBuilder strings.Builder + generateYamlWithComments(&yamlBuilder, reflectValue, 0, "") + return yamlBuilder.String() +} + +// generateYamlWithComments uses a "comment" tag in the struct definition to generate YAML with comments on corresponding lines. +// Note: Map support is missing! +func generateYamlWithComments(yamlBuilder *strings.Builder, obj reflect.Value, indentLevel int, commentPrefix string) { + t := obj.Type() + + indentPrefix := strings.Repeat(" ", indentLevel) + originalCommentPrefix := commentPrefix + for i := 0; i < t.NumField(); i++ { + commentPrefix = originalCommentPrefix + field := t.Field(i) + value := obj.Field(i) + yamlTag := field.Tag.Get("yaml") + commentTag := field.Tag.Get("comment") + + // comment-out non-required empty attributes + if value.IsZero() && !strings.Contains(commentTag, "required") { + commentPrefix = "# " + } + + if value.Kind() == reflect.Struct { + if commentTag == "" { + yamlBuilder.WriteString(fmt.Sprintf("%s%s%s:\n", commentPrefix, indentPrefix, yamlTag)) + } else { + yamlBuilder.WriteString(fmt.Sprintf("%s%s%s: # %s\n", commentPrefix, indentPrefix, yamlTag, commentTag)) + } + generateYamlWithComments(yamlBuilder, value, indentLevel+1, commentPrefix) + continue + } + + if value.Kind() == reflect.Slice { + if commentTag == "" { + yamlBuilder.WriteString(fmt.Sprintf("%s%s%s:\n", commentPrefix, indentPrefix, yamlTag)) + } else { + yamlBuilder.WriteString(fmt.Sprintf("%s%s%s: # %s\n", commentPrefix, indentPrefix, yamlTag, commentTag)) + } + + if value.Len() == 0 { + yamlBuilder.WriteString(fmt.Sprintf("%s%s -\n", commentPrefix, indentPrefix)) + } + for j := 0; j < value.Len(); j++ { + valueStr := getValueStr(value.Index(j)) + yamlBuilder.WriteString(fmt.Sprintf("%s%s - %s\n", "", indentPrefix, valueStr)) + } + continue + } + + valueStr := getValueStr(value) + if commentTag == "" { + yamlBuilder.WriteString(fmt.Sprintf("%s%s%s: %s\n", commentPrefix, indentPrefix, + yamlTag, valueStr)) + } else { + yamlBuilder.WriteString(fmt.Sprintf("%s%s%s: %s # %s\n", commentPrefix, indentPrefix, + yamlTag, valueStr, commentTag)) + } + } +} + +func getValueStr(value reflect.Value) string { + valueStr := "" + if value.Kind() == reflect.String { + valueStr = fmt.Sprintf("\"%v\"", value.Interface()) + } else { + valueStr = fmt.Sprintf("%v", value.Interface()) + } + return valueStr +} diff --git a/pkg/module/scaffold/utils_test.go b/pkg/module/scaffold/utils_test.go new file mode 100644 index 000000000..379c4b8cb --- /dev/null +++ b/pkg/module/scaffold/utils_test.go @@ -0,0 +1,248 @@ +package scaffold + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMarshalFlatWithoutCommentYaml(t *testing.T) { + type Subject struct { + Age int `yaml:"age"` + Name string `yaml:"name"` + Title string `yaml:"title"` + } + + s1 := Subject{ + Age: 22, + Name: "John", + } + + expected := stripLeadingNewline(` +age: 22 +name: "John" +# title: "" +`) + actual := generateYaml(s1) + require.Equal(t, expected, actual) +} + +func TestMarshalFlatWithSomeComment(t *testing.T) { + type Subject struct { + Age int `yaml:"age" comment:"the age of the user"` + Name string `yaml:"name" comment:"the name of the user"` + Title string `yaml:"title"` + Active bool `yaml:"active"` + } + + s1 := Subject{ + Name: "John", + Title: "Dr", + } + + expected := stripLeadingNewline(` +# age: 0 # the age of the user +name: "John" # the name of the user +title: "Dr" +# active: false +`) + actual := generateYaml(s1) + require.Equal(t, expected, actual) +} + +func TestMarshalFlatWithSomeRequiredCommentYaml(t *testing.T) { + type Subject struct { + Age int `yaml:"age" comment:"required, the age of the user"` + Name string `yaml:"name" comment:"required, the name of the user"` + Title string `yaml:"title" comment:"the title of the user"` + Active bool `yaml:"active" comment:"the active status of the user"` + } + + s1 := Subject{ + Name: "John", + Title: "Dr", + } + + // note: "age" is rendered uncommented because of the word "required" in the comment tag + expected := stripLeadingNewline(` +age: 0 # required, the age of the user +name: "John" # required, the name of the user +title: "Dr" # the title of the user +# active: false # the active status of the user +`) + actual := generateYaml(s1) + require.Equal(t, expected, actual) +} + +func TestMarshalNestedStruct(t *testing.T) { + type VeryNested struct { + ID string `yaml:"id" comment:"the id of the user"` + ExpirationDate string `yaml:"expirationDate"` // attribute with no comment + Department string `yaml:"department" comment:"one of: business, development, research, AI"` + } + type Nested struct { + Details VeryNested `yaml:"details"` // struct with no comment + Name string `yaml:"name" comment:"the name of the user"` + } + type Subject struct { + User Nested `yaml:"user" comment:"required, user data"` + Active bool `yaml:"active" comment:"required, the active flag of the user"` + } + + s1 := Subject{ + User: Nested{ + Details: VeryNested{ + ID: "123", + }, + Name: "John", + }, + Active: true, + } + + expected := stripLeadingNewline(` +user: # required, user data + details: + id: "123" # the id of the user +# expirationDate: "" +# department: "" # one of: business, development, research, AI + name: "John" # the name of the user +active: true # required, the active flag of the user +`) + actual := generateYaml(s1) + require.Equal(t, expected, actual) +} + +func TestMarshalSlice(t *testing.T) { + type Subject struct { + Age int `yaml:"age" comment:"required, the age of the user"` + Name string `yaml:"name" comment:"required, the name of the user"` + Nicknames []string `yaml:"nicknames"` + Friends []string `yaml:"friends"` + FavouriteFoods []string `yaml:"favouriteFoods" comment:"favourite foods of the user"` + FavouriteColors []string `yaml:"favouriteColors" comment:"favourite colors of the user"` + } + s1 := Subject{ + Name: "John", + Friends: []string{"Bob", "Alice"}, + FavouriteColors: []string{"Red", "Green", "Blue"}, + } + + expected := stripLeadingNewline(` +age: 0 # required, the age of the user +name: "John" # required, the name of the user +# nicknames: +# - +friends: + - "Bob" + - "Alice" +# favouriteFoods: # favourite foods of the user +# - +favouriteColors: # favourite colors of the user + - "Red" + - "Green" + - "Blue" +`) + actual := generateYaml(s1) + require.Equal(t, expected, actual) +} + +func TestMarshalNestedSlice(t *testing.T) { + type Nested struct { + Nicknames []string `yaml:"nicknames"` + Friends []string `yaml:"friends"` + FavouriteFoods []string `yaml:"favouriteFoods" comment:"favourite foods of the user"` + FavouriteColors []string `yaml:"favouriteColors" comment:"favourite colors of the user"` + } + type Subject struct { + Age int `yaml:"age" comment:"required, the age of the user"` + Name string `yaml:"name" comment:"required, the name of the user"` + Details Nested `yaml:"details" comment:"the details of the user"` + } + s1 := Subject{ + Name: "John", + Details: Nested{ + Friends: []string{"Bob", "Alice"}, + FavouriteColors: []string{"Red", "Green", "Blue"}, + }, + } + + expected := stripLeadingNewline(` +age: 0 # required, the age of the user +name: "John" # required, the name of the user +details: # the details of the user +# nicknames: +# - + friends: + - "Bob" + - "Alice" +# favouriteFoods: # favourite foods of the user +# - + favouriteColors: # favourite colors of the user + - "Red" + - "Green" + - "Blue" +`) + actual := generateYaml(s1) + require.Equal(t, expected, actual) +} + +func TestMarshalDoublyNestedSlice(t *testing.T) { + type DoublyNested struct { + Nicknames []string `yaml:"nicknames"` + Friends []string `yaml:"friends"` + FavouriteFoods []string `yaml:"favouriteFoods" comment:"favourite foods of the user"` + FavouriteColors []string `yaml:"favouriteColors" comment:"favourite colors of the user"` + } + type Nested struct { + Public DoublyNested `yaml:"public"` + Private DoublyNested `yaml:"private" comment:"private details of the user"` + } + type Subject struct { + Age int `yaml:"age" comment:"required, the age of the user"` + Name string `yaml:"name" comment:"required, the name of the user"` + Details Nested `yaml:"details" comment:"the details of the user"` + } + s1 := Subject{ + Name: "John", + Details: Nested{ + Public: DoublyNested{ + Friends: []string{"Bob", "Alice"}, + FavouriteColors: []string{"Red", "Green", "Blue"}, + }, + }, + } + + expected := stripLeadingNewline(` +age: 0 # required, the age of the user +name: "John" # required, the name of the user +details: # the details of the user + public: +# nicknames: +# - + friends: + - "Bob" + - "Alice" +# favouriteFoods: # favourite foods of the user +# - + favouriteColors: # favourite colors of the user + - "Red" + - "Green" + - "Blue" +# private: # private details of the user +# nicknames: +# - +# friends: +# - +# favouriteFoods: # favourite foods of the user +# - +# favouriteColors: # favourite colors of the user +# - +`) + actual := generateYaml(s1) + require.Equal(t, expected, actual) +} + +func stripLeadingNewline(val string) string { + return strings.TrimLeft(val, "\n") +} diff --git a/pkg/module/security_scan.go b/pkg/module/security_scan.go index 85d741204..80ee5625f 100644 --- a/pkg/module/security_scan.go +++ b/pkg/module/security_scan.go @@ -125,16 +125,16 @@ func getImageName(imageURL string) (string, string, error) { } type SecurityScanCfg struct { - ModuleName string `json:"module-name"` - Protecode []string `json:"protecode"` - WhiteSource WhiteSourceSecCfg `json:"whitesource"` - DevBranch string `json:"dev-branch"` - RcTag string `json:"rc-tag"` + ModuleName string `json:"module-name" yaml:"module-name" comment:"string, name of your module"` + Protecode []string `json:"protecode" yaml:"protecode" comment:"list, includes the images which must be scanned by the Protecode scanner (aka. Black Duck Binary Analysis)"` + WhiteSource WhiteSourceSecCfg `json:"whitesource" yaml:"whitesource" comment:"whitesource (aka. Mend) security scanner specific configuration"` + DevBranch string `json:"dev-branch" yaml:"dev-branch" comment:"string, name of the development branch"` + RcTag string `json:"rc-tag" yaml:"rc-tag" comment:"string, release candidate tag"` } type WhiteSourceSecCfg struct { - Language string `json:"language"` - SubProjects string `json:"subprojects"` - Exclude []string `json:"exclude"` + Language string `json:"language" yaml:"language" comment:"string, indicating the programming language the scanner has to analyze"` + SubProjects string `json:"subprojects" yaml:"subprojects" comment:"string, specifying any subprojects"` + Exclude []string `json:"exclude" yaml:"exclude" comment:"list, directories within the repository which should not be scanned"` } func parseSecurityScanConfig(securityConfigPath string) (*SecurityScanCfg, error) { diff --git a/pkg/module/tmp_files.go b/pkg/module/tmp_files.go new file mode 100644 index 000000000..7a09d82cf --- /dev/null +++ b/pkg/module/tmp_files.go @@ -0,0 +1,79 @@ +package module + +import ( + "fmt" + "io" + "net/http" + "net/url" + "os" +) + +type TmpFilesManager struct { + tmpFiles []*os.File +} + +func NewTmpFilesManager() *TmpFilesManager { + return &TmpFilesManager{tmpFiles: []*os.File{}} +} + +func (manager *TmpFilesManager) DownloadRemoteFileToTmpFile(url, dir, filenamePattern string) (string, error) { + bytes, err := getBytesFromURL(url) + if err != nil { + return "", fmt.Errorf("failed to download file from %s: %w", url, err) + } + + tmpFile, err := os.CreateTemp(dir, filenamePattern) + if err != nil { + return "", fmt.Errorf("failed to create temp file with pattern %s: %w", filenamePattern, err) + } + defer tmpFile.Close() + manager.tmpFiles = append(manager.tmpFiles, tmpFile) + if _, err := tmpFile.Write(bytes); err != nil { + return "", fmt.Errorf("failed to write to temp file %s: %w", tmpFile.Name(), err) + } + + return tmpFile.Name(), nil +} + +func (manager *TmpFilesManager) DeleteTmpFiles() []error { + var errors []error + for _, file := range manager.tmpFiles { + err := os.Remove(file.Name()) + if err != nil { + errors = append(errors, err) + } + } + manager.tmpFiles = []*os.File{} + return errors +} + +func getBytesFromURL(urlString string) ([]byte, error) { + ok, url := ParseURL(urlString) + if !ok { + return nil, fmt.Errorf("parseing url failed for %s", urlString) + } + resp, err := http.Get(url.String()) + if err != nil { + return nil, fmt.Errorf("http GET request failed for %s: %w", url, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("bad status for GET request to %s: %s", url, resp.Status) + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body from %s: %w", url, err) + } + + return data, nil +} + +func ParseURL(s string) (bool, *url.URL) { + u, err := url.Parse(s) + if err == nil && u.Scheme != "" && u.Host != "" { + return true, u + } + return false, nil +} diff --git a/pkg/module/tmp_files_test.go b/pkg/module/tmp_files_test.go new file mode 100644 index 000000000..90639da24 --- /dev/null +++ b/pkg/module/tmp_files_test.go @@ -0,0 +1,81 @@ +package module + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "os" + "testing" +) + +func testHandler(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte("")) + if err != nil { + return + } +} + +func TestDownloadRemoteFileToTempFile(t *testing.T) { + t.Parallel() + + mockServer := httptest.NewServer(http.HandlerFunc(testHandler)) + defer mockServer.Close() + tmpFiles := NewTmpFilesManager() + defer tmpFiles.DeleteTmpFiles() + + type args struct { + url string + filename string + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + name: "file download successful", + args: args{ + url: mockServer.URL, + filename: "manifest-*.yaml", + }, + want: []byte(""), + wantErr: false, + }, + { + name: "invalid url results in error", + args: args{ + url: "invalid-url", + filename: "manifest-*.yaml", + }, + want: []byte{}, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got, err := tmpFiles.DownloadRemoteFileToTmpFile(tt.args.url, os.TempDir(), tt.args.filename) + + if err != nil && !tt.wantErr { + t.Errorf("unexpected error occurred: %s", err.Error()) + return + } + if err != nil && tt.wantErr { + return + } + if err == nil && tt.wantErr { + t.Errorf("expected error did not occur: %s", err.Error()) + return + } + + fileContent, err := os.ReadFile(got) + if err != nil { + t.Errorf("created file could not be read: %s", err.Error()) + return + } + assert.Equalf(t, tt.want, fileContent, "DownloadRemoteFileToTmpFile(%v, %v, %v)", + tt.args.url, "", tt.args.filename) + }) + } +} diff --git a/pkg/module/validation.go b/pkg/module/validation.go deleted file mode 100644 index 9a1b5446d..000000000 --- a/pkg/module/validation.go +++ /dev/null @@ -1,422 +0,0 @@ -package module - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "time" - - setup "github.com/kyma-project/cli/internal/cli/setup/envtest" - "github.com/kyma-project/cli/internal/kube" - "github.com/kyma-project/cli/pkg/module/kubebuilder" - "go.uber.org/zap" - "gopkg.in/yaml.v3" - amv "k8s.io/apimachinery/pkg/util/validation" -) - -var ErrEmptyCR = errors.New("provided CR is empty") - -type DefaultCRValidator struct { - crdSearchDir string - crData []byte - crd []byte -} - -func NewDefaultCRValidator(cr []byte, modulePath string) *DefaultCRValidator { - crdSearchDir := filepath.Join(modulePath, kubebuilder.OutputPath) - return &DefaultCRValidator{ - crdSearchDir: crdSearchDir, - crData: cr, - } -} - -func (v *DefaultCRValidator) Run(ctx context.Context, log *zap.SugaredLogger) error { - // skip validation if no CR detected - if len(v.crData) == 0 { - return ErrEmptyCR - } - - crMap, err := parseYamlToMap(v.crData) - if err != nil { - return fmt.Errorf("error parsing default CR: %w", err) - } - - group, kind, err := readGroupKind(crMap) - if err != nil { - return err - } - - // find the file containing the CRD for given group and kind - crd, crdFilePath, err := findCRDFileFor(group, kind, v.crdSearchDir) - if err != nil { - return fmt.Errorf("error finding CRD file in the %q directory: %w", v.crdSearchDir, err) - } - if crd == nil { - return fmt.Errorf("can't find the CRD for (group: %q, kind %q)", group, kind) - } - v.crd = crd - - return runTestEnv(ctx, log, crdFilePath, crMap) -} - -func runTestEnv(ctx context.Context, log *zap.SugaredLogger, crdFilePath string, crMap map[string]interface{}) error { - // setup test env - runner, err := setup.EnvTest() - if err != nil { - return err - } - - err = ensureDefaultNamespace(crMap) - if err != nil { - return fmt.Errorf("error parsing default CR: %w", err) - } - - properCR, err := renderYamlFromMap(crMap) - if err != nil { - return err - } - - if err := runner.Start(crdFilePath, log); err != nil { - return err - } - defer func() { - if err := runner.Stop(); err != nil { - log.Error(fmt.Errorf("error stopping envTest: %w", err)) - } - }() - - kc, err := kube.NewFromRestConfigWithTimeout(runner.RestClient(), 30*time.Second) - if err != nil { - return err - } - - objs, err := kc.ParseManifest(properCR) - if err != nil { - return err - } - - if err := kc.Apply(ctx, false, objs...); err != nil { - return fmt.Errorf("error applying the default CR: %w", err) - } - return nil -} - -// ensureDefaultNamespace ensures that the metadata.namespace attribute exists, and its value is "default". This is because of how we use the envtest to validate the CR. -func ensureDefaultNamespace(modelMap map[string]interface{}) error { - - //Traverse the Map to look for "metadata.namespace" - metadataMap, err := mustReadMap(modelMap, "metadata") - if err != nil { - return err - } - - namespaceVal, ok := metadataMap["namespace"] - if !ok { - //Add the "metadata.namespace" attribute if missing - metadataMap["namespace"] = "default" - } else { - //Set the "metadata.namespace" if different than "default" - existing, ok := namespaceVal.(string) - if !ok { - return errors.New("attribute \"metadata.namespace\" is not a string") - } - if existing != "default" { - metadataMap["namespace"] = "default" - } - } - - return nil -} - -func readGroupKind(crMap map[string]interface{}) (group, kind string, retErr error) { - apiVersion, err := mustReadString(crMap, "apiVersion") - if err != nil { - retErr = fmt.Errorf("can't parse default CR data: %w", err) - return - } - - group = strings.Split(apiVersion, "/")[0] //e.g: apiVersion: example.org/v1 - kind, err = mustReadString(crMap, "kind") - if err != nil { - retErr = fmt.Errorf("can't parse default CR data: %w", err) - return - } - - return -} - -// mustReadMap reads a value from the given map using the given key. The value must be a Map. An error is returned if the key is not in the input map, or the value is not a Map. -func mustReadMap(input map[string]interface{}, key string) (map[string]interface{}, error) { - attrVal, ok := input[key] - if !ok { - return nil, fmt.Errorf("attribute %q not found", key) - } - - asMap, ok := attrVal.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("attribute %q is not a Map", key) - } - - return asMap, nil -} - -// mustReadString reads a value from the given map using the given key. The value must be a string. An error is returned if the key is not in the map, or the value is not a string. -func mustReadString(input map[string]interface{}, key string) (string, error) { - attrVal, ok := input[key] - if !ok { - return "", fmt.Errorf("attribute %q not found", key) - } - - asString, ok := attrVal.(string) - if !ok { - return "", fmt.Errorf("attribute %q is not a string", key) - } - - return asString, nil -} - -func parseYamlToMap(crData []byte) (map[string]interface{}, error) { - modelMap := make(map[string]interface{}) - err := yaml.Unmarshal(crData, &modelMap) - if err != nil { - return nil, fmt.Errorf("error parsing default CR: %w", err) - } - - return modelMap, nil -} - -func renderYamlFromMap(modelMap map[string]interface{}) ([]byte, error) { - - output, err := yaml.Marshal(modelMap) - if err != nil { - return nil, fmt.Errorf("error processing default CR data: %w", err) - } - - return output, nil - -} - -// findCRDFileFor returns path to the file with a CRD definition for the given group and kind, if exists. -// It looks in the dirPath directory and all of its subdirectories, recursively. -func findCRDFileFor(group, kind, dirPath string) ([]byte, string, error) { - - //list all files in the dirPath and all it's subdirectories, recursively - files, err := listFiles(dirPath) - if err != nil { - return nil, "", fmt.Errorf("error listing files in %q directory: %w", dirPath, err) - } - - var found string - var crd []byte - for _, f := range files { - crd, err = getCRDFileFor(group, kind, f) - if err != nil { - //Error is expected. Either the file is not YAML, or it's not a CRD, or it's a CRD but not the one we're looking for. - continue - } - if crd != nil { - found = f - break - } - } - - if found != "" { - return crd, found, nil - } - - return nil, "", nil -} - -// getCRDFileFor returns the crd if the given file is a CRD for given group and kind. -func getCRDFileFor(group, kind, filePath string) ([]byte, error) { - res, err := getCRDFromFile(group, kind, filePath) - if err != nil { - return nil, err - } - return res, nil -} - -// getCRDFromFile tries to find a CRD for given group and kind in the given multi-document YAML file. Returns a generic map representation of the CRD -func getCRDFromFile(group, kind, filePath string) ([]byte, error) { - { - f, err := os.Open(filePath) - if err != nil { - return nil, fmt.Errorf("error reading \"%q\": %w", filePath, err) - } - defer f.Close() - - yd := yaml.NewDecoder(f) - - isNotEOF := func(err error) bool { - return !errors.Is(err, io.EOF) - } - - //Iteration is necessary, because the file may contain several documents and the CRD we're looking for may not be the first one. - err = nil - for err == nil || isNotEOF(err) { - modelMap := make(map[string]interface{}) - err = yd.Decode(modelMap) - if err != nil { - //fail fast if it's not a proper YAML - return nil, err - } - - //Ensure it's a CRD - rootKindVal, err := mustReadString(modelMap, "kind") - if err != nil { - continue - } - if rootKindVal != "CustomResourceDefinition" { - continue - } - - //Find the Group/Kind of this CRD - specMap, err := mustReadMap(modelMap, "spec") - if err != nil { - continue - } - groupVal, err := mustReadString(specMap, "group") - if err != nil { - continue - } - namesMap, err := mustReadMap(specMap, "names") - if err != nil { - continue - } - kindVal, err := mustReadString(namesMap, "kind") - if err != nil { - continue - } - - //Check if this CRD is the one we're looking for - if groupVal == group && kindVal == kind { - res, err := yaml.Marshal(modelMap) - if err != nil { - return nil, err - } - return res, nil - } - } - - if errors.Is(err, io.EOF) { - //Failure: No document in the file matches our search criteria. - return nil, nil - } - - //We should never get here, because Decode() should return EOF once there's no more data to read. - return nil, err - } -} - -// listFiles returns an unordered slice of all the files within the given directory and all it's subdirectories, recursively. -func listFiles(dirPath string) ([]string, error) { - - res := []string{} - - walkFunc := func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.Mode().IsRegular() { - res = append(res, path) - } - return nil - } - - err := filepath.Walk(dirPath, walkFunc) - if err != nil { - return nil, fmt.Errorf("error reading directory \"%q\": %w", dirPath, err) - } - - return res, nil -} - -// ValidateName checks if the name is at least three characters long and if it conforms to the "RFC 1035 Label Names" specification (K8s compatibility requirement) -func ValidateName(name string) error { - if len(name) < 3 { - return errors.New("invalid module name: name must be at least three characters long") - } - - violations := amv.IsDNS1035Label(name) - if len(violations) == 1 { - return fmt.Errorf("invalid module name: %s", violations[0]) - } - if len(violations) > 1 { - vl := "\n - " + strings.Join(violations, "\n - ") - return fmt.Errorf("invalid module name: %s", vl) - } - - return nil -} - -type SingleManifestFileCRValidator struct { - manifestPath string - crData []byte - Crd []byte -} - -func NewSingleManifestFileCRValidator(cr []byte, manifestPath string) *SingleManifestFileCRValidator { - return &SingleManifestFileCRValidator{ - manifestPath: manifestPath, - crData: cr, - } -} - -func (v *SingleManifestFileCRValidator) Run(ctx context.Context, log *zap.SugaredLogger) error { - // skip validation if no CR detected - if len(v.crData) == 0 { - return ErrEmptyCR - } - - crMap, err := parseYamlToMap(v.crData) - if err != nil { - return fmt.Errorf("error parsing default CR: %w", err) - } - - group, kind, err := readGroupKind(crMap) - if err != nil { - return err - } - - // extract CRD matching group and kind from the multi-document YAML manifest - crdBytes, err := getCRDFromFile(group, kind, v.manifestPath) - if err != nil { - return fmt.Errorf("error finding CRD file in the %q file: %w", v.manifestPath, err) - } - if crdBytes == nil { - return fmt.Errorf("can't find the CRD for (group: %q, kind %q)", group, kind) - } - v.Crd = crdBytes - - // store extracted CRD in a temp file - tempDir, err := os.MkdirTemp("", "temporary-crd") - if err != nil { - return fmt.Errorf("error creating temporary directory: %w", err) - } - defer func() { - err := os.RemoveAll(tempDir) - if err != nil { - log.Warn("Error removing temporary directory", err) - } - }() - tempCRDFile := filepath.Join(tempDir, "crd.yaml") - err = os.WriteFile(tempCRDFile, crdBytes, 0600) - if err != nil { - return fmt.Errorf("error writing temporary CRD file %q file: %w", tempCRDFile, err) - } - - // run testEnv using the temporary file with extracted CRD - return runTestEnv(ctx, log, tempCRDFile, crMap) -} - -func (v *SingleManifestFileCRValidator) GetCrd() []byte { - return v.Crd -} - -func (v *DefaultCRValidator) GetCrd() []byte { - return v.crd -} diff --git a/pkg/module/validation_test.go b/pkg/module/validation_test.go deleted file mode 100644 index 27b5ca10e..000000000 --- a/pkg/module/validation_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package module - -import ( - "strings" - "testing" -) - -func TestEnsureDefaultNamespace(t *testing.T) { - tests := []struct { - name string - input string - shouldErr bool - outputVal string - errorShouldContain string - }{ - - { - name: "happy path", - input: correctModel, - shouldErr: false, - outputVal: correctModel, - }, - - { - name: "missing metadata", - input: noMetadataYaml, - shouldErr: true, - errorShouldContain: noMetadataError, - }, - - { - name: "metadata is not map", - input: invalidMetadataTypeYaml, - shouldErr: true, - errorShouldContain: invalidMetadataTypeError, - }, - { - name: "metadata.namespace is not string", - input: invalidNamespaceTypeYaml, - shouldErr: true, - errorShouldContain: invalidNamespaceTypeError, - }, - { - name: "should add metadata.namespace if missing", - input: missingNamespaceYaml, - shouldErr: false, - outputVal: missingNamespaceExpectedOutput, - }, - { - name: "should set metadata.namespace to default if different", - input: differentNamespaceYaml, - shouldErr: false, - outputVal: differentNamespaceExpectedOutput, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - crData, err := parseYamlToMap([]byte(tc.input)) - if err != nil { - t.Fatalf("Can't parse test data: %v", err) - return - } - - err = ensureDefaultNamespace(crData) - - if err == nil && tc.shouldErr { - t.Errorf("ensureDefaultNamespace() should return an error but it didn't") - return - } - if err != nil && !tc.shouldErr { - t.Errorf("ensureDefaultNamespace() should not return an error but it did: %v", err) - return - } - if err != nil && !strings.Contains(err.Error(), tc.errorShouldContain) { - t.Errorf("ensureDefaultNamespace() should return an error containing: \"%v\"\nbut it returned: %v", tc.errorShouldContain, err.Error()) - return - } - - output, yamlErr := renderYamlFromMap(crData) - if yamlErr != nil { - t.Fatalf("Can't render test data: %v", yamlErr) - } - if err == nil && string(output) != tc.outputVal { - t.Errorf("ensureDefaultNamespace() should return:\n%v\nbut it returned:\n%v", tc.outputVal, string(output)) - return - } - }) - - } -} - -const ( - correctModel = `apiVersion: operator.kyma-project.io/v1alpha1 -kind: Sample -metadata: - name: sample-sample - namespace: default -spec: - releaseName: redis-release -` - - noMetadataYaml = `abc: def -foo: bar -` - noMetadataError = `attribute "metadata" not found` - - invalidMetadataTypeYaml = ` -abc: def -metadata: 2 -` - invalidMetadataTypeError = `attribute "metadata" is not a Map` - - invalidNamespaceTypeYaml = ` -abc: def -metadata: - namespace: 2 -` - invalidNamespaceTypeError = `attribute "metadata.namespace" is not a string` - missingNamespaceYaml = `abc: def -metadata: - name: foobar -` - missingNamespaceExpectedOutput = `abc: def -metadata: - name: foobar - namespace: default -` - - differentNamespaceYaml = `abc: def -metadata: - name: foobar - namespace: foobar -` - differentNamespaceExpectedOutput = `abc: def -metadata: - name: foobar - namespace: default -` -) diff --git a/prow/pre-cli-e2e.sh b/prow/pre-cli-e2e.sh deleted file mode 100755 index eff99a325..000000000 --- a/prow/pre-cli-e2e.sh +++ /dev/null @@ -1,7 +0,0 @@ -set -e -make resolve -make build-linux -cp ./bin/kyma-linux /usr/local/bin/kyma -kyma provision k3d --ci -kyma deploy --ci -kyma undeploy --ci --timeout=10m0s diff --git a/tests/e2e/Makefile b/tests/e2e/Makefile index 3eed76896..705477fbf 100644 --- a/tests/e2e/Makefile +++ b/tests/e2e/Makefile @@ -41,4 +41,8 @@ test-same-version-module-creation: .PHONY: test-module-enabling-disabling test-module-enabling-disabling: - go test -ginkgo.v -ginkgo.focus "Kyma CLI deploy, enable and disable commands usage" \ No newline at end of file + go test -ginkgo.v -ginkgo.focus "Kyma CLI deploy, enable and disable commands usage" + +.PHONY: test-create-scaffold +test-create-scaffold: + go test ./create_scaffold -ginkgo.v -ginkgo.focus "Create Scaffold Command" diff --git a/tests/e2e/create_scaffold/scaffold_suite_test.go b/tests/e2e/create_scaffold/scaffold_suite_test.go new file mode 100644 index 000000000..e6ebe3f69 --- /dev/null +++ b/tests/e2e/create_scaffold/scaffold_suite_test.go @@ -0,0 +1,13 @@ +package scaffold_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestCreateScaffold(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "CreateScaffold Suite") +} diff --git a/tests/e2e/create_scaffold/scaffold_test.go b/tests/e2e/create_scaffold/scaffold_test.go new file mode 100644 index 000000000..ca0d489b6 --- /dev/null +++ b/tests/e2e/create_scaffold/scaffold_test.go @@ -0,0 +1,390 @@ +package scaffold_test + +import ( + "fmt" + "io/fs" + "os" + "os/exec" + "path" + + "github.com/kyma-project/cli/cmd/kyma/alpha/create/module" + "gopkg.in/yaml.v3" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +const ( + markerFileData = "test-marker" +) + +var _ = Describe("Create Scaffold Command", Ordered, func() { + var initialDir string + var workDir string + var workDirCleanup func() + + setup := func() { + var err error + initialDir, err = os.Getwd() + Expect(err).To(BeNil()) + workDir, workDirCleanup = resolveWorkingDirectory() + err = os.Chdir(workDir) + Expect(err).To(BeNil()) + + } + teardown := func() { + err := os.Chdir(initialDir) + Expect(err).To(BeNil()) + workDirCleanup() + workDir = "" + initialDir = "" + + } + + Context("Given an empty directory", func() { + BeforeAll(func() { setup() }) + AfterAll(func() { teardown() }) + + var cmd createScaffoldCmd + It("When `kyma alpha create scaffold` command is invoked without any args", func() { + cmd = createScaffoldCmd{} + }) + + It("Then the command should succeed", func() { + Expect(cmd.execute()).To(Succeed()) + + By("And two files are generated") + Expect(filesIn(workDir)).Should(HaveLen(2)) + + By("And the manifest file is generated") + Expect(filesIn(workDir)).Should(ContainElement("manifest.yaml")) + + By("And the module config file is generated") + Expect(filesIn(workDir)).Should(ContainElement("scaffold-module-config.yaml")) + + By("And the module config contains expected entries") + actualModConf := moduleConfigFromFile(workDir, "scaffold-module-config.yaml") + expectedModConf := (&moduleConfigBuilder{}).defaults().get() + Expect(actualModConf).To(BeEquivalentTo(expectedModConf)) + }) + }) + + Context("Given a directory with an existing module configuration file", func() { + BeforeAll(func() { + setup() + Expect(createMarkerFile("scaffold-module-config.yaml")).To(Succeed()) + }) + AfterAll(func() { teardown() }) + + var cmd createScaffoldCmd + It("When `kyma alpha create scaffold` command is invoked without any args", func() { + cmd = createScaffoldCmd{} + }) + It("Then the command should fail", func() { + err := cmd.execute() + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(ContainSubstring("module config file already exists")) + + By("And no files should be generated") + Expect(filesIn(workDir)).Should(HaveLen(1)) + Expect(filesIn(workDir)).Should(ContainElement("scaffold-module-config.yaml")) + Expect(getMarkerFileData("scaffold-module-config.yaml")).Should(Equal(markerFileData)) + }) + }) + + Context("Given a directory with an existing module configuration file", func() { + BeforeAll(func() { + setup() + Expect(createMarkerFile("scaffold-module-config.yaml")).To(Succeed()) + }) + AfterAll(func() { teardown() }) + + var cmd createScaffoldCmd + It("When `kyma alpha create scaffold` command is invoked with --overwrite flag", func() { + cmd = createScaffoldCmd{ + overwrite: true, + } + }) + + It("Then the command should succeed", func() { + Expect(cmd.execute()).To(Succeed()) + + By("And two files are generated") + Expect(filesIn(workDir)).Should(HaveLen(2)) + + By("And the manifest file is generated") + Expect(filesIn(workDir)).Should(ContainElement("manifest.yaml")) + + By("And the module config file is generated") + Expect(filesIn(workDir)).Should(ContainElement("scaffold-module-config.yaml")) + + By("And the module config contains expected entries") + actualModConf := moduleConfigFromFile(workDir, "scaffold-module-config.yaml") + expectedModConf := (&moduleConfigBuilder{}).defaults().get() + Expect(actualModConf).To(BeEquivalentTo(expectedModConf)) + }) + }) + + Context("Given an empty directory", func() { + BeforeAll(func() { setup() }) + AfterAll(func() { teardown() }) + + var cmd createScaffoldCmd + It("When `kyma alpha create scaffold` command args override defaults", func() { + cmd = createScaffoldCmd{ + moduleName: "github.com/custom/module", + moduleVersion: "3.2.1", + moduleChannel: "custom", + moduleConfigFileFlag: "custom-module-config.yaml", + genManifestFlag: "custom-manifest.yaml", + genDefaultCRFlag: "custom-default-cr.yaml", + genSecurityScannersConfigFlag: "custom-security-scanners-config.yaml", + } + }) + It("Then the command should succeed", func() { + Expect(cmd.execute()).To(Succeed()) + + By("And four files are generated") + Expect(filesIn(workDir)).Should(HaveLen(4)) + + By("And the manifest file is generated") + Expect(filesIn(workDir)).Should(ContainElement("custom-manifest.yaml")) + + By("And the defaultCR file is generated") + Expect(filesIn(workDir)).Should(ContainElement("custom-default-cr.yaml")) + + By("And the security-scanners-config file is generated") + Expect(filesIn(workDir)).Should(ContainElement("custom-security-scanners-config.yaml")) + + By("And the module config file is generated") + Expect(filesIn(workDir)).Should(ContainElement("custom-module-config.yaml")) + + By("And the module config contains expected entries") + actualModConf := moduleConfigFromFile(workDir, "custom-module-config.yaml") + expectedModConf := cmd.toConfigBuilder().get() + Expect(actualModConf).To(BeEquivalentTo(expectedModConf)) + }) + }) + + Context("Given a directory with existing files", func() { + BeforeAll(func() { + setup() + Expect(createMarkerFile("custom-manifest.yaml")).To(Succeed()) + Expect(createMarkerFile("custom-default-cr.yaml")).To(Succeed()) + Expect(createMarkerFile("custom-security-scanners-config.yaml")).To(Succeed()) + + }) + AfterAll(func() { teardown() }) + + var cmd createScaffoldCmd + It("When `kyma alpha create scaffold` command is invoked with arguments that match existing files names", func() { + cmd = createScaffoldCmd{ + genManifestFlag: "custom-manifest.yaml", + genDefaultCRFlag: "custom-default-cr.yaml", + genSecurityScannersConfigFlag: "custom-security-scanners-config.yaml", + } + }) + It("Then the command should succeed", func() { + Expect(cmd.execute()).To(Succeed()) + + By("And there should be four files in the directory") + Expect(filesIn(workDir)).Should(HaveLen(4)) + + By("And the manifest file is reused (not generated)") + Expect(getMarkerFileData("custom-manifest.yaml")).Should(Equal(markerFileData)) + + By("And the defaultCR file is reused (not generated)") + Expect(getMarkerFileData("custom-default-cr.yaml")).Should(Equal(markerFileData)) + + By("And the security-scanners-config file is reused (not generated)") + Expect(getMarkerFileData("custom-security-scanners-config.yaml")).Should(Equal(markerFileData)) + + By("And the module config file is generated") + Expect(filesIn(workDir)).Should(ContainElement("scaffold-module-config.yaml")) + + By("And module config contains expected entries") + actualModConf := moduleConfigFromFile(workDir, "scaffold-module-config.yaml") + expectedModConf := cmd.toConfigBuilder().get() + Expect(actualModConf).To(BeEquivalentTo(expectedModConf)) + }) + }) +}) + +func getMarkerFileData(name string) string { + data, err := os.ReadFile(name) + Expect(err).To(BeNil()) + return string(data) +} + +func createMarkerFile(name string) error { + err := os.WriteFile(name, []byte(markerFileData), 0600) + return err +} + +func moduleConfigFromFile(dir, fileName string) *module.Config { + filePath := path.Join(dir, fileName) + data, err := os.ReadFile(filePath) + Expect(err).To(BeNil()) + res := module.Config{} + err = yaml.Unmarshal(data, &res) + Expect(err).To(BeNil()) + return &res +} + +func filesIn(dir string) []string { + fi, err := os.Stat(dir) + Expect(err).To(BeNil()) + Expect(fi.IsDir()).To(BeTrueBecause("The provided path should be a directory: %s", dir)) + + dirFs := os.DirFS(dir) + entries, err := fs.ReadDir(dirFs, ".") + Expect(err).To(BeNil()) + + res := []string{} + for _, ent := range entries { + if ent.Type().IsRegular() { + res = append(res, ent.Name()) + } + } + + return res +} + +func resolveWorkingDirectory() (path string, cleanup func()) { + path = os.Getenv("SCAFFOLD_DIR") + if len(path) > 0 { + cleanup = func() {} + } else { + var err error + path, err = os.MkdirTemp("", "create_scaffold_test") + if err != nil { + Fail(err.Error()) + } + cleanup = func() { + os.RemoveAll(path) + } + } + return +} + +type createScaffoldCmd struct { + moduleName string + moduleVersion string + moduleChannel string + moduleConfigFileFlag string + genDefaultCRFlag string + genSecurityScannersConfigFlag string + genManifestFlag string + overwrite bool +} + +func (cmd *createScaffoldCmd) execute() error { + var command *exec.Cmd + + args := []string{"alpha", "create", "scaffold"} + + if cmd.moduleName != "" { + args = append(args, fmt.Sprintf("--module-name=%s", cmd.moduleName)) + } + + if cmd.moduleVersion != "" { + args = append(args, fmt.Sprintf("--module-version=%s", cmd.moduleVersion)) + } + + if cmd.moduleChannel != "" { + args = append(args, fmt.Sprintf("--module-channel=%s", cmd.moduleChannel)) + } + + if cmd.moduleConfigFileFlag != "" { + args = append(args, fmt.Sprintf("--module-config=%s", cmd.moduleConfigFileFlag)) + } + + if cmd.genDefaultCRFlag != "" { + args = append(args, fmt.Sprintf("--gen-default-cr=%s", cmd.genDefaultCRFlag)) + } + + if cmd.genSecurityScannersConfigFlag != "" { + args = append(args, fmt.Sprintf("--gen-security-config=%s", cmd.genSecurityScannersConfigFlag)) + } + + if cmd.genManifestFlag != "" { + args = append(args, fmt.Sprintf("--gen-manifest=%s", cmd.genManifestFlag)) + } + + if cmd.overwrite { + args = append(args, "--overwrite=true") + } + + command = exec.Command("kyma", args...) + cmdOut, err := command.CombinedOutput() + + if err != nil { + return fmt.Errorf("create scaffold command failed with output: %s and error: %w", cmdOut, err) + } + return nil +} + +func (cmd *createScaffoldCmd) toConfigBuilder() *moduleConfigBuilder { + res := &moduleConfigBuilder{} + res.defaults() + if cmd.moduleName != "" { + res.withName(cmd.moduleName) + } + if cmd.moduleVersion != "" { + res.withVersion(cmd.moduleVersion) + } + if cmd.moduleChannel != "" { + res.withChannel(cmd.moduleChannel) + } + if cmd.genDefaultCRFlag != "" { + res.withDefaultCRPath(cmd.genDefaultCRFlag) + } + if cmd.genSecurityScannersConfigFlag != "" { + res.withSecurityScannersPath(cmd.genSecurityScannersConfigFlag) + } + if cmd.genManifestFlag != "" { + res.withManifestPath(cmd.genManifestFlag) + } + return res +} + +// moduleConfigBuilder is used to simplify module.Config creation for testing purposes +type moduleConfigBuilder struct { + module.Config +} + +func (mcb *moduleConfigBuilder) get() *module.Config { + res := mcb.Config + return &res +} +func (mcb *moduleConfigBuilder) withName(val string) *moduleConfigBuilder { + mcb.Name = val + return mcb +} +func (mcb *moduleConfigBuilder) withVersion(val string) *moduleConfigBuilder { + mcb.Version = val + return mcb +} +func (mcb *moduleConfigBuilder) withChannel(val string) *moduleConfigBuilder { + mcb.Channel = val + return mcb +} +func (mcb *moduleConfigBuilder) withManifestPath(val string) *moduleConfigBuilder { + mcb.ManifestPath = val + return mcb +} +func (mcb *moduleConfigBuilder) withDefaultCRPath(val string) *moduleConfigBuilder { + mcb.DefaultCRPath = val + return mcb +} +func (mcb *moduleConfigBuilder) withSecurityScannersPath(val string) *moduleConfigBuilder { + mcb.Security = val + return mcb +} +func (mcb *moduleConfigBuilder) defaults() *moduleConfigBuilder { + return mcb. + withName("kyma-project.io/module/mymodule"). + withVersion("0.0.1"). + withChannel("regular"). + withManifestPath("manifest.yaml") +}