feat(ansible): support delegate_to task execution

Co-authored-by: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 20:00:45 +00:00
parent 692c2cf58a
commit eb3b9cca07
5 changed files with 110 additions and 57 deletions

View file

@ -3,6 +3,8 @@ package ansible
import (
"context"
"errors"
"io"
"io/fs"
"regexp"
"slices"
"strconv"
@ -17,6 +19,19 @@ import (
var errEndPlay = errors.New("end play")
// sshExecutorClient is the client contract used by the executor.
type sshExecutorClient interface {
Run(ctx context.Context, cmd string) (stdout, stderr string, exitCode int, err error)
RunScript(ctx context.Context, script string) (stdout, stderr string, exitCode int, err error)
Upload(ctx context.Context, local io.Reader, remote string, mode fs.FileMode) error
Download(ctx context.Context, remote string) ([]byte, error)
FileExists(ctx context.Context, path string) (bool, error)
Stat(ctx context.Context, path string) (map[string]any, error)
BecomeState() (become bool, user, password string)
SetBecome(become bool, user, password string)
Close() error
}
// Executor runs Ansible playbooks.
//
// Example:
@ -30,7 +45,7 @@ type Executor struct {
results map[string]map[string]*TaskResult // host -> register_name -> result
handlers map[string][]Task
notified map[string]bool
clients map[string]*SSHClient
clients map[string]sshExecutorClient
mu sync.RWMutex
// Callbacks
@ -61,7 +76,7 @@ func NewExecutor(basePath string) *Executor {
results: make(map[string]map[string]*TaskResult),
handlers: make(map[string][]Task),
notified: make(map[string]bool),
clients: make(map[string]*SSHClient),
clients: make(map[string]sshExecutorClient),
}
}
@ -472,9 +487,17 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, hosts []strin
}
// Get SSH client
client, err := e.getClient(host, play)
executionHost := host
if task.Delegate != "" {
executionHost = e.templateString(task.Delegate, host, task)
if executionHost == "" {
executionHost = host
}
}
client, err := e.getClient(executionHost, play)
if err != nil {
return coreerr.E("Executor.runTaskOnHost", sprintf("get client for %s", host), err)
return coreerr.E("Executor.runTaskOnHost", sprintf("get client for %s", executionHost), err)
}
// Handle loops
@ -592,7 +615,7 @@ func shouldRetryTask(task *Task, host string, e *Executor, result *TaskResult) b
}
// runLoop handles task loops.
func (e *Executor) runLoop(ctx context.Context, host string, client *SSHClient, task *Task, play *Play) error {
func (e *Executor) runLoop(ctx context.Context, host string, client sshExecutorClient, task *Task, play *Play) error {
items := e.resolveLoop(task.Loop, host)
loopVar := "item"
@ -833,7 +856,7 @@ func (e *Executor) getHosts(pattern string) []string {
}
// getClient returns or creates an SSH client for a host.
func (e *Executor) getClient(host string, play *Play) (*SSHClient, error) {
func (e *Executor) getClient(host string, play *Play) (sshExecutorClient, error) {
e.mu.Lock()
defer e.mu.Unlock()
@ -1555,7 +1578,7 @@ func (e *Executor) Close() {
for _, client := range e.clients {
_ = client.Close()
}
e.clients = make(map[string]*SSHClient)
e.clients = make(map[string]sshExecutorClient)
}
// TemplateFile processes a template file.

View file

@ -207,6 +207,30 @@ func TestExecutor_RunTaskOnHosts_Good_RunOnceSharesRegisteredResult(t *testing.T
assert.Equal(t, "hello", e.results["host2"]["debug_result"].Msg)
}
func TestExecutor_RunTaskOnHost_Good_DelegateToUsesDelegatedClient(t *testing.T) {
e, mock := newTestExecutorWithMock("host1")
e.SetVar("delegate_host", "delegate1")
e.inventory.All.Hosts["delegate1"] = &Host{AnsibleHost: "127.0.0.2"}
e.clients["delegate1"] = mock
mock.expectCommand(`echo delegated`, "delegated", "", 0)
task := &Task{
Name: "Delegate command",
Module: "command",
Args: map[string]any{"cmd": "echo delegated"},
Delegate: "{{ delegate_host }}",
Register: "delegated_result",
}
err := e.runTaskOnHost(context.Background(), "host1", []string{"host1"}, task, &Play{})
require.NoError(t, err)
require.NotNil(t, e.results["host1"]["delegated_result"])
assert.Equal(t, "delegated", e.results["host1"]["delegated_result"].Stdout)
assert.True(t, mock.hasExecuted(`echo delegated`))
assert.Equal(t, 1, mock.commandCount())
}
func TestExecutor_RunPlay_Good_SerialBatchesHosts(t *testing.T) {
e := NewExecutor("/tmp")
e.SetInventoryDirect(&Inventory{

View file

@ -269,6 +269,13 @@ func (m *MockSSHClient) Close() error {
return nil
}
// BecomeState returns the current privilege escalation settings.
func (m *MockSSHClient) BecomeState() (bool, string, string) {
m.mu.Lock()
defer m.mu.Unlock()
return m.become, m.becomeUser, m.becomePass
}
// --- Assertion helpers ---
// executedCommands returns a copy of the execution log.
@ -367,19 +374,12 @@ func (m *MockSSHClient) reset() {
// --- Test helper: create executor with mock client ---
// newTestExecutorWithMock creates an Executor pre-wired with a MockSSHClient
// for the given host. The executor has a minimal inventory so that
// executeModule can be called directly.
// for the given host. The executor has a minimal inventory so that tasks can
// be executed through the normal host/client lookup path.
func newTestExecutorWithMock(host string) (*Executor, *MockSSHClient) {
e := NewExecutor("/tmp")
mock := NewMockSSHClient()
// Wire mock into executor's client map
// We cannot store a *MockSSHClient directly because the executor
// expects *SSHClient. Instead, we provide a helper that calls
// modules the same way the executor does but with the mock.
// Since modules call methods on *SSHClient directly and the mock
// has identical method signatures, we use a shim approach.
// Set up minimal inventory so host resolution works
e.SetInventoryDirect(&Inventory{
All: &InventoryGroup{
@ -388,6 +388,7 @@ func newTestExecutorWithMock(host string) (*Executor, *MockSSHClient) {
},
},
})
e.clients[host] = mock
return e, mock
}

View file

@ -20,15 +20,13 @@ type sshFactsRunner interface {
}
// executeModule dispatches to the appropriate module handler.
func (e *Executor) executeModule(ctx context.Context, host string, client *SSHClient, task *Task, play *Play) (*TaskResult, error) {
func (e *Executor) executeModule(ctx context.Context, host string, client sshExecutorClient, task *Task, play *Play) (*TaskResult, error) {
module := NormalizeModule(task.Module)
// Apply task-level become
if task.Become != nil && *task.Become {
// Save old state to restore
oldBecome := client.become
oldUser := client.becomeUser
oldPass := client.becomePass
oldBecome, oldUser, oldPass := client.BecomeState()
client.SetBecome(true, task.BecomeUser, "")
@ -191,7 +189,7 @@ func (e *Executor) templateArgs(args map[string]any, host string, task *Task) ma
// --- Command Modules ---
func (e *Executor) moduleShell(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleShell(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
cmd := getStringArg(args, "_raw_params", "")
if cmd == "" {
cmd = getStringArg(args, "cmd", "")
@ -219,7 +217,7 @@ func (e *Executor) moduleShell(ctx context.Context, client *SSHClient, args map[
}, nil
}
func (e *Executor) moduleCommand(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleCommand(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
cmd := getStringArg(args, "_raw_params", "")
if cmd == "" {
cmd = getStringArg(args, "cmd", "")
@ -247,7 +245,7 @@ func (e *Executor) moduleCommand(ctx context.Context, client *SSHClient, args ma
}, nil
}
func (e *Executor) moduleRaw(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleRaw(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
cmd := getStringArg(args, "_raw_params", "")
if cmd == "" {
return nil, coreerr.E("Executor.moduleRaw", "no command specified", nil)
@ -266,7 +264,7 @@ func (e *Executor) moduleRaw(ctx context.Context, client *SSHClient, args map[st
}, nil
}
func (e *Executor) moduleScript(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleScript(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
script := getStringArg(args, "_raw_params", "")
if script == "" {
return nil, coreerr.E("Executor.moduleScript", "no script specified", nil)
@ -294,7 +292,7 @@ func (e *Executor) moduleScript(ctx context.Context, client *SSHClient, args map
// --- File Modules ---
func (e *Executor) moduleCopy(ctx context.Context, client *SSHClient, args map[string]any, host string, task *Task) (*TaskResult, error) {
func (e *Executor) moduleCopy(ctx context.Context, client sshExecutorClient, args map[string]any, host string, task *Task) (*TaskResult, error) {
dest := getStringArg(args, "dest", "")
if dest == "" {
return nil, coreerr.E("Executor.moduleCopy", "dest required", nil)
@ -337,7 +335,7 @@ func (e *Executor) moduleCopy(ctx context.Context, client *SSHClient, args map[s
return &TaskResult{Changed: true, Msg: sprintf("copied to %s", dest)}, nil
}
func (e *Executor) moduleTemplate(ctx context.Context, client *SSHClient, args map[string]any, host string, task *Task) (*TaskResult, error) {
func (e *Executor) moduleTemplate(ctx context.Context, client sshExecutorClient, args map[string]any, host string, task *Task) (*TaskResult, error) {
src := getStringArg(args, "src", "")
dest := getStringArg(args, "dest", "")
if src == "" || dest == "" {
@ -365,7 +363,7 @@ func (e *Executor) moduleTemplate(ctx context.Context, client *SSHClient, args m
return &TaskResult{Changed: true, Msg: sprintf("templated to %s", dest)}, nil
}
func (e *Executor) moduleFile(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleFile(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
path := getStringArg(args, "path", "")
if path == "" {
path = getStringArg(args, "dest", "")
@ -433,7 +431,7 @@ func (e *Executor) moduleFile(ctx context.Context, client *SSHClient, args map[s
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleLineinfile(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleLineinfile(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
path := getStringArg(args, "path", "")
if path == "" {
path = getStringArg(args, "dest", "")
@ -491,7 +489,7 @@ func (e *Executor) moduleLineinfile(ctx context.Context, client *SSHClient, args
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleStat(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleStat(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
path := getStringArg(args, "path", "")
if path == "" {
return nil, coreerr.E("Executor.moduleStat", "path required", nil)
@ -508,7 +506,7 @@ func (e *Executor) moduleStat(ctx context.Context, client *SSHClient, args map[s
}, nil
}
func (e *Executor) moduleSlurp(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleSlurp(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
path := getStringArg(args, "path", "")
if path == "" {
path = getStringArg(args, "src", "")
@ -530,7 +528,7 @@ func (e *Executor) moduleSlurp(ctx context.Context, client *SSHClient, args map[
}, nil
}
func (e *Executor) moduleFetch(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleFetch(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
src := getStringArg(args, "src", "")
dest := getStringArg(args, "dest", "")
if src == "" || dest == "" {
@ -554,7 +552,7 @@ func (e *Executor) moduleFetch(ctx context.Context, client *SSHClient, args map[
return &TaskResult{Changed: true, Msg: sprintf("fetched %s to %s", src, dest)}, nil
}
func (e *Executor) moduleGetURL(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleGetURL(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
url := getStringArg(args, "url", "")
dest := getStringArg(args, "dest", "")
if url == "" || dest == "" {
@ -578,7 +576,7 @@ func (e *Executor) moduleGetURL(ctx context.Context, client *SSHClient, args map
// --- Package Modules ---
func (e *Executor) moduleApt(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleApt(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
state := getStringArg(args, "state", "present")
updateCache := getBoolArg(args, "update_cache", false)
@ -612,7 +610,7 @@ func (e *Executor) moduleApt(ctx context.Context, client *SSHClient, args map[st
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleAptKey(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleAptKey(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
url := getStringArg(args, "url", "")
keyring := getStringArg(args, "keyring", "")
state := getStringArg(args, "state", "present")
@ -643,7 +641,7 @@ func (e *Executor) moduleAptKey(ctx context.Context, client *SSHClient, args map
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleAptRepository(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleAptRepository(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
repo := getStringArg(args, "repo", "")
filename := getStringArg(args, "filename", "")
state := getStringArg(args, "state", "present")
@ -680,7 +678,7 @@ func (e *Executor) moduleAptRepository(ctx context.Context, client *SSHClient, a
return &TaskResult{Changed: true}, nil
}
func (e *Executor) modulePackage(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) modulePackage(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
// Detect package manager and delegate
stdout, _, _, _ := client.Run(ctx, "which apt-get yum dnf 2>/dev/null | head -1")
stdout = corexTrimSpace(stdout)
@ -695,15 +693,15 @@ func (e *Executor) modulePackage(ctx context.Context, client *SSHClient, args ma
}
}
func (e *Executor) moduleYum(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleYum(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
return e.moduleRPM(ctx, client, args, "yum")
}
func (e *Executor) moduleDnf(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleDnf(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
return e.moduleRPM(ctx, client, args, "dnf")
}
func (e *Executor) moduleRPM(ctx context.Context, client *SSHClient, args map[string]any, manager string) (*TaskResult, error) {
func (e *Executor) moduleRPM(ctx context.Context, client sshExecutorClient, args map[string]any, manager string) (*TaskResult, error) {
name := getStringArg(args, "name", "")
state := getStringArg(args, "state", "present")
updateCache := getBoolArg(args, "update_cache", false)
@ -744,7 +742,7 @@ func (e *Executor) moduleRPM(ctx context.Context, client *SSHClient, args map[st
return &TaskResult{Changed: true}, nil
}
func (e *Executor) modulePip(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) modulePip(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
state := getStringArg(args, "state", "present")
executable := getStringArg(args, "executable", "pip3")
@ -769,7 +767,7 @@ func (e *Executor) modulePip(ctx context.Context, client *SSHClient, args map[st
// --- Service Modules ---
func (e *Executor) moduleService(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleService(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
state := getStringArg(args, "state", "")
enabled := args["enabled"]
@ -811,7 +809,7 @@ func (e *Executor) moduleService(ctx context.Context, client *SSHClient, args ma
return &TaskResult{Changed: len(cmds) > 0}, nil
}
func (e *Executor) moduleSystemd(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleSystemd(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
// systemd is similar to service
if getBoolArg(args, "daemon_reload", false) {
_, _, _, _ = client.Run(ctx, "systemctl daemon-reload")
@ -822,7 +820,7 @@ func (e *Executor) moduleSystemd(ctx context.Context, client *SSHClient, args ma
// --- User/Group Modules ---
func (e *Executor) moduleUser(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleUser(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
state := getStringArg(args, "state", "present")
@ -879,7 +877,7 @@ func (e *Executor) moduleUser(ctx context.Context, client *SSHClient, args map[s
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleGroup(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleGroup(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
state := getStringArg(args, "state", "present")
@ -914,7 +912,7 @@ func (e *Executor) moduleGroup(ctx context.Context, client *SSHClient, args map[
// --- HTTP Module ---
func (e *Executor) moduleURI(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleURI(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
url := getStringArg(args, "url", "")
method := getStringArg(args, "method", "GET")
@ -1297,7 +1295,7 @@ func findInventoryHost(group *InventoryGroup, name string) *Host {
return nil
}
func (e *Executor) moduleWaitFor(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleWaitFor(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
port := 0
if p, ok := args["port"].(int); ok {
port = p
@ -1321,7 +1319,7 @@ func (e *Executor) moduleWaitFor(ctx context.Context, client *SSHClient, args ma
return &TaskResult{Changed: false}, nil
}
func (e *Executor) moduleGit(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleGit(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
repo := getStringArg(args, "repo", "")
dest := getStringArg(args, "dest", "")
version := getStringArg(args, "version", "HEAD")
@ -1350,7 +1348,7 @@ func (e *Executor) moduleGit(ctx context.Context, client *SSHClient, args map[st
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleUnarchive(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleUnarchive(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
src := getStringArg(args, "src", "")
dest := getStringArg(args, "dest", "")
remote := getBoolArg(args, "remote_src", false)
@ -1401,7 +1399,7 @@ func (e *Executor) moduleUnarchive(ctx context.Context, client *SSHClient, args
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleArchive(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleArchive(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
dest := getStringArg(args, "dest", "")
format := lower(getStringArg(args, "format", ""))
paths := archivePaths(args)
@ -1517,7 +1515,7 @@ func getBoolArg(args map[string]any, key string, def bool) bool {
// --- Additional Modules ---
func (e *Executor) moduleHostname(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleHostname(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
if name == "" {
return nil, coreerr.E("Executor.moduleHostname", "name required", nil)
@ -1536,7 +1534,7 @@ func (e *Executor) moduleHostname(ctx context.Context, client *SSHClient, args m
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleSysctl(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleSysctl(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
value := getStringArg(args, "value", "")
state := getStringArg(args, "state", "present")
@ -1569,7 +1567,7 @@ func (e *Executor) moduleSysctl(ctx context.Context, client *SSHClient, args map
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleCron(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleCron(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
job := getStringArg(args, "job", "")
state := getStringArg(args, "state", "present")
@ -1606,7 +1604,7 @@ func (e *Executor) moduleCron(ctx context.Context, client *SSHClient, args map[s
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleBlockinfile(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleBlockinfile(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
path := getStringArg(args, "path", "")
if path == "" {
path = getStringArg(args, "dest", "")
@ -1897,7 +1895,7 @@ func osFamilyFromReleaseID(id string) string {
}
}
func (e *Executor) moduleReboot(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleReboot(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
preRebootDelay := 0
if d, ok := args["pre_reboot_delay"].(int); ok {
preRebootDelay = d
@ -1915,7 +1913,7 @@ func (e *Executor) moduleReboot(ctx context.Context, client *SSHClient, args map
return &TaskResult{Changed: true, Msg: "Reboot initiated"}, nil
}
func (e *Executor) moduleUFW(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleUFW(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
rule := getStringArg(args, "rule", "")
port := getStringArg(args, "port", "")
proto := getStringArg(args, "proto", "tcp")
@ -1966,7 +1964,7 @@ func (e *Executor) moduleUFW(ctx context.Context, client *SSHClient, args map[st
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleAuthorizedKey(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleAuthorizedKey(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
user := getStringArg(args, "user", "")
key := getStringArg(args, "key", "")
state := getStringArg(args, "state", "present")
@ -2017,7 +2015,7 @@ func (e *Executor) moduleAuthorizedKey(ctx context.Context, client *SSHClient, a
return &TaskResult{Changed: true}, nil
}
func (e *Executor) moduleDockerCompose(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleDockerCompose(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) {
projectSrc := getStringArg(args, "project_src", "")
state := getStringArg(args, "state", "present")

7
ssh.go
View file

@ -212,6 +212,13 @@ func (c *SSHClient) Close() error {
return nil
}
// BecomeState returns the current privilege escalation settings.
func (c *SSHClient) BecomeState() (bool, string, string) {
c.mu.Lock()
defer c.mu.Unlock()
return c.become, c.becomeUser, c.becomePass
}
// Run executes a command on the remote host.
//
// Example: