diff --git a/internal/cmd/cli.go b/internal/cmd/cli.go index a4ccdde..29e08ff 100644 --- a/internal/cmd/cli.go +++ b/internal/cmd/cli.go @@ -3,6 +3,7 @@ package cmd import ( "context" + "errors" "fmt" "os" @@ -38,25 +39,16 @@ type ( // Run executes the command line interface to the app. The passed ctx is used to cancel long running tasks. // appName is the name of the application and forms the suffix of the dot config file. func Run(ctx context.Context) int { + return runWithArgs(ctx, os.Args) +} + +func runWithArgs(ctx context.Context, args []string) int { // Setup cli clogging handler loggee.SetLogger(apexlog.NewCli(2)) - info := buildinfo.GetBuildInfo(ctx) + cli := newCli(ctx) - cli := &cli{ - appName: info.RunName, - rootCmd: &cobra.Command{ - Use: info.RunName, - Short: "\U0001F680 Rocket powered task runner", - Long: "Rocket powered task runner to assist delivering ci build missions", - Args: cobra.NoArgs, - SilenceErrors: true, - Version: info.String(), - }, - ctx: ctx, - } - - cli.rootCmd.SetVersionTemplate(`{{printf "%s:%s\n" .Name .Version}}`) + cli.rootCmd.SetArgs(args) initCmd := &cobra.Command{ Use: "init", @@ -75,7 +67,7 @@ func Run(ctx context.Context) int { Args: cobra.ArbitraryArgs, SilenceErrors: true, SilenceUsage: true, - RunE: cli.runFireCmd, + RunE: cli.runLaunchCmd, } cli.rootCmd.PersistentFlags().StringVar(&cli.configFile, flagConfig, "", @@ -102,6 +94,27 @@ func Run(ctx context.Context) int { return ExitCodeSuccess } +func newCli(ctx context.Context) *cli { + info := buildinfo.GetBuildInfo(ctx) + + cli := &cli{ + appName: info.RunName, + rootCmd: &cobra.Command{ + Use: info.RunName, + Short: "\U0001F680 Rocket powered task runner", + Long: "Rocket powered task runner to assist delivering ci build missions", + Args: cobra.NoArgs, + SilenceErrors: true, + Version: info.String(), + }, + ctx: ctx, + } + + cli.rootCmd.SetVersionTemplate(`{{printf "%s:%s\n" .Name .Version}}`) + + return cli +} + // initConfig is called during the cobra start up process to init the config settings. func (cli *cli) initConfig() { // Switch dir if necessary @@ -112,6 +125,11 @@ func (cli *cli) initConfig() { } } + if cli.appName == "" { + cli.initError = errors.New("No app name") + return + } + // Establish logging isCustomConfig := false viper.SetConfigType("yaml") diff --git a/internal/cmd/cli_test.go b/internal/cmd/cli_test.go new file mode 100644 index 0000000..eb05969 --- /dev/null +++ b/internal/cmd/cli_test.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "context" + "strings" + "testing" + + "github.com/nehemming/cirocket/pkg/buildinfo" +) + +func TestNewCli(t *testing.T) { + ctx := context.Background() + + cli := newCli(ctx) + + if cli.rootCmd == nil { + t.Error("no root cmd") + } + + if cli.ctx != ctx { + t.Error("wrong context") + } +} + +func TestNewCliRootCmd(t *testing.T) { + ctx := buildinfo.NewInfo("1.0", "", "", "", "").NewContext(context.Background()) + + cli := newCli(ctx) + + rootCmd := cli.rootCmd + + if rootCmd.Args == nil { + t.Error("Args") + } + if rootCmd.SilenceErrors == false { + t.Error("SilenceErrors") + } + + if rootCmd.Version == "" { + t.Error("Version") + } +} + +func TestInitConfigBlankAppNameErrors(t *testing.T) { + cli := newCli(context.Background()) + + if cli.initError != nil { + t.Error("pre init error") + } + + cli.appName = "" + + cli.initConfig() + if cli.initError == nil { + t.Error("expected error") + } +} + +func TestInitConfig(t *testing.T) { + cli := newCli(context.Background()) + + if cli.initError != nil { + t.Error("pre init error") + } + + cli.appName = "notknown" + + cli.initConfig() + if cli.initError == nil || !strings.HasPrefix(cli.initError.Error(), "Config File \".notknown.yml\"") { + t.Error("unexpected", cli.initError) + } +} + +func TestRun(t *testing.T) { + ctx := buildinfo.NewInfo("1.0", "", "", "", "").NewContext(context.Background()) + + exitCode := runWithArgs(ctx, []string{"--version"}) + + if exitCode != ExitCodeSuccess { + t.Error("unexpected exit code", exitCode) + } +} diff --git a/internal/cmd/launch.go b/internal/cmd/launch.go index b452dda..c2f1892 100644 --- a/internal/cmd/launch.go +++ b/internal/cmd/launch.go @@ -33,7 +33,7 @@ func parseParams(valueParams []string) ([]rocket.Param, error) { return params, nil } -func (cli *cli) runFireCmd(cmd *cobra.Command, args []string) error { +func (cli *cli) runLaunchCmd(cmd *cobra.Command, args []string) error { // Check that the init process found a config file if cli.initError != nil { return cli.initError diff --git a/internal/cmd/launch_test.go b/internal/cmd/launch_test.go index 4e9118b..73a81f3 100644 --- a/internal/cmd/launch_test.go +++ b/internal/cmd/launch_test.go @@ -51,3 +51,6 @@ func TestParseParamsMultiple(t *testing.T) { t.Error("unexpected value", r[1].Name, r[0].Value) } } + +func TestGetCliParams(t *testing.T) { +} diff --git a/pkg/rocket/builtin/template.go b/pkg/rocket/builtin/template.go index 133bf20..0a108c6 100644 --- a/pkg/rocket/builtin/template.go +++ b/pkg/rocket/builtin/template.go @@ -108,6 +108,7 @@ func loadTemplate(ctx context.Context, capComm *rocket.CapComm, name string, tem } return template.New(name). + Option("missingkey=zero"). Funcs(capComm.FuncMap()). Delims(templateCfg.Delims.Left, templateCfg.Delims.Right).Parse(string(b)) } diff --git a/pkg/rocket/builtin/template_test.go b/pkg/rocket/builtin/template_test.go index 1e3682d..046e0bc 100644 --- a/pkg/rocket/builtin/template_test.go +++ b/pkg/rocket/builtin/template_test.go @@ -29,3 +29,27 @@ func TestTemplateHelloWorld(t *testing.T) { t.Error("failure", err) } } + +func TestTemplateMissingArgIsBalk(t *testing.T) { + loggee.SetLogger(stdlog.New()) + + capComm := rocket.NewCapComm("testdata/test.yml", stdlog.New()) + + ctx := context.Background() + + templateCfg := &Template{ + Template: rocket.InputSpec{ + Path: "{{.notfound}}testdata/hello.yml", + }, + } + + if err := capComm.AttachInputSpec(ctx, templateResourceID, templateCfg.Template); err != nil { + t.Error("unexpected", err) + return + } + + _, err := loadTemplate(ctx, capComm, "test", templateCfg) + if err != nil { + t.Error("unexpected", err) + } +} diff --git a/pkg/rocket/capcomm.go b/pkg/rocket/capcomm.go index 9419c41..6b01a04 100644 --- a/pkg/rocket/capcomm.go +++ b/pkg/rocket/capcomm.go @@ -134,6 +134,11 @@ func getUserName() string { return "unknown" } +// NewCapComm returns a cap comm object that is suitable for using for testing. +func NewCapComm(testConfigFile string, log loggee.Logger) *CapComm { + return newCapCommFromEnvironment(testConfigFile, log).Copy(false) +} + // newCapCommFromEnvironment creates a new capCom from the environment. func newCapCommFromEnvironment(configFile string, log loggee.Logger) *CapComm { paramKvg := NewKeyValueGetter(nil) @@ -556,8 +561,8 @@ func (capComm *CapComm) AttachRedirect(ctx context.Context, redirect Redirection func (capComm *CapComm) MergeParams(ctx context.Context, params []Param) error { capComm.mustNotBeSealed() - // Params need to be expanded prior to merging - expanded := make(map[string]string) + // safe type conversion as KeyValueGetter used all cases except root thats sealed sp not possible to be here + kvg := capComm.params.(*KeyValueGetter) for index, p := range params { if p.Name == "" { @@ -568,13 +573,7 @@ func (capComm *CapComm) MergeParams(ctx context.Context, params []Param) error { if err != nil { return errors.Wrapf(err, "parameter %s", p.Name) } - expanded[p.Name] = v - } - - // safe type conversion as KeyValueGetter used all cases except root thats sealed sp not possible to be here - kvg := capComm.params.(*KeyValueGetter) - for k, v := range expanded { - kvg.kv[k] = v + kvg.kv[p.Name] = v } return nil @@ -627,8 +626,11 @@ func (capComm *CapComm) ExpandString(ctx context.Context, name, value string) (s return "", errors.Wrap(err, "executing template") } + // fix on nil see https://github.com/golang/go/issues/24963 + clean := strings.Replace(buf.String(), "", "", -1) + // Finally expand any environment variables in the $VAR format - return capComm.expandShellEnv(buf.String()), nil + return capComm.expandShellEnv(clean), nil } // FuncMap returns the function mapping used by CapComm. diff --git a/pkg/rocket/capcomm_test.go b/pkg/rocket/capcomm_test.go index fc7c10c..699d643 100644 --- a/pkg/rocket/capcomm_test.go +++ b/pkg/rocket/capcomm_test.go @@ -994,7 +994,7 @@ func TestMergeTemplateEnvs(t *testing.T) { defer os.Unsetenv("TEST_ENV_CAPCOMM") ctx := context.Background() - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(false) + capComm := NewCapComm(testConfigFile, stdlog.New()) envMap := make(EnvMap) @@ -1024,11 +1024,11 @@ func TestFuncMap(t *testing.T) { } } -func TestFGetExecEnvNoOSInherit(t *testing.T) { +func TestGetExecEnvNoOSInherit(t *testing.T) { os.Setenv("TEST_ENV_CAPCOMM", "99") defer os.Unsetenv("TEST_ENV_CAPCOMM") - execEnv := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true).GetExecEnv() + execEnv := NewCapComm(testConfigFile, stdlog.New()).Copy(true).GetExecEnv() if len(execEnv) != 0 { t.Error("execEnv len not 0", len(execEnv)) @@ -1038,6 +1038,7 @@ func TestFGetExecEnvNoOSInherit(t *testing.T) { func TestGetExecEnv(t *testing.T) { envMap := make(EnvMap) envMap["TEST_ENV_CAPCOMM"] = "99" + execEnv := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true).MergeBasicEnvMap(envMap).GetExecEnv() if len(execEnv) != 1 { @@ -1048,7 +1049,7 @@ func TestGetExecEnv(t *testing.T) { } func TestIsFilteredInclude(t *testing.T) { - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) if capComm.isFiltered(nil) != false { t.Error("Nil should not filter") @@ -1070,7 +1071,7 @@ func TestIsFilteredInclude(t *testing.T) { } func TestIsFilteredNotInclude(t *testing.T) { - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) if capComm.isFiltered(nil) != false { t.Error("Nil should not filter") @@ -1089,7 +1090,7 @@ func TestIsFilteredNotInclude(t *testing.T) { } func TestIsFilteredExclude(t *testing.T) { - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) if capComm.isFiltered(nil) != false { t.Error("Nil should not filter") @@ -1109,7 +1110,7 @@ func TestIsFilteredExclude(t *testing.T) { } func TestIsFilteredNotExclude(t *testing.T) { - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) if capComm.isFiltered(nil) != false { t.Error("Nil should not filter") @@ -1140,7 +1141,7 @@ func TestMustNotBeSealed(t *testing.T) { func TestIndentTemplateFunc(t *testing.T) { ctx := context.Background() - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) s, err := capComm.ExpandString(ctx, "spacing", `{{"\nhello"|Indent 6}} `) @@ -1155,7 +1156,7 @@ func TestIndentTemplateFunc(t *testing.T) { func TestIndentFirstLineTemplateFunc(t *testing.T) { ctx := context.Background() - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) s, err := capComm.ExpandString(ctx, "spacing", `{{"hello"|Indent 6}} `) @@ -1169,7 +1170,7 @@ func TestIndentFirstLineTemplateFunc(t *testing.T) { } func TestExportVariable(t *testing.T) { - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) if len(capComm.variables) != 0 { t.Error("pre existing vars", len(capComm.variables)) @@ -1227,7 +1228,7 @@ func TestValidateInputSpecMultiple(t *testing.T) { func TestCreateProviderFromInputSpec(t *testing.T) { ctx := context.Background() - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) capComm.ExportVariable("test_it", "hello") @@ -1267,7 +1268,7 @@ func TestCreateProviderFromInputSpec(t *testing.T) { func TestAttachInputSpec(t *testing.T) { ctx := context.Background() - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) capComm.ExportVariable("test_it", "hello") @@ -1331,7 +1332,7 @@ func TestValidateOutputSpecMultiple(t *testing.T) { func TestCreateProviderFromoutputSpec(t *testing.T) { ctx := context.Background() - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) capComm.ExportVariable("test_it", "hello") defer func() { @@ -1449,7 +1450,7 @@ func TestGetParamFromURLOptionalSuccess(t *testing.T) { func TestExpandParam(t *testing.T) { ctx := context.Background() - capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(true) + capComm := NewCapComm(testConfigFile, stdlog.New()) capComm.ExportVariable("test_it", "hello") for i, r := range []Param{ @@ -1491,3 +1492,41 @@ func TestAttachRedirectLogOutput(t *testing.T) { t.Error("AttachRedirect error", err) } } + +func TestStringExpandNoValueIssue(t *testing.T) { + capComm := newCapCommFromEnvironment(testConfigFile, stdlog.New()).Copy(false) + ctx := context.Background() + + s, err := capComm.ExpandString(ctx, "string", "{{.notfound}}test") + if s != "test" || err != nil { + t.Error("unexpected", s, err) + } +} + +func TestExpandAdjacentMergeParamsWorks(t *testing.T) { + ctx := context.Background() + capComm := NewCapComm(testConfigFile, stdlog.New()) + + params := []Param{ + { + Name: "one", + Value: "here", + }, + { + Name: "two", + Value: "{{.one}}", + Print: true, + }, + } + + err := capComm.MergeParams(ctx, params) + if err != nil { + t.Error("unexpected", err) + } + + v := capComm.params.Get("two") + + if v != "here" { + t.Error("unexpected v", v) + } +}