diff --git a/cmd/ansible/specs/RFC.md b/cmd/ansible/specs/RFC.md new file mode 100644 index 0000000..c32201a --- /dev/null +++ b/cmd/ansible/specs/RFC.md @@ -0,0 +1,15 @@ +# anscmd +**Import:** `dappco.re/go/core/ansible/cmd/ansible` +**Files:** 3 + +## Types +This package has no exported structs, interfaces, or type aliases. + +## Functions +### Register +`func Register(c *core.Core)` + +Registers two CLI commands on `c`: + +- `ansible`: Runs a playbook through `ansible.Executor`. The command exposes `inventory`, `limit`, `tags`, `skip-tags`, `extra-vars`, `verbose`, and `check` flags. +- `ansible/test`: Opens an SSH connection to a host, prints basic host facts, and exposes `user`, `password`, `key`, and `port` flags. diff --git a/specs/RFC.md b/specs/RFC.md new file mode 100644 index 0000000..1aeec15 --- /dev/null +++ b/specs/RFC.md @@ -0,0 +1,364 @@ +# ansible +**Import:** `dappco.re/go/core/ansible` +**Files:** 6 + +## Types +This package exports structs only. It has no exported interfaces or type aliases. + +### Playbook +`type Playbook struct` + +Top-level playbook wrapper for YAML documents that decode to an inline list of plays. + +Fields: +- `Plays []Play`: Inlined play definitions in declaration order. + +### Play +`type Play struct` + +One play in a playbook, including host targeting, privilege settings, vars, task lists, roles, handlers, and run controls. + +Fields: +- `Name string`: Human-readable play name used in output and callbacks. +- `Hosts string`: Inventory pattern resolved before the play runs. +- `Connection string`: Optional connection type; `"local"` skips SSH fact gathering. +- `Become bool`: Enables privilege escalation for tasks in the play. +- `BecomeUser string`: Override user for privilege escalation. +- `GatherFacts *bool`: Optional fact-gathering switch; `nil` means facts are gathered. +- `Vars map[string]any`: Play-scoped variables merged into parser and executor state. +- `PreTasks []Task`: Tasks run before roles and main tasks. +- `Tasks []Task`: Main task list. +- `PostTasks []Task`: Tasks run after the main task list. +- `Roles []RoleRef`: Role references executed between `PreTasks` and `Tasks`. +- `Handlers []Task`: Handler tasks that may run after normal tasks when notified. +- `Tags []string`: Tags attached to the play. +- `Environment map[string]string`: Environment variables attached to the play. +- `Serial any`: Serial batch setting; the YAML accepts either an `int` or a `string`. +- `MaxFailPercent int`: Maximum tolerated failure percentage for the play. + +### RoleRef +`type RoleRef struct` + +Role entry used in `Play.Roles`. The YAML may be either a scalar role name or a mapping. + +Fields: +- `Role string`: Canonical role name used by the executor. +- `Name string`: Alternate YAML field that is normalised into `Role` during unmarshalling. +- `TasksFrom string`: Task file loaded from the role's `tasks/` directory. +- `Vars map[string]any`: Variables merged while the role runs. +- `When any`: Optional condition evaluated before the role runs. +- `Tags []string`: Tags declared on the role reference. + +### Task +`type Task struct` + +Single Ansible task, including the selected module, module args, flow-control settings, includes, blocks, notifications, and privilege settings. + +Fields: +- `Name string`: Optional task name. +- `Module string`: Module name extracted from the YAML key rather than a fixed field. +- `Args map[string]any`: Module arguments extracted from the YAML value. +- `Register string`: Variable name used to store the task result. +- `When any`: Conditional expression or expression list. +- `Loop any`: Loop source, typically a slice or templated string. +- `LoopControl *LoopControl`: Optional loop metadata such as custom variable names. +- `Vars map[string]any`: Task-local variables. +- `Environment map[string]string`: Task-local environment overrides. +- `ChangedWhen any`: Override for changed-state evaluation. +- `FailedWhen any`: Override for failure evaluation. +- `IgnoreErrors bool`: Continue after task failure when true. +- `NoLog bool`: Marker for suppressing logging. +- `Become *bool`: Per-task privilege-escalation override. +- `BecomeUser string`: Per-task privilege-escalation user. +- `Delegate string`: `delegate_to` target host. +- `RunOnce bool`: Runs the task once rather than on every host. +- `Tags []string`: Tags attached to the task. +- `Block []Task`: Main block tasks for `block` syntax. +- `Rescue []Task`: Rescue tasks run after a block failure. +- `Always []Task`: Tasks that always run after a block. +- `Notify any`: Handler notification target, either a string or a list. +- `Retries int`: Retry count for `until` loops. +- `Delay int`: Delay between retries. +- `Until string`: Condition checked for retry loops. +- `IncludeTasks string`: Path used by `include_tasks`. +- `ImportTasks string`: Path used by `import_tasks`. +- `IncludeRole *struct{...}`: Role inclusion payload with `Name`, optional `TasksFrom`, and optional `Vars`. +- `ImportRole *struct{...}`: Role import payload with `Name`, optional `TasksFrom`, and optional `Vars`. + +### LoopControl +`type LoopControl struct` + +Loop metadata attached to a task. + +Fields: +- `LoopVar string`: Name assigned to the current loop item. +- `IndexVar string`: Name assigned to the current loop index. +- `Label string`: Display label for loop items. +- `Pause int`: Pause between loop iterations. +- `Extended bool`: Enables extended loop metadata. + +### TaskResult +`type TaskResult struct` + +Normalised execution result returned by module handlers and stored in registered variables. + +Fields: +- `Changed bool`: Whether the task changed remote state. +- `Failed bool`: Whether the task failed. +- `Skipped bool`: Whether the task was skipped. +- `Msg string`: Summary message. +- `Stdout string`: Standard output captured from command-based modules. +- `Stderr string`: Standard error captured from command-based modules. +- `RC int`: Command exit status when applicable. +- `Results []TaskResult`: Per-item loop results. +- `Data map[string]any`: Module-specific result payload. +- `Duration time.Duration`: Execution duration recorded by the executor. + +### Inventory +`type Inventory struct` + +Root inventory object. + +Fields: +- `All *InventoryGroup`: Root inventory group decoded from the `all` key. + +### InventoryGroup +`type InventoryGroup struct` + +Inventory group containing hosts, child groups, and inherited variables. + +Fields: +- `Hosts map[string]*Host`: Hosts defined directly in the group. +- `Children map[string]*InventoryGroup`: Nested child groups. +- `Vars map[string]any`: Variables inherited by descendant hosts unless overridden. + +### Host +`type Host struct` + +Per-host inventory entry with Ansible connection settings and inline custom vars. + +Fields: +- `AnsibleHost string`: Remote address or hostname to connect to. +- `AnsiblePort int`: SSH port. +- `AnsibleUser string`: SSH user. +- `AnsiblePassword string`: SSH password. +- `AnsibleSSHPrivateKeyFile string`: Private key path for SSH authentication. +- `AnsibleConnection string`: Connection transport hint. +- `AnsibleBecomePassword string`: Password used for privilege escalation. +- `Vars map[string]any`: Additional host variables stored inline in YAML. + +### Facts +`type Facts struct` + +Subset of gathered host facts stored by the executor. + +Fields: +- `Hostname string`: Short hostname. +- `FQDN string`: Fully qualified domain name. +- `OS string`: OS family. +- `Distribution string`: Distribution identifier. +- `Version string`: Distribution version. +- `Architecture string`: Machine architecture. +- `Kernel string`: Kernel release. +- `Memory int64`: Total memory in MiB. +- `CPUs int`: Virtual CPU count. +- `IPv4 string`: Default IPv4 address. + +### Parser +`type Parser struct` + +Stateful YAML parser for playbooks, inventories, task files, and roles. Its internal path and variable cache are unexported. + +### Executor +`type Executor struct` + +Playbook execution engine that combines parser state, inventory, vars, gathered facts, registered results, handler notifications, and SSH client reuse. + +Fields: +- `OnPlayStart func(play *Play)`: Optional callback fired before a play starts. +- `OnTaskStart func(host string, task *Task)`: Optional callback fired before a task runs on a host. +- `OnTaskEnd func(host string, task *Task, result *TaskResult)`: Optional callback fired after a task result is produced. +- `OnPlayEnd func(play *Play)`: Optional callback fired after a play finishes. +- `Limit string`: Additional host filter applied after normal play host resolution. +- `Tags []string`: Inclusive tag filter for task execution. +- `SkipTags []string`: Exclusive tag filter that always skips matching tasks. +- `CheckMode bool`: Public execution flag exposed for callers and CLI wiring. +- `Diff bool`: Public execution flag exposed for callers and CLI wiring. +- `Verbose int`: Verbosity level used by executor logging and CLI callbacks. + +### SSHClient +`type SSHClient struct` + +Lazy SSH client that owns connection, authentication, privilege-escalation, and timeout state. All fields are unexported. + +### SSHConfig +`type SSHConfig struct` + +Configuration used to construct an `SSHClient`. + +Fields: +- `Host string`: Target host. +- `Port int`: Target SSH port; defaults to `22`. +- `User string`: SSH user; defaults to `"root"`. +- `Password string`: SSH password. +- `KeyFile string`: Private key path. +- `Become bool`: Enables privilege escalation on the client. +- `BecomeUser string`: User used for privilege escalation. +- `BecomePass string`: Password used for privilege escalation. +- `Timeout time.Duration`: Connection timeout; defaults to `30 * time.Second`. + +## Functions +### NewParser +`func NewParser(basePath string) *Parser` + +Constructs a parser rooted at `basePath` and initialises its internal variable map. `basePath` is later used to resolve role search paths. + +### (*Parser).ParsePlaybook +`func (p *Parser) ParsePlaybook(path string) ([]Play, error)` + +Reads a playbook YAML file, unmarshals it into `[]Play`, and post-processes every `PreTasks`, `Tasks`, `PostTasks`, and handler entry to extract `Task.Module` and `Task.Args`. + +### (*Parser).ParsePlaybookIter +`func (p *Parser) ParsePlaybookIter(path string) (iter.Seq[Play], error)` + +Wrapper around `ParsePlaybook` that yields parsed plays through an `iter.Seq`. + +### (*Parser).ParseInventory +`func (p *Parser) ParseInventory(path string) (*Inventory, error)` + +Reads an inventory YAML file and unmarshals it into the public `Inventory` model. + +### (*Parser).ParseTasks +`func (p *Parser) ParseTasks(path string) ([]Task, error)` + +Reads a task file, unmarshals it into `[]Task`, and extracts module names and args for every task entry. + +### (*Parser).ParseTasksIter +`func (p *Parser) ParseTasksIter(path string) (iter.Seq[Task], error)` + +Wrapper around `ParseTasks` that yields parsed tasks through an `iter.Seq`. + +### (*Parser).ParseRole +`func (p *Parser) ParseRole(name string, tasksFrom string) ([]Task, error)` + +Resolves `roles//tasks/` across several search patterns rooted around `basePath`, defaults `tasksFrom` to `main.yml`, merges role defaults without overwriting existing parser vars, merges role vars with overwrite semantics, and then parses the resolved task file. + +### (*RoleRef).UnmarshalYAML +`func (r *RoleRef) UnmarshalYAML(unmarshal func(any) error) error` + +Accepts either a scalar role name or a structured role mapping. When the mapping only sets `Name`, the method copies it into `Role`. + +### (*Task).UnmarshalYAML +`func (t *Task) UnmarshalYAML(node *yaml.Node) error` + +Decodes the standard task fields, scans the remaining YAML keys for the first recognised module name, stores free-form arguments in `Args["_raw_params"]`, accepts module mappings and nil-valued modules, and maps `with_items` into `Loop` when `Loop` is unset. + +### NormalizeModule +`func NormalizeModule(name string) string` + +Returns `ansible.builtin.` for short module names and leaves dotted names unchanged. + +### GetHosts +`func GetHosts(inv *Inventory, pattern string) []string` + +Resolves hosts from a non-nil inventory by handling `all`, `localhost`, group names, and explicit host names. Patterns containing `:` are recognised as future work and currently return `nil`. + +### GetHostsIter +`func GetHostsIter(inv *Inventory, pattern string) iter.Seq[string]` + +Iterator wrapper around `GetHosts`. + +### AllHostsIter +`func AllHostsIter(group *InventoryGroup) iter.Seq[string]` + +Yields every host reachable from a group tree in deterministic order by sorting host keys and child-group keys at each level. + +### GetHostVars +`func GetHostVars(inv *Inventory, hostname string) map[string]any` + +Builds the effective variable map for `hostname` by walking the group tree, applying direct-group vars, host connection settings, inline host vars, and then parent-group vars for keys not already set by a nearer scope. + +### NewExecutor +`func NewExecutor(basePath string) *Executor` + +Constructs an executor with a parser rooted at `basePath` and fresh maps for vars, facts, registered results, handlers, notifications, and SSH clients. + +### (*Executor).SetInventory +`func (e *Executor) SetInventory(path string) error` + +Parses an inventory file through the embedded parser and stores the resulting `Inventory` on the executor. + +### (*Executor).SetInventoryDirect +`func (e *Executor) SetInventoryDirect(inv *Inventory)` + +Stores a caller-supplied inventory pointer on the executor without parsing. + +### (*Executor).SetVar +`func (e *Executor) SetVar(key string, value any)` + +Stores an executor-scoped variable under a write lock. + +### (*Executor).Run +`func (e *Executor) Run(ctx context.Context, playbookPath string) error` + +Parses the playbook at `playbookPath` and runs plays sequentially. Each play resolves hosts, merges play vars, gathers facts by default, runs `PreTasks`, roles, `Tasks`, `PostTasks`, and finally any handlers that were notified during the play. + +### (*Executor).Close +`func (e *Executor) Close()` + +Closes every cached `SSHClient` and replaces the client cache with a fresh empty map. + +### (*Executor).TemplateFile +`func (e *Executor) TemplateFile(src, host string, task *Task) (string, error)` + +Reads a template file, performs a basic Jinja2-to-Go-template token conversion, and executes it against a context built from executor vars, host vars, and gathered facts. If parsing or execution fails, it falls back to the executor's simpler string-templating path. + +### NewSSHClient +`func NewSSHClient(cfg SSHConfig) (*SSHClient, error)` + +Applies defaults for `Port`, `User`, and `Timeout`, then constructs an `SSHClient` from `cfg`. + +### (*SSHClient).Connect +`func (c *SSHClient) Connect(ctx context.Context) error` + +Lazily establishes the SSH connection. Authentication is attempted in this order: explicit key file, default keys from `~/.ssh`, then password-based auth. The method also ensures `known_hosts` exists and uses it for host-key verification. + +### (*SSHClient).Close +`func (c *SSHClient) Close() error` + +Closes the active SSH connection, if any, and clears the cached client pointer. + +### (*SSHClient).Run +`func (c *SSHClient) Run(ctx context.Context, cmd string) (stdout, stderr string, exitCode int, err error)` + +Runs a command on the remote host, opening a new SSH session after calling `Connect`. When privilege escalation is enabled, the command is wrapped with `sudo`, using either the become password, the SSH password, or passwordless `sudo -n`. The method returns stdout, stderr, an exit code, and honours context cancellation by signalling the session. + +### (*SSHClient).RunScript +`func (c *SSHClient) RunScript(ctx context.Context, script string) (stdout, stderr string, exitCode int, err error)` + +Wraps `script` in a heredoc passed to `bash` and delegates execution to `Run`. + +### (*SSHClient).Upload +`func (c *SSHClient) Upload(ctx context.Context, local io.Reader, remote string, mode fs.FileMode) error` + +Reads all content from `local`, creates the remote parent directory, writes the file via `cat >`, applies the requested mode with `chmod`, and handles both normal and `sudo`-mediated uploads. + +### (*SSHClient).Download +`func (c *SSHClient) Download(ctx context.Context, remote string) ([]byte, error)` + +Downloads a remote file by running `cat` and returning the captured bytes. A non-zero remote exit status is reported as an error. + +### (*SSHClient).FileExists +`func (c *SSHClient) FileExists(ctx context.Context, path string) (bool, error)` + +Checks remote path existence with `test -e`. + +### (*SSHClient).Stat +`func (c *SSHClient) Stat(ctx context.Context, path string) (map[string]any, error)` + +Returns a minimal stat map parsed from remote shell output. The current implementation reports boolean `exists` and `isdir` keys. + +### (*SSHClient).SetBecome +`func (c *SSHClient) SetBecome(become bool, user, password string)` + +Updates the client's privilege-escalation flag and replaces the stored become user and password only when non-empty override values are supplied.