Skip to content

Commit

Permalink
fix echo with sudo
Browse files Browse the repository at this point in the history
before we expanded wars too late and usage of sudo with echo command expansion, like $(id) didn't work under sudo
  • Loading branch information
umputun committed Dec 20, 2024
1 parent 1cf9c8b commit c0c8057
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 86 deletions.
2 changes: 1 addition & 1 deletion pkg/runner/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func (ec *execCmd) Echo(ctx context.Context) (resp execCmdResp, err error) {
echoCmd = fmt.Sprintf("echo %q", echoCmd)
}
if ec.cmd.Options.Sudo {
echoCmd = fmt.Sprintf("sudo %q", echoCmd)
echoCmd = fmt.Sprintf("sudo %s -c '%s'", ec.shell(), echoCmd)
}
out, err := ec.exec.Run(ctx, echoCmd, nil)
if err != nil {
Expand Down
211 changes: 128 additions & 83 deletions pkg/runner/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,89 +330,6 @@ func Test_execCmd(t *testing.T) {
assert.Equal(t, " {skip: test}", resp.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"}}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", resp.details)

ec = execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Echo: "echo welcome back", Name: "test"}}
resp, err = ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", resp.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"}}}
resp, err = ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: foo welcome back}", resp.details)
})

t.Run("echo command with condition true", func(t *testing.T) {
defer os.Remove("/tmp/test.condition")
_, err := sess.Run(ctx, "touch /tmp/test.condition", nil)
require.NoError(t, err)
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "welcome back",
Name: "test",
Condition: "test -f /tmp/test.condition",
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", resp.details)
})

t.Run("echo command with condition false", func(t *testing.T) {
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "welcome back",
Name: "test",
Condition: "test -f /tmp/nonexistent.condition",
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {skip: test}", resp.details)
})

t.Run("echo command with condition true inverted", func(t *testing.T) {
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "welcome back",
Name: "test",
Condition: "! test -f /tmp/nonexistent.condition",
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", resp.details)
})

t.Run("echo command with condition false inverted", func(t *testing.T) {
defer os.Remove("/tmp/test2.condition")
_, err := sess.Run(ctx, "touch /tmp/test2.condition", nil)
require.NoError(t, err)
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "welcome back",
Name: "test",
Condition: "! test -f /tmp/test2.condition",
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {skip: test}", resp.details)
})

t.Run("sync command", func(t *testing.T) {
ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Sync: config.SyncInternal{
Source: "testdata", Dest: "/tmp/sync.testdata", Exclude: []string{"conf2.yml"}}, Name: "test"}}
Expand Down Expand Up @@ -516,6 +433,134 @@ func Test_execCmd(t *testing.T) {
})
}

func Test_execEcho(t *testing.T) {
testingHostAndPort, teardown := startTestContainer(t)
defer teardown()
logs := executor.MakeLogs(false, false, nil)
ctx := context.Background()
connector, connErr := executor.NewConnector("testdata/test_ssh_key", time.Second*10, logs)
require.NoError(t, connErr)
sess, errSess := connector.Connect(ctx, testingHostAndPort, "my-hostAddr", "test")
require.NoError(t, errSess)

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"}}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", resp.details)

ec = execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Echo: "echo welcome back", Name: "test"}}
resp, err = ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", resp.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"}}}
resp, err = ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: foo welcome back}", resp.details)
})

t.Run("echo command with condition true", func(t *testing.T) {
defer os.Remove("/tmp/test.condition")
_, err := sess.Run(ctx, "touch /tmp/test.condition", nil)
require.NoError(t, err)
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "welcome back",
Name: "test",
Condition: "test -f /tmp/test.condition",
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", resp.details)
})

t.Run("echo command with condition false", func(t *testing.T) {
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "welcome back",
Name: "test",
Condition: "test -f /tmp/nonexistent.condition",
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {skip: test}", resp.details)
})

t.Run("echo command with condition true inverted", func(t *testing.T) {
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "welcome back",
Name: "test",
Condition: "! test -f /tmp/nonexistent.condition",
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: welcome back}", resp.details)
})

t.Run("echo command with condition false inverted", func(t *testing.T) {
defer os.Remove("/tmp/test2.condition")
_, err := sess.Run(ctx, "touch /tmp/test2.condition", nil)
require.NoError(t, err)
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "welcome back",
Name: "test",
Condition: "! test -f /tmp/test2.condition",
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {skip: test}", resp.details)
})

t.Run("echo command with sudo", func(t *testing.T) {
ec := execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "hello from sudo",
Name: "test",
Options: config.CmdOptions{
Sudo: true,
},
},
}
resp, err := ec.Echo(ctx)
require.NoError(t, err)
assert.Equal(t, " {echo: hello from sudo}", resp.details)

ec = execCmd{
exec: sess,
tsk: &config.Task{Name: "test"},
cmd: config.Cmd{
Echo: "$(id)",
Name: "test",
},
}
resp, err = ec.Echo(ctx)
require.NoError(t, err)
assert.Contains(t, resp.details, "uid=911(test)")

ec.cmd.Options.Sudo = true
resp, err = ec.Echo(ctx)
require.NoError(t, err)
assert.Contains(t, resp.details, "uid=0(root)")
})
}

func Test_execCmdWithTmp(t *testing.T) {
testingHostAndPort, teardown := startTestContainer(t)
defer teardown()
Expand Down
34 changes: 32 additions & 2 deletions pkg/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func TestProcess_Run(t *testing.T) {
assert.Contains(t, outWriter.String(), `/conf.yml in`)
})

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

Expand All @@ -283,7 +283,7 @@ func TestProcess_Run(t *testing.T) {
assert.Contains(t, outWriter.String(), `completed command "echo things" {echo: vars - 6, 9, zzzzz}`)
})

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

Expand All @@ -310,6 +310,36 @@ func TestProcess_Run(t *testing.T) {
t.Log("out:\n", outWriter.String())
assert.Contains(t, outWriter.String(), `completed command "echo things" {echo: vars - 6, 9, zzzzz}`)
})

t.Run("echo with variables and sudo, verbose", 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)
lgs := executor.MakeLogs(true, false, conf.AllSecretValues())
lgs.Info = lgs.Info.WithWriter(wr)

conn, err := executor.NewConnector("testdata/test_ssh_key", time.Second*10, lgs)
require.NoError(t, err)

p := Process{
Concurrency: 1,
Connector: conn,
Playbook: conf,
Logs: lgs,
Only: []string{"copy configuration", "some command", "echo things sudo"},
Verbose: true,
}
log.SetOutput(io.Discard)
res, err := p.Run(ctx, "task1", testingHostAndPort)
require.NoError(t, err)
assert.Equal(t, 3, res.Commands)
t.Log("out:\n", outWriter.String())
assert.Contains(t, outWriter.String(), `completed command "echo things sudo" {echo: vars - 6, 9, zzzzz`)
assert.Contains(t, outWriter.String(), `uid=0(root) gid=0(root)`)
})

t.Run("delete multiple files", func(t *testing.T) {
conf, err := config.New("testdata/conf.yml", nil, nil)
require.NoError(t, err)
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 @@ -108,6 +108,10 @@ tasks:
echo: vars - $foo, $bar, $baz
options: {no_auto: true}

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

- name: prep multiple files for delete
copy:
- {src: testdata/conf.yml, dst: /tmp/deleteme.1}
Expand Down

0 comments on commit c0c8057

Please sign in to comment.