Add shell executable support
Some checks are pending
CI / test (push) Waiting to run
CI / auto-fix (push) Waiting to run
CI / auto-merge (push) Waiting to run

This commit is contained in:
Virgil 2026-04-03 12:58:03 +00:00
parent 4ccb8fc93b
commit b75ba32cc2
2 changed files with 62 additions and 2 deletions

View file

@ -318,7 +318,7 @@ func (e *Executor) moduleShell(ctx context.Context, client sshExecutorClient, ar
cmd = prefixCommandStdin(cmd, stdin, getBoolArg(args, "stdin_add_newline", true))
}
stdout, stderr, rc, err := client.RunScript(ctx, cmd)
stdout, stderr, rc, err := runShellScriptCommand(ctx, client, cmd, getStringArg(args, "executable", ""))
if err != nil {
return &TaskResult{Failed: true, Msg: err.Error(), Stdout: stdout, Stderr: stderr, RC: rc}, nil
}
@ -508,7 +508,7 @@ func (e *Executor) moduleScript(ctx context.Context, client sshExecutorClient, a
data = sprintf("cd %q && %s", chdir, data)
}
stdout, stderr, rc, err := client.RunScript(ctx, data)
stdout, stderr, rc, err := runShellScriptCommand(ctx, client, data, getStringArg(args, "executable", ""))
if err != nil {
return &TaskResult{Failed: true, Msg: err.Error()}, nil
}
@ -522,6 +522,21 @@ func (e *Executor) moduleScript(ctx context.Context, client sshExecutorClient, a
}, nil
}
// runShellScriptCommand executes a shell script using either the default
// heredoc path or a caller-specified executable.
//
// Example:
//
// stdout, stderr, rc, err := runShellScriptCommand(ctx, client, "echo hi", "/bin/dash")
func runShellScriptCommand(ctx context.Context, client sshExecutorClient, script, executable string) (stdout, stderr string, exitCode int, err error) {
if executable == "" {
return client.RunScript(ctx, script)
}
cmd := sprintf("%s -c %s", shellSingleQuote(executable), shellSingleQuote(script))
return client.Run(ctx, cmd)
}
// --- File Modules ---
func (e *Executor) moduleCopy(ctx context.Context, client sshExecutorClient, args map[string]any, host string, task *Task) (*TaskResult, error) {

View file

@ -415,6 +415,26 @@ func TestModulesCmd_ModuleShell_Good_WithChdir(t *testing.T) {
assert.Contains(t, last.Cmd, "npm install")
}
func TestModulesCmd_ModuleShell_Good_ExecutableUsesRun(t *testing.T) {
e, mock := newTestExecutorWithMock("host1")
mock.expectCommand(`/bin/dash.*echo test`, "test\n", "", 0)
result, err := e.moduleShell(context.Background(), mock, map[string]any{
"_raw_params": "echo test",
"executable": "/bin/dash",
})
require.NoError(t, err)
assert.True(t, result.Changed)
last := mock.lastCommand()
require.NotNil(t, last)
assert.Equal(t, "Run", last.Method)
assert.Contains(t, last.Cmd, "/bin/dash")
assert.Contains(t, last.Cmd, "-c")
assert.Contains(t, last.Cmd, "echo test")
}
func TestModulesCmd_ModuleShell_Bad_NoCommand(t *testing.T) {
e, _ := newTestExecutorWithMock("host1")
mock := NewMockSSHClient()
@ -642,6 +662,31 @@ func TestModulesCmd_ModuleScript_Good_ChdirPrefixesScript(t *testing.T) {
assert.Equal(t, `cd "/opt/app" && pwd`, last.Cmd)
}
func TestModulesCmd_ModuleScript_Good_ExecutableUsesRun(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := joinPath(tmpDir, "dash.sh")
require.NoError(t, writeTestFile(scriptPath, []byte("echo script works"), 0755))
e, mock := newTestExecutorWithMock("host1")
mock.expectCommand(`/bin/dash.*echo script works`, "script works\n", "", 0)
result, err := e.moduleScript(context.Background(), mock, map[string]any{
"_raw_params": scriptPath,
"executable": "/bin/dash",
})
require.NoError(t, err)
require.NotNil(t, result)
assert.True(t, result.Changed)
last := mock.lastCommand()
require.NotNil(t, last)
assert.Equal(t, "Run", last.Method)
assert.Contains(t, last.Cmd, "/bin/dash")
assert.Contains(t, last.Cmd, "-c")
assert.Contains(t, last.Cmd, "echo script works")
}
func TestModulesCmd_ModuleScript_Bad_NoScript(t *testing.T) {
e, _ := newTestExecutorWithMock("host1")
mock := NewMockSSHClient()