Skip to content

Commit

Permalink
Merge pull request #2798 from lizardruss/ENG-2735
Browse files Browse the repository at this point in the history
fix: treat pipeline flag defaults consistently when used as command f…
  • Loading branch information
lizardruss authored Feb 21, 2024
2 parents e6293eb + 99ad69b commit b6dd518
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 44 deletions.
53 changes: 18 additions & 35 deletions cmd/run_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/loft-sh/devspace/pkg/devspace/hook"
"github.com/loft-sh/devspace/pkg/devspace/kill"
"github.com/loft-sh/devspace/pkg/devspace/kubectl"
"github.com/loft-sh/devspace/pkg/devspace/pipeline"
pipelinepkg "github.com/loft-sh/devspace/pkg/devspace/pipeline"
"github.com/loft-sh/devspace/pkg/devspace/pipeline/types"
"github.com/loft-sh/devspace/pkg/devspace/plugin"
"github.com/loft-sh/devspace/pkg/devspace/upgrade"
Expand Down Expand Up @@ -100,51 +100,34 @@ func (cmd *RunPipelineCmd) AddPipelineFlags(f factory.Factory, command *cobra.Co
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
}
val, err := pipelinepkg.GetDefaultValue(pipelineFlag)
if err != nil {
f.GetLog().Errorf("Error parsing default value for flag %s: %#v is not a boolean", pipelineFlag.Name, pipelineFlag.Default)
}

command.Flags().BoolP(pipelineFlag.Name, pipelineFlag.Short, val, usage)
command.Flags().BoolP(pipelineFlag.Name, pipelineFlag.Short, val.(bool), 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
}
val, err := pipelinepkg.GetDefaultValue(pipelineFlag)
if err != nil {
f.GetLog().Errorf("Error parsing default value for flag %s: %#v is not a string", pipelineFlag.Name, pipelineFlag.Default)
}

command.Flags().StringP(pipelineFlag.Name, pipelineFlag.Short, val, usage)
command.Flags().StringP(pipelineFlag.Name, pipelineFlag.Short, val.(string), 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
}
val, err := pipelinepkg.GetDefaultValue(pipelineFlag)
if err != nil {
f.GetLog().Errorf("Error parsing default value for flag %s: %#v is not an integer", pipelineFlag.Name, pipelineFlag.Default)
}

command.Flags().IntP(pipelineFlag.Name, pipelineFlag.Short, val, usage)
command.Flags().IntP(pipelineFlag.Name, pipelineFlag.Short, val.(int), 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
}
val, err := pipelinepkg.GetDefaultValue(pipelineFlag)
if err != nil {
f.GetLog().Errorf("Error parsing default value for flag %s: %#v is not a string array", pipelineFlag.Name, pipelineFlag.Default)
}

command.Flags().StringSliceP(pipelineFlag.Name, pipelineFlag.Short, val, usage)
command.Flags().StringSliceP(pipelineFlag.Name, pipelineFlag.Short, val.([]string), usage)
}
}
}
Expand Down Expand Up @@ -463,7 +446,7 @@ func runPipeline(ctx devspacecontext.Context, args []string, options *CommandOpt
dependencyRegistry := registry.NewDependencyRegistry(ctx.Config().Config().Name, options.DeployOptions.Render)

// get deploy pipeline
pipe := pipeline.NewPipeline(ctx.Config().Config().Name, devPodManager, dependencyRegistry, configPipeline, options.Options)
pipe := pipelinepkg.NewPipeline(ctx.Config().Config().Name, devPodManager, dependencyRegistry, configPipeline, options.Options)
kill.SetStopFunction(func(message string) {
if message != "" {
ctx.Log().WriteString(logrus.FatalLevel, "\n"+ansi.Color("fatal", "red+b")+" "+message+"\n")
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/configuration/pipelines/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,8 @@ pipelines:
short: e
type: stringArray
run: |-
extraEnv=$(get_flag "env") # Retrieve the value of the `env` flag and store it in a variable
echo ${extraEnv[1]}
extraEnv=($(get_flag "env")) # Retrieve the value of the `env` flag and store it in an array variable
echo ${extraEnv[0]} # Arrays are zero indexed

TERMINAL_ENABLED=true
if [ $(get_flag "logs") == "true" ]; then # Test if --logs/-l flag is used or not
Expand Down
139 changes: 139 additions & 0 deletions e2e/tests/pipelines/pipelines.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ var _ = DevSpaceDescribe("pipelines", func() {
framework.ExpectLocalFileContentsImmediately("other.txt", "test\n")
framework.ExpectLocalFileContentsImmediately("other2.txt", "false\n")
framework.ExpectLocalFileContentsImmediately("other3.txt", "true\n")
framework.ExpectLocalFileContentsImmediately("other4-0.txt", "one\n")
framework.ExpectLocalFileContentsImmediately("other4-1.txt", "two\n")
framework.ExpectLocalFileContentsImmediately("other-profile.txt", "profile1\n")
framework.ExpectLocalFileContentsImmediately("dep1-test.txt", "test\n")
framework.ExpectLocalFileContentsImmediately("dep1-test2.txt", "true\n")
Expand All @@ -85,6 +87,143 @@ var _ = DevSpaceDescribe("pipelines", func() {
framework.ExpectLocalFileContentsImmediately("dep1-other-profile.txt", "profile1\n")
})

ginkgo.It("should resolve pipeline override array flags", func() {
tempDir, err := framework.CopyToTempDir("tests/pipelines/testdata/flags")
framework.ExpectNoError(err)
defer framework.CleanupTempDir(initialDir, tempDir)

ns, err := kubeClient.CreateNamespace("pipelines")
framework.ExpectNoError(err)
defer framework.ExpectDeleteNamespace(kubeClient, ns)

rootCmd := cmd.NewRootCmd(f)
persistentFlags := rootCmd.PersistentFlags()
globalFlags := flags.SetGlobalFlags(persistentFlags)
globalFlags.NoWarn = true
globalFlags.Namespace = ns
globalFlags.Profiles = []string{"profile1"}

cmdCtx := values.WithCommandFlags(context.Background(), globalFlags.Flags)
cmdCtx = values.WithFlagsMap(cmdCtx, map[string]string{
"other": "test",
"other2": "false",
"other3": "true",
"other4": "three four",
})

devCmd := &cmd.RunPipelineCmd{
GlobalFlags: globalFlags,
Pipeline: "other",
Ctx: cmdCtx,
}
err = devCmd.RunDefault(f)
framework.ExpectNoError(err)

framework.ExpectLocalFileContentsImmediately("other.txt", "test\n")
framework.ExpectLocalFileContentsImmediately("other2.txt", "false\n")
framework.ExpectLocalFileContentsImmediately("other3.txt", "true\n")
framework.ExpectLocalFileContentsImmediately("other-profile.txt", "profile1\n")
framework.ExpectLocalFileContentsImmediately("other4-0.txt", "three\n")
framework.ExpectLocalFileContentsImmediately("other4-1.txt", "four\n")
})

ginkgo.It("should resolve pipeline override with --set-flags", func() {
tempDir, err := framework.CopyToTempDir("tests/pipelines/testdata/flags")
framework.ExpectNoError(err)
defer framework.CleanupTempDir(initialDir, tempDir)

ns, err := kubeClient.CreateNamespace("pipelines")
framework.ExpectNoError(err)
defer framework.ExpectDeleteNamespace(kubeClient, ns)

rootCmd := cmd.NewRootCmd(f)
persistentFlags := rootCmd.PersistentFlags()
globalFlags := flags.SetGlobalFlags(persistentFlags)
globalFlags.NoWarn = true
globalFlags.Namespace = ns
globalFlags.Profiles = []string{"profile1"}

cmdCtx := values.WithCommandFlags(context.Background(), globalFlags.Flags)
cmdCtx = values.WithFlagsMap(cmdCtx, map[string]string{})

devCmd := &cmd.RunPipelineCmd{
GlobalFlags: globalFlags,
Pipeline: "other-override",
Ctx: cmdCtx,
}
err = devCmd.RunDefault(f)
framework.ExpectNoError(err)

framework.ExpectLocalFileContentsImmediately("other.txt", "test\n")
framework.ExpectLocalFileContentsImmediately("other2.txt", "true\n")
framework.ExpectLocalFileContentsImmediately("other3.txt", "true\n")
framework.ExpectLocalFileContentsImmediately("other-profile.txt", "profile1\n")
framework.ExpectLocalFileContentsImmediately("other4-0.txt", "five\n")
framework.ExpectLocalFileContentsImmediately("other4-1.txt", "six\n")
})

ginkgo.It("should resolve dependency pipeline flag defaults", func() {
tempDir, err := framework.CopyToTempDir("tests/pipelines/testdata/flags")
framework.ExpectNoError(err)
defer framework.CleanupTempDir(initialDir, tempDir)

ns, err := kubeClient.CreateNamespace("pipelines")
framework.ExpectNoError(err)
defer framework.ExpectDeleteNamespace(kubeClient, ns)

rootCmd := cmd.NewRootCmd(f)
persistentFlags := rootCmd.PersistentFlags()
globalFlags := flags.SetGlobalFlags(persistentFlags)
globalFlags.NoWarn = true
globalFlags.Namespace = ns
globalFlags.Profiles = []string{"profile1"}

cmdCtx := values.WithCommandFlags(context.Background(), globalFlags.Flags)
cmdCtx = values.WithFlagsMap(cmdCtx, map[string]string{})

devCmd := &cmd.RunPipelineCmd{
GlobalFlags: globalFlags,
Pipeline: "arr-dep1",
Ctx: cmdCtx,
}
err = devCmd.RunDefault(f)
framework.ExpectNoError(err)

framework.ExpectLocalFileContentsImmediately("arr-0.txt", "one")
framework.ExpectLocalFileContentsImmediately("arr-1.txt", "two")
})

ginkgo.It("should resolve dependency pipeline flag defaults", func() {
tempDir, err := framework.CopyToTempDir("tests/pipelines/testdata/flags")
framework.ExpectNoError(err)
defer framework.CleanupTempDir(initialDir, tempDir)

ns, err := kubeClient.CreateNamespace("pipelines")
framework.ExpectNoError(err)
defer framework.ExpectDeleteNamespace(kubeClient, ns)

rootCmd := cmd.NewRootCmd(f)
persistentFlags := rootCmd.PersistentFlags()
globalFlags := flags.SetGlobalFlags(persistentFlags)
globalFlags.NoWarn = true
globalFlags.Namespace = ns
globalFlags.Profiles = []string{"profile1"}

cmdCtx := values.WithCommandFlags(context.Background(), globalFlags.Flags)
cmdCtx = values.WithFlagsMap(cmdCtx, map[string]string{})

devCmd := &cmd.RunPipelineCmd{
GlobalFlags: globalFlags,
Pipeline: "arr-dep1-override",
Ctx: cmdCtx,
}
err = devCmd.RunDefault(f)
framework.ExpectNoError(err)

framework.ExpectLocalFileContentsImmediately("arr-0.txt", "three")
framework.ExpectLocalFileContentsImmediately("arr-1.txt", "")
})

ginkgo.It("should exec container", func() {
tempDir, err := framework.CopyToTempDir("tests/pipelines/testdata/exec_container")
framework.ExpectNoError(err)
Expand Down
12 changes: 12 additions & 0 deletions e2e/tests/pipelines/testdata/flags/dep1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,15 @@ pipelines:
echo $(get_flag test3) > dep1-test2.txt
echo $(get_flag profile) > dep1-dev-profile.txt
run_pipelines other --set-flag other2=false
array:
flags:
- name: arr
type: stringArray
default:
- one
- two
run: |-
arr=($(get_flag arr))
echo -n ${arr[0]} > arr-0.txt
echo -n ${arr[1]} > arr-1.txt
21 changes: 21 additions & 0 deletions e2e/tests/pipelines/testdata/flags/devspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ pipelines:
default: true
- name: other3
default: true
- name: other4
type: stringArray
default:
- one
- two
run: |-
if get_flag test; then
exit 1
Expand All @@ -24,6 +29,22 @@ pipelines:
echo $(get_flag other2) > other2.txt
echo $(get_flag other3) > other3.txt
echo $(get_flag profile) > other-profile.txt
other4=($(get_flag other4))
echo ${other4[0]} > other4-0.txt
echo ${other4[1]} > other4-1.txt
other-override:
run: |-
run_pipelines other --set-flag other4=five --set-flag other4=six
arr-dep1:
run: |-
run_dependency_pipelines dep1 --pipeline array
arr-dep1-override:
run: |-
run_dependency_pipelines dep1 --pipeline array --set-flag arr=three
dev:
flags:
Expand Down
77 changes: 77 additions & 0 deletions pkg/devspace/pipeline/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package pipeline

import (
"fmt"
"strconv"

"github.com/loft-sh/devspace/pkg/devspace/config/versions/latest"
)

func GetDefaultValue(pipelineFlag latest.PipelineFlag) (interface{}, error) {
var ok bool

switch pipelineFlag.Type {
case "", latest.PipelineFlagTypeBoolean:
val := false
if pipelineFlag.Default != nil {
val, ok = pipelineFlag.Default.(bool)
if !ok {
return nil, fmt.Errorf(" default is not a boolean")
}
}
return val, nil
case latest.PipelineFlagTypeString:
val := ""
if pipelineFlag.Default != nil {
val, ok = pipelineFlag.Default.(string)
if !ok {
return nil, fmt.Errorf("default is not a string")
}
}
return val, nil
case latest.PipelineFlagTypeInteger:
val := 0
if pipelineFlag.Default != nil {
switch pipelineFlag.Default.(type) {
case float64:
floatVal, ok := pipelineFlag.Default.(float64)
if !ok {
return nil, fmt.Errorf("default is not an integer")
}
return int(floatVal), nil
case int:
intVal, ok := pipelineFlag.Default.(int)
if !ok {
return nil, fmt.Errorf("default is not an integer")
}
return intVal, nil
case string:
strVal, ok := pipelineFlag.Default.(string)
if !ok {
return nil, fmt.Errorf("default is not an integer")
}
intVal, err := strconv.ParseInt(strVal, 10, 0)
if err != nil {
return nil, err
}
return int(intVal), nil
}
return nil, fmt.Errorf("default is not an integer")
}
return val, nil
case latest.PipelineFlagTypeStringArray:
val := []string{}
if pipelineFlag.Default != nil {
for _, anyVal := range pipelineFlag.Default.([]interface{}) {
strVal, ok := anyVal.(string)
if !ok {
return nil, fmt.Errorf("default is not a string array")
}
val = append(val, strVal)
}
}
return val, nil
}

return nil, fmt.Errorf("unsupported flag type")
}
Loading

0 comments on commit b6dd518

Please sign in to comment.