Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Echo command #96

Merged
merged 3 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ Spot supports the following command types:
- `sync`: syncs directory from the local machine to the remote host(s). Optionally supports deleting files on the remote host(s) that don't exist locally. Example: `sync: {"src": "testdata", "dst": "/tmp/things", "delete": true}`
- `delete`: deletes a file or directory on the remote host(s), optionally can remove recursively. Example: `delete: {"path": "/tmp/things", "recur": true}`
- `wait`: waits for the specified command to finish on the remote host(s) with 0 error code. This command is useful when you need to wait for a service to start before executing the next command. Allows to specify the timeout as well as check interval. Example: `wait: {"cmd": "curl -s --fail localhost:8080", "timeout": "30s", "interval": "1s"}`
- `echo`: prints the specified message to the console. Example: `echo: "Hello World $some_var"`. This command is useful for debugging purposes and also to print the value of variables to the console.

### Command options

Expand Down
2 changes: 2 additions & 0 deletions pkg/config/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Cmd struct {
Delete DeleteInternal `yaml:"delete" toml:"delete"`
Wait WaitInternal `yaml:"wait" toml:"wait"`
Script string `yaml:"script" toml:"script,multiline"`
Echo string `yaml:"echo" toml:"echo"`
Environment map[string]string `yaml:"env" toml:"env"`
Options CmdOptions `yaml:"options" toml:"options,omitempty"`
Condition string `yaml:"cond" toml:"cond,omitempty"`
Expand Down Expand Up @@ -330,6 +331,7 @@ func (cmd *Cmd) validate() error {
{"delete", func() bool { return cmd.Delete.Location != "" }},
{"sync", func() bool { return cmd.Sync.Source != "" && cmd.Sync.Dest != "" }},
{"wait", func() bool { return cmd.Wait.Command != "" }},
{"echo", func() bool { return cmd.Echo != "" }},
}

setCmds, names := []string{}, []string{}
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ func TestCmd_validate(t *testing.T) {
{"only wait", Cmd{Wait: WaitInternal{Command: "command"}}, ""},
{"multiple fields set", Cmd{Script: "example_script", Copy: CopyInternal{Source: "source", Dest: "dest"}},
"only one of [script, copy] is allowed"},
{"nothing set", Cmd{}, "one of [script, copy, mcopy, delete, sync, wait] must be set"},
{"nothing set", Cmd{}, "one of [script, copy, mcopy, delete, sync, wait, echo] must be set"},
}

for _, tt := range tbl {
Expand Down
19 changes: 19 additions & 0 deletions pkg/runner/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,25 @@ func (ec *execCmd) Wait(ctx context.Context) (details string, vars map[string]st
}
}

// Echo prints a message. It enforces the echo command to start with "echo " and adds sudo if needed.
// It returns the result of the echo command as details string.
func (ec *execCmd) Echo(ctx context.Context) (details string, vars map[string]string, err error) {
tmpl := templater{hostAddr: ec.hostAddr, hostName: ec.hostName, task: ec.tsk, command: ec.cmd.Name, env: ec.cmd.Environment}
echoCmd := tmpl.apply(ec.cmd.Echo)
if !strings.HasPrefix(echoCmd, "echo ") {
echoCmd = fmt.Sprintf("echo %s", echoCmd)
}
if ec.cmd.Options.Sudo {
echoCmd = fmt.Sprintf("sudo %s", echoCmd)
}
out, err := ec.exec.Run(ctx, echoCmd, false)
if err != nil {
return "", nil, fmt.Errorf("can't run echo command on %s: %w", ec.hostAddr, err)
}
details = fmt.Sprintf(" {echo: %s}", strings.Join(out, "; "))
return details, nil, nil
}

func (ec *execCmd) checkCondition(ctx context.Context) (bool, error) {
if ec.cmd.Condition == "" {
return true, nil // no condition, always allow
Expand Down
17 changes: 17 additions & 0 deletions pkg/runner/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,21 @@ func Test_execCmd(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, " {skip: test}", details)
})

t.Run("echo command", func(t *testing.T) {
ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Echo: "welcome back", Name: "test"}}
details, _, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", details)

ec = execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Echo: "echo welcome back", Name: "test"}}
details, _, err = ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", details)

ec = execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Echo: "$var1 welcome back", Name: "test", Environment: map[string]string{"var1": "foo"}}}
details, _, err = ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: foo welcome back}", details)
})
}
3 changes: 3 additions & 0 deletions pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ func (p *Process) execCommand(ctx context.Context, ec execCmd) (details string,
case ec.cmd.Wait.Command != "":
log.Printf("[DEBUG] wait for command on %s", ec.hostAddr)
return ec.Wait(ctx)
case ec.cmd.Echo != "":
log.Printf("[DEBUG] echo on %s", ec.hostAddr)
return ec.Echo(ctx)
default:
return "", nil, fmt.Errorf("unknown command %q", ec.cmd.Name)
}
Expand Down
20 changes: 20 additions & 0 deletions pkg/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,26 @@ func TestProcess_Run(t *testing.T) {
assert.Contains(t, outWriter.String(), `uploaded testdata/conf.yml to localhost:/tmp/.spot/conf.yml`)
})

t.Run("echo with variables", func(t *testing.T) {
conf, err := config.New("testdata/conf.yml", nil, nil)
require.NoError(t, err)

outWriter := &bytes.Buffer{}
wr := io.MultiWriter(outWriter, os.Stderr)
p := Process{
Concurrency: 1,
Connector: connector,
Config: conf,
ColorWriter: executor.NewColorizedWriter(wr, "", "", "", nil),
Only: []string{"copy configuration", "some command", "echo things"},
Verbose: true,
}
log.SetOutput(io.Discard)
res, err := p.Run(ctx, "task1", testingHostAndPort)
require.NoError(t, err)
assert.Equal(t, 3, res.Commands)
assert.Contains(t, outWriter.String(), `completed command "echo things" {echo: vars - 6, 9, zzzzz}`)
})
}

func TestProcess_RunWithSudo(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/runner/testdata/conf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ tasks:
copy: {src: "${filename}", dst: "/srv/conf.yml"}
options: {no_auto: true, sudo: true}

- name: echo things
echo: vars - $foo, $bar, $baz
options: {no_auto: true}

- name: failed_task
commands:
- name: good command
Expand Down