Skip to content

Commit 438b93b

Browse files
authored
Merge pull request #133 from IdlePhysicist/feat/retry-in-shell
shu retry: Add --in-shell flag
2 parents ff84022 + 8d02b0b commit 438b93b

2 files changed

Lines changed: 53 additions & 4 deletions

File tree

tw/pkg/commands/shu/retry.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@ type retryCfg struct {
1919
Attempts int
2020
Delay time.Duration
2121
Timeout time.Duration
22+
// InBash indicates whether the passed command should be run inside Bash.
23+
InBash bool
2224
}
2325

2426
func retryCommand() *cobra.Command {
2527
cfg := &retryCfg{}
2628

2729
cmd := &cobra.Command{
28-
Use: "retry -- <command>",
29-
Example: ``,
30+
Use: "retry -- <command>",
31+
Example: `
32+
retry -a 5 -- curl http://localhost:8080/healthz
33+
34+
retry -a 5 -b -- "[ $((RANDOM % 5)) -eq 0 ] && exit 0 || exit 10"
35+
`,
3036
RunE: func(cmd *cobra.Command, args []string) error {
3137
return cfg.Run(cmd, args)
3238
},
@@ -35,6 +41,7 @@ func retryCommand() *cobra.Command {
3541
cmd.Flags().IntVarP(&cfg.Attempts, "attempts", "a", 1, "Number of times to retry")
3642
cmd.Flags().DurationVarP(&cfg.Delay, "delay", "d", 1*time.Second, "Delay between attempts")
3743
cmd.Flags().DurationVarP(&cfg.Timeout, "timeout", "t", 5*time.Minute, "Timeout for the command")
44+
cmd.Flags().BoolVarP(&cfg.InBash, "in-bash", "b", false, "Run the passed Bash inside a Bash shell")
3845

3946
return cmd
4047
}
@@ -53,15 +60,15 @@ func (c *retryCfg) Run(cmd *cobra.Command, args []string) error {
5360
defer cancel()
5461

5562
l := clog.FromContext(ctx).With("command", rawcmd)
56-
l.InfoContext(ctx, "args received", "args", args)
63+
l.InfoContext(ctx, "args received", "args", args, "in-bash", c.InBash)
5764

5865
attempt := 0
5966
err := retry.Do(
6067
func() error {
6168
attempt++
6269
l.InfoContextf(ctx, "[%d/%d] attempting command", attempt, c.Attempts)
6370

64-
command := exec.CommandContext(ctx, args[0], args[1:]...)
71+
command := newCommand(ctx, c.InBash, args)
6572
command.Stdout = cmd.OutOrStdout()
6673
command.Stderr = cmd.ErrOrStderr()
6774
command.Env = os.Environ()
@@ -82,3 +89,18 @@ func (c *retryCfg) Run(cmd *cobra.Command, args []string) error {
8289

8390
return err
8491
}
92+
93+
func newCommand(ctx context.Context, inShell bool, args []string) *exec.Cmd {
94+
var c *exec.Cmd
95+
if inShell {
96+
shellArgs := make([]string, 0, len(args)+1)
97+
shellArgs = append(shellArgs, "-c")
98+
shellArgs = append(shellArgs, args...)
99+
100+
c = exec.CommandContext(ctx, "/bin/bash", shellArgs...)
101+
} else {
102+
c = exec.CommandContext(ctx, args[0], args[1:]...)
103+
}
104+
105+
return c
106+
}

tw/pkg/commands/shu/retry_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package shu
2+
3+
import (
4+
"context"
5+
"os/exec"
6+
"syscall"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestNewCommand(t *testing.T) {
13+
shellArgs := []string{"[[ 'x' == 'y' ]] || (echo \"bad\" >&2; exit 10)"} // NOTE: Add `set -x;` at the start to see what's going on.
14+
15+
c := newCommand(context.Background(), true, shellArgs)
16+
17+
err := c.Run()
18+
require.NotNil(t, err) // We're expecting an error here since exit >0
19+
20+
exitErr, ok := err.(*exec.ExitError)
21+
require.True(t, ok)
22+
23+
status, ok := exitErr.Sys().(syscall.WaitStatus)
24+
require.True(t, ok)
25+
code := status.ExitStatus()
26+
require.Equal(t, 10, code)
27+
}

0 commit comments

Comments
 (0)