From d3e06af8a74498e53eb3b07bc4b446dd361254b0 Mon Sep 17 00:00:00 2001 From: Fabian Kramm Date: Wed, 6 Apr 2022 10:58:44 +0200 Subject: [PATCH 1/2] refactor: rename variables and forbid command overwrite --- cmd/overwrite_command.go | 70 --------------- cmd/root.go | 20 +---- cmd/run.go | 59 ++++++++++++- cmd/run_pipeline.go | 88 ++++--------------- .../helm_concurrent_new/devspace.yaml | 4 +- .../imports/testdata/local/devspace.yaml | 2 +- e2e/tests/imports/testdata/local/import3.yaml | 2 +- examples/pipelines/deployment.yaml | 17 ---- examples/pipelines/devspace.yaml | 14 --- .../devspace.yaml | 6 +- .../loader/variable/predefined_variable.go | 41 ++++----- .../config/loader/variable/resolver.go | 2 +- pkg/devspace/config/versions/latest/schema.go | 32 ++++++- .../config/versions/v1beta11/upgrade.go | 11 ++- pkg/devspace/context/lazy/context.go | 1 - pkg/devspace/context/values/values.go | 28 +++--- .../engine/basichandler/commands/get_flag.go | 49 +++++++++++ .../basichandler/commands/is_command.go | 29 ------ .../pipeline/engine/basichandler/handler.go | 6 +- pkg/devspace/pipeline/job.go | 8 +- pkg/devspace/pipeline/pipeline.go | 12 +-- pkg/devspace/pipeline/types/pipeline.go | 2 +- 22 files changed, 222 insertions(+), 281 deletions(-) delete mode 100644 cmd/overwrite_command.go delete mode 100644 examples/pipelines/deployment.yaml delete mode 100644 examples/pipelines/devspace.yaml delete mode 100644 pkg/devspace/context/lazy/context.go create mode 100644 pkg/devspace/pipeline/engine/basichandler/commands/get_flag.go delete mode 100644 pkg/devspace/pipeline/engine/basichandler/commands/is_command.go diff --git a/cmd/overwrite_command.go b/cmd/overwrite_command.go deleted file mode 100644 index 19334c74d1..0000000000 --- a/cmd/overwrite_command.go +++ /dev/null @@ -1,70 +0,0 @@ -package cmd - -import ( - "context" - "github.com/loft-sh/devspace/cmd/flags" - "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" - devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context" - "github.com/loft-sh/devspace/pkg/devspace/plugin" - "github.com/loft-sh/devspace/pkg/util/factory" - "github.com/spf13/cobra" - "io" - "os" -) - -// OverwriteCmd holds the cmd flags of an overwrite command -type OverwriteCmd struct { - *flags.GlobalFlags - - Command *latest.CommandConfig - Variables map[string]interface{} - - Stdout io.Writer - Stderr io.Writer -} - -// NewOverwriteCmd creates a new overwrite command -func NewOverwriteCmd(f factory.Factory, globalFlags *flags.GlobalFlags, command *latest.CommandConfig, variables map[string]interface{}) *cobra.Command { - cmd := &OverwriteCmd{ - GlobalFlags: globalFlags, - Command: command, - Variables: variables, - Stdout: os.Stdout, - Stderr: os.Stderr, - } - - description := command.Description - longDescription := command.Description - if description == "" { - description = "Runs command: " + command.Name - longDescription = description - } - if len(description) > 64 { - if len(description) > 64 { - description = description[:61] + "..." - } - } - - runCmd := &cobra.Command{ - Use: command.Name, - Short: description, - Long: longDescription, - Args: cobra.ArbitraryArgs, - RunE: func(cobraCmd *cobra.Command, _ []string) error { - args, err := ParseArgs(cobraCmd, cmd.GlobalFlags, f.GetLog()) - if err != nil { - return err - } - - plugin.SetPluginCommand(cobraCmd, args) - return cmd.Run(f, args) - }, - } - runCmd.DisableFlagParsing = true - return runCmd -} - -func (cmd *OverwriteCmd) Run(f factory.Factory, args []string) error { - devCtx := devspacecontext.NewContext(context.Background(), cmd.Variables, f.GetLog()) - return executeCommandWithAfter(devCtx.Context(), cmd.Command, args, cmd.Variables, devCtx.WorkingDir(), cmd.Stdout, cmd.Stderr, os.Stdin, devCtx.Log()) -} diff --git a/cmd/root.go b/cmd/root.go index cf26bf8085..e8ee2bedc1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -260,10 +260,10 @@ Additional run commands: rootCmd.AddCommand(NewCompletionCmd()) // check overwrite commands - rootCmd.AddCommand(replaceCommand("dev", rawConfig, f, globalFlags, NewDevCmd)) - rootCmd.AddCommand(replaceCommand("deploy", rawConfig, f, globalFlags, NewDeployCmd)) - rootCmd.AddCommand(replaceCommand("build", rawConfig, f, globalFlags, NewBuildCmd)) - rootCmd.AddCommand(replaceCommand("purge", rawConfig, f, globalFlags, NewPurgeCmd)) + rootCmd.AddCommand(NewDevCmd(f, globalFlags)) + rootCmd.AddCommand(NewDeployCmd(f, globalFlags)) + rootCmd.AddCommand(NewBuildCmd(f, globalFlags)) + rootCmd.AddCommand(NewPurgeCmd(f, globalFlags)) // Add plugin commands plugin.AddPluginCommands(rootCmd, plugins, "") @@ -271,18 +271,6 @@ Additional run commands: return rootCmd } -func replaceCommand(command string, rawConfig *RawConfig, f factory.Factory, globalFlags *flags.GlobalFlags, fallback func(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra.Command) *cobra.Command { - if rawConfig != nil && rawConfig.CommandsConfig != nil && rawConfig.Resolver != nil && rawConfig.CommandsConfig.Commands != nil { - // get command - overwriteCommand, ok := rawConfig.CommandsConfig.Commands[command] - if ok && !overwriteCommand.DisableReplace { - return NewOverwriteCmd(f, globalFlags, overwriteCommand, rawConfig.Resolver.ResolvedVariables()) - } - } - - return fallback(f, globalFlags) -} - func disableKlog() { flagSet := &flag.FlagSet{} klog.InitFlags(flagSet) diff --git a/cmd/run.go b/cmd/run.go index 5fc67c2046..d731729190 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -77,7 +77,7 @@ devspace --dependency my-dependency run any-command --any-command-flag if rawConfig != nil && rawConfig.CommandsConfig != nil { for _, cmd := range rawConfig.CommandsConfig.Commands { - runCmd.AddCommand(NewOverwriteCmd(f, globalFlags, cmd, rawConfig.Resolver.ResolvedVariables())) + runCmd.AddCommand(NewSpecificRunCommand(f, globalFlags, cmd, rawConfig.Resolver.ResolvedVariables())) } } runCmd.Flags().StringVar(&cmd.Dependency, "dependency", "", "Run a command from a specific dependency") @@ -308,3 +308,60 @@ func ExecuteCommand(ctx context.Context, cmd *latest.CommandConfig, variables ma shellArgs = append(shellArgs, args...) return command.CommandWithEnv(ctx, dir, stdout, stderr, stdin, extraEnv, shellCommand, shellArgs...) } + +// RunCommandCmd holds the cmd flags of an run command +type RunCommandCmd struct { + *flags.GlobalFlags + + Command *latest.CommandConfig + Variables map[string]interface{} + + Stdout io.Writer + Stderr io.Writer +} + +// NewSpecificRunCommand creates a new run command +func NewSpecificRunCommand(f factory.Factory, globalFlags *flags.GlobalFlags, command *latest.CommandConfig, variables map[string]interface{}) *cobra.Command { + cmd := &RunCommandCmd{ + GlobalFlags: globalFlags, + Command: command, + Variables: variables, + Stdout: os.Stdout, + Stderr: os.Stderr, + } + + description := command.Description + longDescription := command.Description + if description == "" { + description = "Runs command: " + command.Name + longDescription = description + } + if len(description) > 64 { + if len(description) > 64 { + description = description[:61] + "..." + } + } + + runCmd := &cobra.Command{ + Use: command.Name, + Short: description, + Long: longDescription, + Args: cobra.ArbitraryArgs, + RunE: func(cobraCmd *cobra.Command, _ []string) error { + args, err := ParseArgs(cobraCmd, cmd.GlobalFlags, f.GetLog()) + if err != nil { + return err + } + + plugin.SetPluginCommand(cobraCmd, args) + return cmd.Run(f, args) + }, + } + runCmd.DisableFlagParsing = true + return runCmd +} + +func (cmd *RunCommandCmd) Run(f factory.Factory, args []string) error { + devCtx := devspacecontext.NewContext(context.Background(), cmd.Variables, f.GetLog()) + return executeCommandWithAfter(devCtx.Context(), cmd.Command, args, cmd.Variables, devCtx.WorkingDir(), cmd.Stdout, cmd.Stderr, os.Stdin, devCtx.Log()) +} diff --git a/cmd/run_pipeline.go b/cmd/run_pipeline.go index d488bded80..fb2333455e 100644 --- a/cmd/run_pipeline.go +++ b/cmd/run_pipeline.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/loft-sh/devspace/cmd/flags" "github.com/loft-sh/devspace/pkg/devspace/build" - config2 "github.com/loft-sh/devspace/pkg/devspace/config" "github.com/loft-sh/devspace/pkg/devspace/config/loader" "github.com/loft-sh/devspace/pkg/devspace/config/localcache" "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" @@ -27,8 +26,6 @@ import ( "github.com/loft-sh/devspace/pkg/util/interrupt" "github.com/loft-sh/devspace/pkg/util/log" "github.com/loft-sh/devspace/pkg/util/message" - "github.com/loft-sh/devspace/pkg/util/ptr" - "github.com/loft-sh/devspace/pkg/util/survey" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -62,8 +59,6 @@ type RunPipelineCmd struct { ForceDeploy bool SkipDeploy bool - Terminal bool - ShowUI bool // used for testing to allow interruption @@ -91,7 +86,6 @@ func (cmd *RunPipelineCmd) AddFlags(command *cobra.Command) { command.Flags().BoolVar(&cmd.SkipPush, "skip-push", cmd.SkipPush, "Skips image pushing, useful for minikube deployment") command.Flags().BoolVar(&cmd.SkipPushLocalKubernetes, "skip-push-local-kube", cmd.SkipPushLocalKubernetes, "Skips image pushing, if a local kubernetes environment is detected") - command.Flags().BoolVar(&cmd.Terminal, "terminal", cmd.Terminal, "Open a terminal instead of showing logs") command.Flags().BoolVar(&cmd.ShowUI, "show-ui", cmd.ShowUI, "Shows the ui server") } @@ -110,14 +104,8 @@ func NewRunPipelineCmd(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra ####################################################### Execute a pipeline #######################################################`, - Args: cobra.MaximumNArgs(1), + Args: cobra.ArbitraryArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { - if len(args) == 0 && cmd.Pipeline == "" { - return fmt.Errorf("please specify a pipeline through --pipeline or argument") - } else if len(args) == 1 && cmd.Pipeline == "" { - cmd.Pipeline = args[0] - } - return cmd.Run(cobraCmd, args, f, "run-pipeline", "runPipelineCommand") }, } @@ -132,6 +120,20 @@ func (cmd *RunPipelineCmd) RunDefault(f factory.Factory) error { // Run executes the command logic func (cmd *RunPipelineCmd) Run(cobraCmd *cobra.Command, args []string, f factory.Factory, commandName, hookName string) error { + dashArgs := []string{} + if cobraCmd.ArgsLenAtDash() > -1 { + dashArgs = args[cobraCmd.ArgsLenAtDash():] + args = args[:cobraCmd.ArgsLenAtDash()] + } + + if len(args) > 1 { + return fmt.Errorf("please specify only 1 pipeline to execute. E.g. devspace run-pipeline my-pipe -- other args") + } else if len(args) == 0 && cmd.Pipeline == "" { + return fmt.Errorf("please specify a pipeline through --pipeline or argument") + } else if len(args) == 1 && cmd.Pipeline == "" { + cmd.Pipeline = args[0] + } + if cmd.log == nil { cmd.log = f.GetLog() } @@ -157,7 +159,7 @@ func (cmd *RunPipelineCmd) Run(cobraCmd *cobra.Command, args []string, f factory } // set command in context - cmd.Ctx = values.WithCommand(cmd.Ctx, commandName) + cmd.Ctx = values.WithFlags(cmd.Ctx, cobraCmd.Flags()) options := cmd.BuildOptions(cmd.ToConfigOptions()) ctx, err := initialize(cmd.Ctx, f, false, options, cmd.log) if err != nil { @@ -165,7 +167,7 @@ func (cmd *RunPipelineCmd) Run(cobraCmd *cobra.Command, args []string, f factory } return runWithHooks(ctx, hookName, func() error { - return runPipeline(ctx, options) + return runPipeline(ctx, dashArgs, options) }) } @@ -176,7 +178,6 @@ type CommandOptions struct { ConfigOptions *loader.ConfigOptions Pipeline string - Terminal bool ShowUI bool UIPort int } @@ -245,12 +246,6 @@ func initialize(ctx context.Context, f factory.Factory, allowFailingKubeClient b // add root name to context ctx = values.WithRootName(ctx, configInterface.Config().Name) - // adjust config - err = adjustConfig(configInterface, options, logger) - if err != nil { - return nil, err - } - // create devspace context devCtx := devspacecontext.NewContext(ctx, configInterface.Variables(), logger). WithConfig(configInterface). @@ -295,50 +290,6 @@ func updateLastKubeContext(ctx devspacecontext.Context) error { return nil } -func adjustConfig(conf config2.Config, options *CommandOptions, log log.Logger) error { - // check if terminal is enabled - c := conf.Config() - if options.Terminal { - if len(c.Dev) == 0 { - return errors.New("No dev config available in DevSpace config") - } - - devNames := make([]string, 0, len(c.Dev)) - for k := range c.Dev { - devNames = append(devNames, k) - } - - // if only one image exists, use it, otherwise show image picker - devName := "" - if len(devNames) == 1 { - devName = devNames[0] - } else { - var err error - devName, err = log.Question(&survey.QuestionOptions{ - Question: "Where do you want to open a terminal to?", - Options: devNames, - }) - if err != nil { - return err - } - } - - // adjust dev config - for k := range c.Dev { - if k == devName { - if c.Dev[devName].Terminal == nil { - c.Dev[devName].Terminal = &latest.Terminal{} - } - c.Dev[devName].Terminal.Enabled = ptr.Bool(true) - } else { - c.Dev[devName].Terminal = nil - } - } - } - - return nil -} - func runWithHooks(ctx devspacecontext.Context, command string, fn func() error) (err error) { err = hook.ExecuteHooks(ctx, nil, command+":before:execute") if err != nil { @@ -405,13 +356,12 @@ func (cmd *RunPipelineCmd) BuildOptions(configOptions *loader.ConfigOptions) *Co }, }, ConfigOptions: configOptions, - Terminal: cmd.Terminal, Pipeline: cmd.Pipeline, ShowUI: cmd.ShowUI, } } -func runPipeline(ctx devspacecontext.Context, options *CommandOptions) error { +func runPipeline(ctx devspacecontext.Context, args []string, options *CommandOptions) error { var configPipeline *latest.Pipeline if ctx.Config().Config().Pipelines != nil && ctx.Config().Config().Pipelines[options.Pipeline] != nil { configPipeline = ctx.Config().Config().Pipelines[options.Pipeline] @@ -459,7 +409,7 @@ func runPipeline(ctx devspacecontext.Context, options *CommandOptions) error { defer stderrWriter.Close() // start pipeline - err = pipe.Run(ctx.WithLogger(log.NewStreamLoggerWithFormat(stdoutWriter, stderrWriter, ctx.Log().GetLevel(), log.TimeFormat))) + err = pipe.Run(ctx.WithLogger(log.NewStreamLoggerWithFormat(stdoutWriter, stderrWriter, ctx.Log().GetLevel(), log.TimeFormat)), args) if err != nil { return err } diff --git a/e2e/tests/deploy/testdata/helm_concurrent_new/devspace.yaml b/e2e/tests/deploy/testdata/helm_concurrent_new/devspace.yaml index 369adc0890..f822f383aa 100644 --- a/e2e/tests/deploy/testdata/helm_concurrent_new/devspace.yaml +++ b/e2e/tests/deploy/testdata/helm_concurrent_new/devspace.yaml @@ -10,9 +10,9 @@ deployments: pipelines: deploy: run: |- - echo '{"helm": {"values": {"containers": [{"image":"alpine"}]}}}' > ${devspace.tempFolder}/deployment.yaml + echo '{"helm": {"values": {"containers": [{"image":"alpine"}]}}}' > ${DEVSPACE_TMPDIR}/deployment.yaml - create_deployments test1 test2 test3 test4 --from-file test1:${devspace.tempFolder}/deployment.yaml \ + create_deployments test1 test2 test3 test4 --from-file test1:${DEVSPACE_TMPDIR}/deployment.yaml \ --from test2:base \ --set test3:helm.values.containers[0].image=alpine \ --set-string test4:helm.values.containers[0].image=alpine \ diff --git a/e2e/tests/imports/testdata/local/devspace.yaml b/e2e/tests/imports/testdata/local/devspace.yaml index c6e7a2081a..50bc4e37dc 100644 --- a/e2e/tests/imports/testdata/local/devspace.yaml +++ b/e2e/tests/imports/testdata/local/devspace.yaml @@ -20,7 +20,7 @@ pipelines: deploy: run: |- echo ${devspace.name} > name.txt - echo ${devspace.tempFolder} > temp.txt + echo ${DEVSPACE_TMPDIR} > temp.txt run_dependency_pipelines --all > dependency.txt diff --git a/e2e/tests/imports/testdata/local/import3.yaml b/e2e/tests/imports/testdata/local/import3.yaml index ed9d5c0982..0b29cd121a 100644 --- a/e2e/tests/imports/testdata/local/import3.yaml +++ b/e2e/tests/imports/testdata/local/import3.yaml @@ -12,4 +12,4 @@ pipelines: run: |- echo ${IMPORT3} echo ${devspace.name} > dependency-name.txt - echo ${devspace.tempFolder} > dependency-temp.txt \ No newline at end of file + echo ${DEVSPACE_TMPDIR} > dependency-temp.txt \ No newline at end of file diff --git a/examples/pipelines/deployment.yaml b/examples/pipelines/deployment.yaml deleted file mode 100644 index e3488ef348..0000000000 --- a/examples/pipelines/deployment.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: test -spec: - replicas: 1 - selector: - matchLabels: - app: test - template: - metadata: - labels: - app: test - spec: - containers: - - name: alpine - image: alpine \ No newline at end of file diff --git a/examples/pipelines/devspace.yaml b/examples/pipelines/devspace.yaml deleted file mode 100644 index 698d8afc5a..0000000000 --- a/examples/pipelines/devspace.yaml +++ /dev/null @@ -1,14 +0,0 @@ -version: v2beta1 -name: pipelines - -dev: - test: - imageSelector: alpine - terminal: - enabled: true - -pipelines: - dev: |- - kubectl apply -f deployment.yaml - - start_dev test diff --git a/examples/redeploy-instead-of-hot-reload/devspace.yaml b/examples/redeploy-instead-of-hot-reload/devspace.yaml index 0786e396ce..a17689659f 100755 --- a/examples/redeploy-instead-of-hot-reload/devspace.yaml +++ b/examples/redeploy-instead-of-hot-reload/devspace.yaml @@ -4,10 +4,10 @@ name: redeploy-instead-of-hot-reload vars: IMAGE: yourusername/devspace +# Run the project in watch mode via 'devspace run watch' commands: - # Overwrite default devspace dev - dev: |- - run_watch -p devspace.yaml -p *.go -- devspace run-pipeline dev $@ + watch: |- + run_watch -p devspace.yaml -p *.go -- devspace dev "$@" images: default: diff --git a/pkg/devspace/config/loader/variable/predefined_variable.go b/pkg/devspace/config/loader/variable/predefined_variable.go index d14cfcd17e..18725e30f7 100644 --- a/pkg/devspace/config/loader/variable/predefined_variable.go +++ b/pkg/devspace/config/loader/variable/predefined_variable.go @@ -34,40 +34,40 @@ type PredefinedVariableFunction func(ctx context.Context, options *PredefinedVar // predefinedVars holds all predefined variables that can be used in the config var predefinedVars = map[string]PredefinedVariableFunction{ - "devspace.name": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_NAME": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { name, ok := values.NameFrom(ctx) if !ok { return "", nil } return name, nil }, - "devspace.tempFolder": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_TMPDIR": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { tempFolder, ok := values.TempFolderFrom(ctx) if !ok { return os.TempDir(), nil } return tempFolder, nil }, - "devspace.version": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_VERSION": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { return upgrade.GetVersion(), nil }, - "devspace.random": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_RANDOM": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { return randutil.GenerateRandomString(6), nil }, - "devspace.profile": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_PROFILE": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { return options.Profile, nil }, - "devspace.userHome": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_USER_HOME": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { homeDir, err := homedir.Dir() if err != nil { return nil, err } return homeDir, nil }, - "devspace.timestamp": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_TIMESTAMP": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { return strconv.FormatInt(time.Now().Unix(), 10), nil }, - "devspace.git.branch": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_GIT_BRANCH": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { configPath := options.ConfigPath branch, err := git.GetBranch(filepath.Dir(configPath)) if err != nil { @@ -76,7 +76,7 @@ var predefinedVars = map[string]PredefinedVariableFunction{ return branch, nil }, - "devspace.git.commit": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_GIT_COMMIT": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { configPath := options.ConfigPath hash, err := git.GetHash(ctx, filepath.Dir(configPath)) if err != nil { @@ -85,14 +85,14 @@ var predefinedVars = map[string]PredefinedVariableFunction{ return hash[:8], nil }, - "devspace.context": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_CONTEXT": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { if options.KubeClient == nil { return "", nil } return options.KubeClient.CurrentContext(), nil }, - "devspace.namespace": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { + "DEVSPACE_NAMESPACE": func(ctx context.Context, options *PredefinedVariableOptions) (interface{}, error) { if options.KubeClient == nil { return "", nil } @@ -103,15 +103,16 @@ var predefinedVars = map[string]PredefinedVariableFunction{ func init() { // migrate old names - predefinedVars["DEVSPACE_VERSION"] = predefinedVars["devspace.version"] - predefinedVars["DEVSPACE_RANDOM"] = predefinedVars["devspace.random"] - predefinedVars["DEVSPACE_PROFILE"] = predefinedVars["devspace.profile"] - predefinedVars["DEVSPACE_USER_HOME"] = predefinedVars["devspace.userHome"] - predefinedVars["DEVSPACE_TIMESTAMP"] = predefinedVars["devspace.timestamp"] - predefinedVars["DEVSPACE_GIT_BRANCH"] = predefinedVars["devspace.git.branch"] - predefinedVars["DEVSPACE_GIT_COMMIT"] = predefinedVars["devspace.git.commit"] - predefinedVars["DEVSPACE_CONTEXT"] = predefinedVars["devspace.context"] - predefinedVars["DEVSPACE_NAMESPACE"] = predefinedVars["devspace.namespace"] + predefinedVars["devspace.name"] = predefinedVars["DEVSPACE_NAME"] + predefinedVars["devspace.version"] = predefinedVars["DEVSPACE_VERSION"] + predefinedVars["devspace.random"] = predefinedVars["DEVSPACE_RANDOM"] + predefinedVars["devspace.profile"] = predefinedVars["DEVSPACE_PROFILE"] + predefinedVars["devspace.userHome"] = predefinedVars["DEVSPACE_USER_HOME"] + predefinedVars["devspace.timestamp"] = predefinedVars["DEVSPACE_TIMESTAMP"] + predefinedVars["devspace.git.branch"] = predefinedVars["DEVSPACE_GIT_BRANCH"] + predefinedVars["devspace.git.commit"] = predefinedVars["DEVSPACE_GIT_COMMIT"] + predefinedVars["devspace.context"] = predefinedVars["DEVSPACE_CONTEXT"] + predefinedVars["devspace.namespace"] = predefinedVars["DEVSPACE_NAMESPACE"] } func IsPredefinedVariable(name string) bool { diff --git a/pkg/devspace/config/loader/variable/resolver.go b/pkg/devspace/config/loader/variable/resolver.go index 98ba11f58c..7329048be5 100644 --- a/pkg/devspace/config/loader/variable/resolver.go +++ b/pkg/devspace/config/loader/variable/resolver.go @@ -18,7 +18,7 @@ import ( "github.com/pkg/errors" ) -var AlwaysResolvePredefinedVars = []string{"devspace.name", "devspace.tempFolder", "devspace.version", "devspace.random", "devspace.profile", "devspace.userHome", "devspace.timestamp", "devspace.context", "devspace.namespace"} +var AlwaysResolvePredefinedVars = []string{"DEVSPACE_NAME", "DEVSPACE_TMPDIR", "DEVSPACE_VERSION", "DEVSPACE_RANDOM", "DEVSPACE_PROFILE", "DEVSPACE_USER_HOME", "DEVSPACE_TIMESTAMP", "DEVSPACE_CONTEXT", "DEVSPACE_NAMESPACE"} // NewResolver creates a new resolver that caches resolved variables in memory and in the provided cache func NewResolver(localCache localcache.Cache, predefinedVariableOptions *PredefinedVariableOptions, flags []string, log log.Logger) (Resolver, error) { diff --git a/pkg/devspace/config/versions/latest/schema.go b/pkg/devspace/config/versions/latest/schema.go index 25f872cf75..66546d30ed 100644 --- a/pkg/devspace/config/versions/latest/schema.go +++ b/pkg/devspace/config/versions/latest/schema.go @@ -136,6 +136,10 @@ type Pipeline struct { // Name of the pipeline, will be filled automatically Name string `yaml:"name,omitempty" json:"name,omitempty"` + // Flags are extra flags that can be used for running the pipeline via + // devspace run-pipeline. + Flags []PipelineFlag `yaml:"flags,omitempty" json:"flags,omitempty"` + // ContinueOnError will not fail the whole job and pipeline if // a call within the step fails. ContinueOnError bool `yaml:"continueOnError,omitempty" json:"continueOnError,omitempty"` @@ -144,6 +148,30 @@ type Pipeline struct { Run string `yaml:"run,omitempty" json:"run,omitempty"` } +// PipelineFlag defines an extra pipeline flag +type PipelineFlag struct { + // Name is the name of the flag + Name string `yaml:"name,omitempty" json:"name,omitempty"` + + // Shorthand is optional and is the shorthand name for this flag. E.g. 'g' converts to '-g' + Shorthand string `yaml:"shorthand,omitempty" json:"shorthand,omitempty"` + + // Type is the type of the flag. Defaults to bool if empty + Type PipelineFlagType `yaml:"type,omitempty" json:"type,omitempty"` + + // Description is the description as shown in `devspace run-pipeline my-pipe -h` + Description string `yaml:"description,omitempty" json:"description,omitempty"` +} + +type PipelineFlagType string + +const ( + PipelineFlagTypeString = "string" + PipelineFlagTypeBoolean = "bool" + PipelineFlagTypeInteger = "int" + PipelineFlagTypeStringSlice = "string_slice" +) + func (p *Pipeline) UnmarshalYAML(unmarshal func(interface{}) error) error { pipelineString := "" err := unmarshal(&pipelineString) @@ -1364,10 +1392,6 @@ type CommandConfig struct { // COMMAND_ERROR. After string `yaml:"after,omitempty" json:"after,omitempty"` - // DisableReplace signals DevSpace to not replace the default command. E.g. - // dev does not replace devspace dev. - DisableReplace bool `yaml:"disableReplace,omitempty" json:"disableReplace,omitempty"` - // Internal commands are not show in list and are usable through run_command Internal bool `yaml:"internal,omitempty" json:"internal,omitempty"` diff --git a/pkg/devspace/config/versions/v1beta11/upgrade.go b/pkg/devspace/config/versions/v1beta11/upgrade.go index 43790278d8..b9db35fa3f 100644 --- a/pkg/devspace/config/versions/v1beta11/upgrade.go +++ b/pkg/devspace/config/versions/v1beta11/upgrade.go @@ -516,12 +516,11 @@ func (c *Config) Upgrade(log log.Logger) (config.Config, error) { for _, command := range c.Commands { commandName := encoding.Convert(command.Name) nextConfig.Commands[commandName] = &next.CommandConfig{ - Name: commandName, - Command: command.Command, - Args: command.Args, - AppendArgs: command.AppendArgs, - Description: command.Description, - DisableReplace: true, + Name: commandName, + Command: command.Command, + Args: command.Args, + AppendArgs: command.AppendArgs, + Description: command.Description, } } diff --git a/pkg/devspace/context/lazy/context.go b/pkg/devspace/context/lazy/context.go deleted file mode 100644 index 13d2cf945e..0000000000 --- a/pkg/devspace/context/lazy/context.go +++ /dev/null @@ -1 +0,0 @@ -package lazy diff --git a/pkg/devspace/context/values/values.go b/pkg/devspace/context/values/values.go index da2c41207e..862fcd987c 100644 --- a/pkg/devspace/context/values/values.go +++ b/pkg/devspace/context/values/values.go @@ -1,6 +1,9 @@ package values -import "context" +import ( + "context" + flag "github.com/spf13/pflag" +) // The key type is unexported to prevent collisions type key int @@ -13,8 +16,20 @@ const ( dependencyKey rootNameKey devContextKey + flagsKey ) +// WithFlags creates a new context with the dev context +func WithFlags(parent context.Context, flagSet *flag.FlagSet) context.Context { + return WithValue(parent, flagsKey, flagSet) +} + +// FlagsFrom returns a context used to start and stop dev configurations +func FlagsFrom(ctx context.Context) (*flag.FlagSet, bool) { + flags, ok := ctx.Value(flagsKey).(*flag.FlagSet) + return flags, ok +} + // WithDevContext creates a new context with the dev context func WithDevContext(parent context.Context, devCtx context.Context) context.Context { return WithValue(parent, devContextKey, devCtx) @@ -64,17 +79,6 @@ func TempFolderFrom(ctx context.Context) (string, bool) { return user, ok } -// WithCommand returns a copy of parent in which the devspace command is set -func WithCommand(parent context.Context, name string) context.Context { - return WithValue(parent, commandKey, name) -} - -// CommandFrom returns the name of the devspace command -func CommandFrom(ctx context.Context) (string, bool) { - user, ok := ctx.Value(commandKey).(string) - return user, ok -} - func WithDependency(parent context.Context, dependency bool) context.Context { return WithValue(parent, dependencyKey, dependency) } diff --git a/pkg/devspace/pipeline/engine/basichandler/commands/get_flag.go b/pkg/devspace/pipeline/engine/basichandler/commands/get_flag.go new file mode 100644 index 0000000000..8157240d5f --- /dev/null +++ b/pkg/devspace/pipeline/engine/basichandler/commands/get_flag.go @@ -0,0 +1,49 @@ +package commands + +import ( + "context" + "fmt" + "github.com/loft-sh/devspace/pkg/devspace/context/values" + flag "github.com/spf13/pflag" + "mvdan.cc/sh/v3/interp" + "strings" +) + +func GetFlag(ctx context.Context, args []string) error { + hc := interp.HandlerCtx(ctx) + if len(args) != 1 { + _, _ = hc.Stderr.Write([]byte(fmt.Sprintf("usage: get_flag NAME"))) + return interp.NewExitStatus(1) + } + + flags, ok := values.FlagsFrom(ctx) + if !ok { + return interp.NewExitStatus(1) + } + + value := "" + found := false + flags.VisitAll(func(f *flag.Flag) { + if !found && f.Name == args[0] { + sliceType, ok := f.Value.(flag.SliceValue) + if ok { + value = strings.Join(sliceType.GetSlice(), ",") + } else { + value = f.Value.String() + } + found = true + } + }) + if !found { + _, _ = hc.Stderr.Write([]byte(fmt.Sprintf("couldn't find flag %s", args[0]))) + return interp.NewExitStatus(1) + } + + _, err := hc.Stdout.Write([]byte(value)) + if err != nil { + _, _ = hc.Stderr.Write([]byte(err.Error())) + return interp.NewExitStatus(1) + } + + return interp.NewExitStatus(0) +} diff --git a/pkg/devspace/pipeline/engine/basichandler/commands/is_command.go b/pkg/devspace/pipeline/engine/basichandler/commands/is_command.go deleted file mode 100644 index 90b084fca4..0000000000 --- a/pkg/devspace/pipeline/engine/basichandler/commands/is_command.go +++ /dev/null @@ -1,29 +0,0 @@ -package commands - -import ( - "context" - "github.com/loft-sh/devspace/pkg/devspace/context/values" - "mvdan.cc/sh/v3/interp" - "os" -) - -func IsCommand(ctx context.Context, args []string) error { - if len(args) != 1 { - return interp.NewExitStatus(1) - } - - command, ok := values.CommandFrom(ctx) - if ok { - if command == args[0] { - return interp.NewExitStatus(0) - } - return interp.NewExitStatus(1) - } - - if len(os.Args) < 2 { - return interp.NewExitStatus(1) - } else if os.Args[1] == args[0] { - return interp.NewExitStatus(0) - } - return interp.NewExitStatus(1) -} diff --git a/pkg/devspace/pipeline/engine/basichandler/handler.go b/pkg/devspace/pipeline/engine/basichandler/handler.go index 1b4fc486db..22690f675d 100644 --- a/pkg/devspace/pipeline/engine/basichandler/handler.go +++ b/pkg/devspace/pipeline/engine/basichandler/handler.go @@ -17,6 +17,9 @@ import ( // BasicCommands are extra commands DevSpace provides within the shell or are common // commands that might not be available locally for example in windows systems. var BasicCommands = map[string]func(ctx context.Context, args []string) error{ + "get_flag": func(ctx context.Context, args []string) error { + return enginecommands.GetFlag(ctx, args) + }, "is_os": func(ctx context.Context, args []string) error { return enginecommands.IsOS(args) }, @@ -29,9 +32,6 @@ var BasicCommands = map[string]func(ctx context.Context, args []string) error{ "is_true": func(ctx context.Context, args []string) error { return enginecommands.IsTrue(args) }, - "is_command": func(ctx context.Context, args []string) error { - return enginecommands.IsCommand(ctx, args) - }, "sleep": func(ctx context.Context, args []string) error { return HandleError(ctx, "sleep", enginecommands.Sleep(ctx, args)) }, diff --git a/pkg/devspace/pipeline/job.go b/pkg/devspace/pipeline/job.go index fbd18237f2..be2e9c3918 100644 --- a/pkg/devspace/pipeline/job.go +++ b/pkg/devspace/pipeline/job.go @@ -59,7 +59,7 @@ func (j *Job) Stop() error { return t.Wait() } -func (j *Job) Run(ctx devspacecontext.Context, environ expand.Environ) error { +func (j *Job) Run(ctx devspacecontext.Context, args []string, environ expand.Environ) error { if ctx.IsDone() { return ctx.Context().Err() } @@ -72,7 +72,7 @@ func (j *Job) Run(ctx devspacecontext.Context, environ expand.Environ) error { t.Go(func() error { // start the actual job done := t.NotifyGo(func() error { - return j.execute(ctx, t, environ) + return j.execute(ctx, args, t, environ) }) // wait until job is dying @@ -93,7 +93,7 @@ func (j *Job) Run(ctx devspacecontext.Context, environ expand.Environ) error { return t.Wait() } -func (j *Job) execute(ctx devspacecontext.Context, parent *tomb.Tomb, environ expand.Environ) error { +func (j *Job) execute(ctx devspacecontext.Context, args []string, parent *tomb.Tomb, environ expand.Environ) error { ctx = ctx.WithLogger(ctx.Log()) stdoutReader, stdoutWriter := io.Pipe() defer stdoutWriter.Close() @@ -118,6 +118,6 @@ func (j *Job) execute(ctx devspacecontext.Context, parent *tomb.Tomb, environ ex }) handler := pipelinehandler.NewPipelineExecHandler(ctx, stdoutWriter, stderrWriter, j.Pipeline) - _, err := engine.ExecutePipelineShellCommand(ctx.Context(), j.Config.Run, os.Args[1:], ctx.WorkingDir(), j.Config.ContinueOnError, stdoutWriter, stderrWriter, os.Stdin, environ, handler) + _, err := engine.ExecutePipelineShellCommand(ctx.Context(), j.Config.Run, args, ctx.WorkingDir(), j.Config.ContinueOnError, stdoutWriter, stderrWriter, os.Stdin, environ, handler) return err } diff --git a/pkg/devspace/pipeline/pipeline.go b/pkg/devspace/pipeline/pipeline.go index 0d0c3ab63c..bdbceb7bbf 100644 --- a/pkg/devspace/pipeline/pipeline.go +++ b/pkg/devspace/pipeline/pipeline.go @@ -175,8 +175,8 @@ func (p *pipeline) Dependencies() map[string]types.Pipeline { return children } -func (p *pipeline) Run(ctx devspacecontext.Context) error { - return p.executeJob(ctx, p.main, ctx.Environ()) +func (p *pipeline) Run(ctx devspacecontext.Context, args []string) error { + return p.executeJob(ctx, p.main, args, ctx.Environ()) } func (p *pipeline) StartNewDependencies(ctx devspacecontext.Context, dependencies []types2.Dependency, options types.DependencyOptions) error { @@ -327,7 +327,7 @@ func (p *pipeline) startNewDependency(ctx devspacecontext.Context, dependency ty if streamLogger, ok := ctx.Log().(*log.StreamLogger); !ok || streamLogger.GetFormat() != log.RawFormat { ctx = ctx.WithLogger(ctx.Log().WithPrefix(dependency.Name() + " ")) } - return pip.Run(ctx.AsDependency(dependency)) + return pip.Run(ctx.AsDependency(dependency), nil) } func (p *pipeline) StartNewPipelines(ctx devspacecontext.Context, pipelines []*latest.Pipeline, options types.PipelineOptions) error { @@ -376,7 +376,7 @@ func (p *pipeline) startNewPipeline(ctx devspacecontext.Context, configPipeline } defer p.removeJob(j, id) - err = p.executeJob(ctx, j, options.Environ) + err = p.executeJob(ctx, j, nil, options.Environ) if err != nil { return err } @@ -414,13 +414,13 @@ func (p *pipeline) removeJob(j *Job, id string) { } } -func (p *pipeline) executeJob(ctx devspacecontext.Context, j *Job, environ expand.Environ) error { +func (p *pipeline) executeJob(ctx devspacecontext.Context, j *Job, args []string, environ expand.Environ) error { // don't start jobs on a cancelled context if ctx.IsDone() { return nil } - err := j.Run(ctx, environ) + err := j.Run(ctx, args, environ) if err != nil { return err } diff --git a/pkg/devspace/pipeline/types/pipeline.go b/pkg/devspace/pipeline/types/pipeline.go index b81d742089..f564b39b85 100644 --- a/pkg/devspace/pipeline/types/pipeline.go +++ b/pkg/devspace/pipeline/types/pipeline.go @@ -36,7 +36,7 @@ type PipelineOptions struct { type Pipeline interface { // Run runs the main pipeline - Run(ctx devspacecontext.Context) error + Run(ctx devspacecontext.Context, args []string) error // DevPodManager retrieves the used dev pod manager DevPodManager() devpod.Manager From 22e04fd6249cb7d190efcf1632eb67ffe996f5be Mon Sep 17 00:00:00 2001 From: Fabian Kramm Date: Wed, 6 Apr 2022 11:31:32 +0200 Subject: [PATCH 2/2] feat: add pipeline flags --- cmd/build.go | 11 +- cmd/deploy.go | 11 +- cmd/dev.go | 11 +- cmd/purge.go | 11 +- cmd/render.go | 11 +- cmd/root.go | 18 +-- cmd/run.go | 4 +- cmd/run_pipeline.go | 115 ++++++++++++++++-- e2e/tests/hooks/hooks.go | 4 +- pkg/devspace/config/loader/parser.go | 16 +++ .../config/loader/variable/resolver.go | 2 +- pkg/devspace/config/versions/latest/schema.go | 15 ++- pkg/devspace/config/versions/versions.go | 23 ++-- pkg/devspace/context/values/values.go | 1 - .../engine/basichandler/commands/get_flag.go | 3 +- pkg/util/yamlutil/yaml.go | 7 ++ 16 files changed, 204 insertions(+), 59 deletions(-) diff --git a/cmd/build.go b/cmd/build.go index 23a41e86cf..0566ff71a9 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -2,12 +2,13 @@ package cmd import ( "github.com/loft-sh/devspace/cmd/flags" + "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" "github.com/loft-sh/devspace/pkg/util/factory" "github.com/spf13/cobra" ) // NewBuildCmd creates a new devspace build command -func NewBuildCmd(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra.Command { +func NewBuildCmd(f factory.Factory, globalFlags *flags.GlobalFlags, rawConfig *RawConfig) *cobra.Command { cmd := &RunPipelineCmd{ GlobalFlags: globalFlags, Pipeline: "build", @@ -23,10 +24,14 @@ func NewBuildCmd(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra.Comma Builds all defined images and pushes them #######################################################`, RunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.Run(cobraCmd, args, f, "build", "buildCommand") + return cmd.Run(cobraCmd, args, f, "buildCommand") }, } - cmd.AddFlags(buildCmd) + var pipeline *latest.Pipeline + if rawConfig != nil && rawConfig.Config != nil && rawConfig.Config.Pipelines != nil { + pipeline = rawConfig.Config.Pipelines["build"] + } + cmd.AddPipelineFlags(f, buildCmd, pipeline) return buildCmd } diff --git a/cmd/deploy.go b/cmd/deploy.go index 9f972e2335..d38e6460fb 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -2,12 +2,13 @@ package cmd import ( "github.com/loft-sh/devspace/cmd/flags" + "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" "github.com/loft-sh/devspace/pkg/util/factory" "github.com/spf13/cobra" ) // NewDeployCmd creates a new deploy command -func NewDeployCmd(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra.Command { +func NewDeployCmd(f factory.Factory, globalFlags *flags.GlobalFlags, rawConfig *RawConfig) *cobra.Command { cmd := &RunPipelineCmd{ GlobalFlags: globalFlags, SkipPushLocalKubernetes: true, @@ -29,10 +30,14 @@ devspace deploy --kube-context=deploy-context #######################################################`, Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.Run(cobraCmd, args, f, "deploy", "deployCommand") + return cmd.Run(cobraCmd, args, f, "deployCommand") }, } - cmd.AddFlags(deployCmd) + var pipeline *latest.Pipeline + if rawConfig != nil && rawConfig.Config != nil && rawConfig.Config.Pipelines != nil { + pipeline = rawConfig.Config.Pipelines["deploy"] + } + cmd.AddPipelineFlags(f, deployCmd, pipeline) return deployCmd } diff --git a/cmd/dev.go b/cmd/dev.go index 6ca684d96e..8b2c0c066d 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -2,12 +2,13 @@ package cmd import ( "github.com/loft-sh/devspace/cmd/flags" + "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" "github.com/loft-sh/devspace/pkg/util/factory" "github.com/spf13/cobra" ) // NewDevCmd creates a new devspace dev command -func NewDevCmd(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra.Command { +func NewDevCmd(f factory.Factory, globalFlags *flags.GlobalFlags, rawConfig *RawConfig) *cobra.Command { cmd := &RunPipelineCmd{ GlobalFlags: globalFlags, SkipPushLocalKubernetes: true, @@ -24,10 +25,14 @@ Starts your project in development mode #######################################################`, Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.Run(cobraCmd, args, f, "dev", "devCommand") + return cmd.Run(cobraCmd, args, f, "devCommand") }, } - cmd.AddFlags(devCmd) + var pipeline *latest.Pipeline + if rawConfig != nil && rawConfig.Config != nil && rawConfig.Config.Pipelines != nil { + pipeline = rawConfig.Config.Pipelines["dev"] + } + cmd.AddPipelineFlags(f, devCmd, pipeline) return devCmd } diff --git a/cmd/purge.go b/cmd/purge.go index c2a36893aa..d2f069b1f0 100644 --- a/cmd/purge.go +++ b/cmd/purge.go @@ -1,6 +1,7 @@ package cmd import ( + "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" "github.com/loft-sh/devspace/pkg/util/factory" "github.com/loft-sh/devspace/cmd/flags" @@ -8,7 +9,7 @@ import ( ) // NewPurgeCmd creates a new purge command -func NewPurgeCmd(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra.Command { +func NewPurgeCmd(f factory.Factory, globalFlags *flags.GlobalFlags, rawConfig *RawConfig) *cobra.Command { cmd := &RunPipelineCmd{ GlobalFlags: globalFlags, Pipeline: "purge", @@ -27,10 +28,14 @@ devspace purge #######################################################`, Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.Run(cobraCmd, args, f, "purge", "purgeCommand") + return cmd.Run(cobraCmd, args, f, "purgeCommand") }, } - cmd.AddFlags(purgeCmd) + var pipeline *latest.Pipeline + if rawConfig != nil && rawConfig.Config != nil && rawConfig.Config.Pipelines != nil { + pipeline = rawConfig.Config.Pipelines["purge"] + } + cmd.AddPipelineFlags(f, purgeCmd, pipeline) return purgeCmd } diff --git a/cmd/render.go b/cmd/render.go index d218562f8d..afdef3aefd 100644 --- a/cmd/render.go +++ b/cmd/render.go @@ -1,6 +1,7 @@ package cmd import ( + "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" "os" "github.com/loft-sh/devspace/cmd/flags" @@ -9,7 +10,7 @@ import ( ) // NewRenderCmd creates a new devspace render command -func NewRenderCmd(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra.Command { +func NewRenderCmd(f factory.Factory, globalFlags *flags.GlobalFlags, rawConfig *RawConfig) *cobra.Command { cmd := &RunPipelineCmd{ GlobalFlags: globalFlags, SkipPushLocalKubernetes: true, @@ -31,10 +32,14 @@ deployment. #######################################################`, RunE: func(cobraCmd *cobra.Command, args []string) error { f.GetLog().Warnf("This command is deprecated, please use 'devspace deploy --render' instead") - return cmd.Run(cobraCmd, args, f, "render", "renderCommand") + return cmd.Run(cobraCmd, args, f, "renderCommand") }, } - cmd.AddFlags(renderCmd) + var pipeline *latest.Pipeline + if rawConfig != nil && rawConfig.Config != nil && rawConfig.Config.Pipelines != nil { + pipeline = rawConfig.Config.Pipelines["deploy"] + } + cmd.AddPipelineFlags(f, renderCmd, pipeline) return renderCmd } diff --git a/cmd/root.go b/cmd/root.go index e8ee2bedc1..0c5b223aed 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -246,7 +246,7 @@ Additional run commands: rootCmd.AddCommand(NewInitCmd(f)) rootCmd.AddCommand(NewRestartCmd(f, globalFlags)) rootCmd.AddCommand(NewSyncCmd(f, globalFlags)) - rootCmd.AddCommand(NewRenderCmd(f, globalFlags)) + rootCmd.AddCommand(NewRenderCmd(f, globalFlags, rawConfig)) rootCmd.AddCommand(NewUpgradeCmd()) rootCmd.AddCommand(NewEnterCmd(f, globalFlags)) rootCmd.AddCommand(NewAnalyzeCmd(f, globalFlags)) @@ -256,14 +256,14 @@ Additional run commands: rootCmd.AddCommand(NewRunCmd(f, globalFlags, rawConfig)) rootCmd.AddCommand(NewAttachCmd(f, globalFlags)) rootCmd.AddCommand(NewPrintCmd(f, globalFlags)) - rootCmd.AddCommand(NewRunPipelineCmd(f, globalFlags)) + rootCmd.AddCommand(NewRunPipelineCmd(f, globalFlags, rawConfig)) rootCmd.AddCommand(NewCompletionCmd()) // check overwrite commands - rootCmd.AddCommand(NewDevCmd(f, globalFlags)) - rootCmd.AddCommand(NewDeployCmd(f, globalFlags)) - rootCmd.AddCommand(NewBuildCmd(f, globalFlags)) - rootCmd.AddCommand(NewPurgeCmd(f, globalFlags)) + rootCmd.AddCommand(NewDevCmd(f, globalFlags, rawConfig)) + rootCmd.AddCommand(NewDeployCmd(f, globalFlags, rawConfig)) + rootCmd.AddCommand(NewBuildCmd(f, globalFlags, rawConfig)) + rootCmd.AddCommand(NewPurgeCmd(f, globalFlags, rawConfig)) // Add plugin commands plugin.AddPluginCommands(rootCmd, plugins, "") @@ -326,7 +326,7 @@ type RawConfig struct { RawConfig map[string]interface{} Resolver variable.Resolver - CommandsConfig *latest.Config + Config *latest.Config resolvedMutex sync.Mutex resolved map[string]string @@ -345,8 +345,8 @@ func (r *RawConfig) Parse( r.Resolver = resolver // try parsing commands - latestConfig, beforeConversion, err := loader.NewCommandsParser().Parse(ctx, originalRawConfig, rawConfig, resolver, log) - r.CommandsConfig = latestConfig + latestConfig, beforeConversion, err := loader.NewCommandsPipelinesParser().Parse(ctx, originalRawConfig, rawConfig, resolver, log) + r.Config = latestConfig return latestConfig, beforeConversion, err } diff --git a/cmd/run.go b/cmd/run.go index d731729190..723e7825e1 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -75,8 +75,8 @@ devspace --dependency my-dependency run any-command --any-command-flag return cmd.RunRun(f, args) } - if rawConfig != nil && rawConfig.CommandsConfig != nil { - for _, cmd := range rawConfig.CommandsConfig.Commands { + if rawConfig != nil && rawConfig.Config != nil { + for _, cmd := range rawConfig.Config.Commands { runCmd.AddCommand(NewSpecificRunCommand(f, globalFlags, cmd, rawConfig.Resolver.ResolvedVariables())) } } diff --git a/cmd/run_pipeline.go b/cmd/run_pipeline.go index fb2333455e..1c6ae6076f 100644 --- a/cmd/run_pipeline.go +++ b/cmd/run_pipeline.go @@ -67,7 +67,7 @@ type RunPipelineCmd struct { log log.Logger } -func (cmd *RunPipelineCmd) AddFlags(command *cobra.Command) { +func (cmd *RunPipelineCmd) AddPipelineFlags(f factory.Factory, command *cobra.Command, pipeline *latest.Pipeline) { command.Flags().StringSliceVar(&cmd.SkipDependency, "skip-dependency", cmd.SkipDependency, "Skips the following dependencies for deployment") command.Flags().StringSliceVar(&cmd.Dependency, "dependency", cmd.Dependency, "Deploys only the specified named dependencies") @@ -87,41 +87,134 @@ func (cmd *RunPipelineCmd) AddFlags(command *cobra.Command) { command.Flags().BoolVar(&cmd.SkipPushLocalKubernetes, "skip-push-local-kube", cmd.SkipPushLocalKubernetes, "Skips image pushing, if a local kubernetes environment is detected") command.Flags().BoolVar(&cmd.ShowUI, "show-ui", cmd.ShowUI, "Shows the ui server") + + if pipeline != nil { + for _, pipelineFlag := range pipeline.Flags { + if pipelineFlag.Name == "" { + continue + } + + usage := pipelineFlag.Description + if usage == "" { + usage = "Flag " + pipelineFlag.Name + } + + var ok bool + if pipelineFlag.Type == "" || pipelineFlag.Type == latest.PipelineFlagTypeBoolean { + val := false + if pipelineFlag.Default != nil { + val, ok = pipelineFlag.Default.(bool) + if !ok { + f.GetLog().Errorf("Error parsing default value for flag %s: %#v is not a boolean", pipelineFlag.Name, pipelineFlag.Default) + continue + } + } + + command.Flags().BoolP(pipelineFlag.Name, pipelineFlag.Short, val, usage) + } else if pipelineFlag.Type == latest.PipelineFlagTypeString { + val := "" + if pipelineFlag.Default != nil { + val, ok = pipelineFlag.Default.(string) + if !ok { + f.GetLog().Errorf("Error parsing default value for flag %s: %#v is not a string", pipelineFlag.Name, pipelineFlag.Default) + continue + } + } + + command.Flags().StringP(pipelineFlag.Name, pipelineFlag.Short, val, usage) + } else if pipelineFlag.Type == latest.PipelineFlagTypeInteger { + val := 0 + if pipelineFlag.Default != nil { + val, ok = pipelineFlag.Default.(int) + if !ok { + f.GetLog().Errorf("Error parsing default value for flag %s: %#v is not an integer", pipelineFlag.Name, pipelineFlag.Default) + continue + } + } + + command.Flags().IntP(pipelineFlag.Name, pipelineFlag.Short, val, usage) + } else if pipelineFlag.Type == latest.PipelineFlagTypeStringArray { + val := []string{} + if pipelineFlag.Default != nil { + val, ok = pipelineFlag.Default.([]string) + if !ok { + f.GetLog().Errorf("Error parsing default value for flag %s: %#v is not a string array", pipelineFlag.Name, pipelineFlag.Default) + continue + } + } + + command.Flags().StringSliceP(pipelineFlag.Name, pipelineFlag.Short, val, usage) + } + } + } } // NewRunPipelineCmd creates a new devspace run-pipeline command -func NewRunPipelineCmd(f factory.Factory, globalFlags *flags.GlobalFlags) *cobra.Command { +func NewRunPipelineCmd(f factory.Factory, globalFlags *flags.GlobalFlags, rawConfig *RawConfig) *cobra.Command { cmd := &RunPipelineCmd{ GlobalFlags: globalFlags, SkipPushLocalKubernetes: true, } runPipelineCmd := &cobra.Command{ Use: "run-pipeline", - Short: "Starts the development mode", + Short: "Starts a DevSpace pipeline", Long: ` ####################################################### ############## devspace run-pipeline ################## ####################################################### -Execute a pipeline +Execute a pipeline: +devspace run-pipeline my-pipeline +devspace run-pipeline dev #######################################################`, Args: cobra.ArbitraryArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { - return cmd.Run(cobraCmd, args, f, "run-pipeline", "runPipelineCommand") + return cmd.Run(cobraCmd, args, f, "runPipelineCommand") }, } - cmd.AddFlags(runPipelineCmd) + if rawConfig != nil && rawConfig.Config != nil { + for _, pipe := range rawConfig.Config.Pipelines { + runPipelineCmd.AddCommand(NewSpecificPipelineCmd(f, globalFlags, pipe)) + } + } + cmd.AddPipelineFlags(f, runPipelineCmd, nil) return runPipelineCmd } +// NewSpecificPipelineCmd creates a new devspace render command +func NewSpecificPipelineCmd(f factory.Factory, globalFlags *flags.GlobalFlags, pipeline *latest.Pipeline) *cobra.Command { + cmd := &RunPipelineCmd{ + GlobalFlags: globalFlags, + SkipPushLocalKubernetes: true, + Pipeline: pipeline.Name, + } + + specificPipelineCmd := &cobra.Command{ + Use: pipeline.Name, + Short: "Executes pipeline " + pipeline.Name, + Long: ` +####################################################### +######### devspace run-pipeline ` + pipeline.Name + ` ########## +####################################################### +Executes pipeline ` + pipeline.Name + ` +#######################################################`, + RunE: func(cobraCmd *cobra.Command, args []string) error { + return cmd.Run(cobraCmd, args, f, "runPipelineCommand") + }, + } + + cmd.AddPipelineFlags(f, specificPipelineCmd, pipeline) + return specificPipelineCmd +} + func (cmd *RunPipelineCmd) RunDefault(f factory.Factory) error { - return cmd.Run(nil, nil, f, "run-pipeline", "runPipelineCommand") + return cmd.Run(nil, nil, f, "runPipelineCommand") } // Run executes the command logic -func (cmd *RunPipelineCmd) Run(cobraCmd *cobra.Command, args []string, f factory.Factory, commandName, hookName string) error { +func (cmd *RunPipelineCmd) Run(cobraCmd *cobra.Command, args []string, f factory.Factory, hookName string) error { dashArgs := []string{} - if cobraCmd.ArgsLenAtDash() > -1 { + if cobraCmd != nil && cobraCmd.ArgsLenAtDash() > -1 { dashArgs = args[cobraCmd.ArgsLenAtDash():] args = args[:cobraCmd.ArgsLenAtDash()] } @@ -159,7 +252,9 @@ func (cmd *RunPipelineCmd) Run(cobraCmd *cobra.Command, args []string, f factory } // set command in context - cmd.Ctx = values.WithFlags(cmd.Ctx, cobraCmd.Flags()) + if cobraCmd != nil { + cmd.Ctx = values.WithFlags(cmd.Ctx, cobraCmd.Flags()) + } options := cmd.BuildOptions(cmd.ToConfigOptions()) ctx, err := initialize(cmd.Ctx, f, false, options, cmd.log) if err != nil { diff --git a/e2e/tests/hooks/hooks.go b/e2e/tests/hooks/hooks.go index d3ae07db04..cef9557b29 100644 --- a/e2e/tests/hooks/hooks.go +++ b/e2e/tests/hooks/hooks.go @@ -55,7 +55,7 @@ var _ = DevSpaceDescribe("hooks", func() { Pipeline: "build", SkipPush: false, } - err = buildCmd.Run(nil, nil, f, "build", "buildCommand") + err = buildCmd.Run(nil, nil, f, "buildCommand") framework.ExpectError(err) // check if files are there @@ -81,7 +81,7 @@ var _ = DevSpaceDescribe("hooks", func() { Pipeline: "dev", SkipPush: true, } - err = devCmd.Run(nil, nil, f, "dev", "devCommand") + err = devCmd.Run(nil, nil, f, "devCommand") framework.ExpectError(err) // check if files are correctly created diff --git a/pkg/devspace/config/loader/parser.go b/pkg/devspace/config/loader/parser.go index e2978cf2ba..021f3b59a8 100644 --- a/pkg/devspace/config/loader/parser.go +++ b/pkg/devspace/config/loader/parser.go @@ -33,6 +33,22 @@ func (d *defaultParser) Parse(ctx context.Context, originalRawConfig map[string] return fillVariablesAndParse(ctx, resolver, rawConfig, log) } +func NewCommandsPipelinesParser() Parser { + return &commandsPipelinesParser{} +} + +type commandsPipelinesParser struct{} + +func (c *commandsPipelinesParser) Parse(ctx context.Context, originalRawConfig map[string]interface{}, rawConfig map[string]interface{}, resolver variable.Resolver, log log.Logger) (*latest.Config, map[string]interface{}, error) { + // modify the config + preparedConfig, err := versions.Get(rawConfig, "commands", "pipelines") + if err != nil { + return nil, nil, err + } + + return fillVariablesAndParse(ctx, resolver, preparedConfig, log) +} + func NewCommandsParser() Parser { return &commandsParser{} } diff --git a/pkg/devspace/config/loader/variable/resolver.go b/pkg/devspace/config/loader/variable/resolver.go index 7329048be5..d6d0882773 100644 --- a/pkg/devspace/config/loader/variable/resolver.go +++ b/pkg/devspace/config/loader/variable/resolver.go @@ -18,7 +18,7 @@ import ( "github.com/pkg/errors" ) -var AlwaysResolvePredefinedVars = []string{"DEVSPACE_NAME", "DEVSPACE_TMPDIR", "DEVSPACE_VERSION", "DEVSPACE_RANDOM", "DEVSPACE_PROFILE", "DEVSPACE_USER_HOME", "DEVSPACE_TIMESTAMP", "DEVSPACE_CONTEXT", "DEVSPACE_NAMESPACE"} +var AlwaysResolvePredefinedVars = []string{"DEVSPACE_NAME", "DEVSPACE_TMPDIR", "DEVSPACE_VERSION", "DEVSPACE_RANDOM", "DEVSPACE_PROFILE", "DEVSPACE_USER_HOME", "DEVSPACE_TIMESTAMP", "devspace.context", "DEVSPACE_CONTEXT", "devspace.namespace", "DEVSPACE_NAMESPACE"} // NewResolver creates a new resolver that caches resolved variables in memory and in the provided cache func NewResolver(localCache localcache.Cache, predefinedVariableOptions *PredefinedVariableOptions, flags []string, log log.Logger) (Resolver, error) { diff --git a/pkg/devspace/config/versions/latest/schema.go b/pkg/devspace/config/versions/latest/schema.go index 66546d30ed..3ed1fd5a02 100644 --- a/pkg/devspace/config/versions/latest/schema.go +++ b/pkg/devspace/config/versions/latest/schema.go @@ -153,12 +153,15 @@ type PipelineFlag struct { // Name is the name of the flag Name string `yaml:"name,omitempty" json:"name,omitempty"` - // Shorthand is optional and is the shorthand name for this flag. E.g. 'g' converts to '-g' - Shorthand string `yaml:"shorthand,omitempty" json:"shorthand,omitempty"` + // Short is optional and is the shorthand name for this flag. E.g. 'g' converts to '-g' + Short string `yaml:"short,omitempty" json:"short,omitempty"` // Type is the type of the flag. Defaults to bool if empty Type PipelineFlagType `yaml:"type,omitempty" json:"type,omitempty"` + // Default is the default value for this flag + Default interface{} `yaml:"default,omitempty" json:"default,omitempty"` + // Description is the description as shown in `devspace run-pipeline my-pipe -h` Description string `yaml:"description,omitempty" json:"description,omitempty"` } @@ -169,7 +172,7 @@ const ( PipelineFlagTypeString = "string" PipelineFlagTypeBoolean = "bool" PipelineFlagTypeInteger = "int" - PipelineFlagTypeStringSlice = "string_slice" + PipelineFlagTypeStringArray = "stringArray" ) func (p *Pipeline) UnmarshalYAML(unmarshal func(interface{}) error) error { @@ -187,7 +190,7 @@ func (p *Pipeline) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } - return json.Unmarshal(out, p) + return yamlutil.UnmarshalStrictJSON(out, p) } p.Run = pipelineString @@ -1425,7 +1428,7 @@ func (c *CommandConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } - return json.Unmarshal(out, c) + return yamlutil.UnmarshalStrictJSON(out, c) } c.Command = commandString @@ -1495,7 +1498,7 @@ func (v *Variable) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } - return json.Unmarshal(out, v) + return yamlutil.UnmarshalStrictJSON(out, v) } if strings.HasPrefix(varString, "$(") && strings.HasSuffix(varString, ")") { varString = strings.TrimPrefix(strings.TrimSuffix(varString, ")"), "$(") diff --git a/pkg/devspace/config/versions/versions.go b/pkg/devspace/config/versions/versions.go index 04e75be0e1..807e26047b 100644 --- a/pkg/devspace/config/versions/versions.go +++ b/pkg/devspace/config/versions/versions.go @@ -82,26 +82,25 @@ func ParseProfile(ctx context.Context, basePath string, data map[string]interfac } // Get parses only the key from the config -func Get(data map[string]interface{}, key string) (map[string]interface{}, error) { +func Get(data map[string]interface{}, keys ...string) (map[string]interface{}, error) { retMap := map[string]interface{}{} err := util.Convert(data, &retMap) if err != nil { return nil, err } - keyData, ok := retMap[key] - if !ok { - return map[string]interface{}{ - "version": retMap["version"], - "name": retMap["name"], - }, nil - } - - return map[string]interface{}{ + retConfig := map[string]interface{}{ "version": retMap["version"], "name": retMap["name"], - key: keyData, - }, nil + } + for _, key := range keys { + keyData, ok := retMap[key] + if ok { + retConfig[key] = keyData + } + } + + return retConfig, nil } // ParseVariables parses only the variables from the config diff --git a/pkg/devspace/context/values/values.go b/pkg/devspace/context/values/values.go index 862fcd987c..842f4a301d 100644 --- a/pkg/devspace/context/values/values.go +++ b/pkg/devspace/context/values/values.go @@ -12,7 +12,6 @@ const ( // nameKey is the context key for the DevSpace name. nameKey key = iota tempFolderKey - commandKey dependencyKey rootNameKey devContextKey diff --git a/pkg/devspace/pipeline/engine/basichandler/commands/get_flag.go b/pkg/devspace/pipeline/engine/basichandler/commands/get_flag.go index 8157240d5f..19cef6be7a 100644 --- a/pkg/devspace/pipeline/engine/basichandler/commands/get_flag.go +++ b/pkg/devspace/pipeline/engine/basichandler/commands/get_flag.go @@ -12,12 +12,13 @@ import ( func GetFlag(ctx context.Context, args []string) error { hc := interp.HandlerCtx(ctx) if len(args) != 1 { - _, _ = hc.Stderr.Write([]byte(fmt.Sprintf("usage: get_flag NAME"))) + _, _ = hc.Stderr.Write([]byte("usage: get_flag NAME")) return interp.NewExitStatus(1) } flags, ok := values.FlagsFrom(ctx) if !ok { + _, _ = hc.Stderr.Write([]byte("cannot use get_flag in a non pipeline command")) return interp.NewExitStatus(1) } diff --git a/pkg/util/yamlutil/yaml.go b/pkg/util/yamlutil/yaml.go index 6eaefddb3d..4a7a59c1de 100644 --- a/pkg/util/yamlutil/yaml.go +++ b/pkg/util/yamlutil/yaml.go @@ -2,6 +2,7 @@ package yamlutil import ( "bytes" + "encoding/json" "fmt" "io/ioutil" "os" @@ -20,6 +21,12 @@ func UnmarshalString(data string, out interface{}) error { var lineRegEx = regexp.MustCompile(`^line ([0-9]+):`) +func UnmarshalStrictJSON(data []byte, out interface{}) error { + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + return decoder.Decode(out) +} + func UnmarshalStrict(data []byte, out interface{}) error { decoder := yaml.NewDecoder(bytes.NewReader(data)) decoder.KnownFields(true)