package builtin import ( "context" "fmt" "github.com/vladimir/goherence/internal/module" "github.com/vladimir/goherence/internal/ssh" ) // ShellModule — единственный по-настоящему императивный builtin-модуль. // Его использование намеренно ограничивается линтером (internal/lint) — // сам модуль здесь просто честно исполняет команду, никакой магии. type ShellModule struct{} func (m *ShellModule) Name() string { return "shell" } func (m *ShellModule) Run(ctx context.Context, in module.Input, conn *ssh.Conn) (module.Result, error) { cmd, ok := in.Args["cmd"].(string) if !ok { return module.Result{}, fmt.Errorf("shell: аргумент cmd обязателен и должен быть строкой") } if in.CheckMode { return module.Result{ SchemaVersion: module.SchemaVersion, Changed: true, // shell не умеет предсказывать идемпотентность — check-mode всегда "would change" Msg: "check mode: команда не выполнена: " + cmd, }, nil } res, err := conn.Run(cmd) if err != nil { return module.Result{}, err } return module.Result{ SchemaVersion: module.SchemaVersion, Changed: true, // shell не имеет понятия состояния — считается изменяющим всегда Failed: res.ExitCode != 0, Msg: res.Stdout, Diff: map[string]string{ "stderr": res.Stderr, "exit_code": fmt.Sprintf("%d", res.ExitCode), }, }, nil } // CommandModule — то же самое, что shell, но семантически "без shell-обвязки" // (в реальном Ansible command не проходит через /bin/sh, что важно для // экранирования). Для первой версии реализация идентична shell — // разница добавляется отдельно, когда понадобится exec без shell-интерпретации. type CommandModule struct{ ShellModule } func (m *CommandModule) Name() string { return "command" }