Documentation
¶
Overview ¶
Package playbook implements Ansible's playbook execution model: plays over an inventory pattern, tasks with when/loop/register/notify/ block-rescue-always, and handlers — wired to github.com/go-ansible/{inventory,vars,template,modules,facts} and github.com/go-remoteexec/transport.
Index ¶
- func ConfigFilePath() string
- func ConfigFileValueForEnv(envVar string) (string, bool)
- func DefaultConnect(ctx context.Context, hostName string, hostVars map[string]any) (remoteexec.Connection, error)
- type BaseCallback
- type Callback
- type ConfigSetting
- type Connector
- type DefaultCallback
- type Engine
- type HostSummary
- type Play
- type PlayResult
- type Playbook
- type Result
- type RoleRef
- type RunResult
- type Task
- type VarPrompt
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ConfigFilePath ¶ added in v0.4.1
func ConfigFilePath() string
ConfigFilePath returns the ansible.cfg go-ansible would read from — see findConfigFile — or "" if none of the usual locations has one. go-ansible/cli's ansible-config uses this for `view` and to report each setting's real origin (env var / config file / compiled default) in `dump`, rather than guessing from whether the resolved value merely differs from the default.
func ConfigFileValueForEnv ¶ added in v0.4.1
ConfigFileValueForEnv reports whether the ansible.cfg go-ansible would read from actually sets the [defaults] key matching envVar (e.g. "ANSIBLE_REMOTE_USER" -> "remote_user") — go-ansible/cli's ansible-config dump uses this to report a setting's real origin rather than guessing "cfg" just because some config file happens to exist, whether or not it sets that particular key.
func DefaultConnect ¶
func DefaultConnect(ctx context.Context, hostName string, hostVars map[string]any) (remoteexec.Connection, error)
DefaultConnect implements Ansible's connection-variable conventions: ansible_connection ("local" or "ssh", default "ssh" — "smart" is treated as ssh, this package has no separate paramiko/openssh split to be smart about), ansible_host, ansible_port (default 22), ansible_user, ansible_password / ansible_ssh_pass, ansible_ssh_private_key_file, ansible_host_key_checking, ansible_ssh_timeout. ansible_ssh_common_args is not implemented (OpenSSH-specific flags have no equivalent in a Go SSH client). "localhost"/"127.0.0.1" (by hostName, unless ansible_connection overrides it) use Local.
Three of these settings — remote_user, host_key_checking, timeout — match real Ansible's own config precedence: an inventory/host var wins if set, otherwise an ANSIBLE_* environment variable if set, otherwise ansible.cfg's [defaults] section (see configFileValue) if it sets the matching key, otherwise the compiled-in default below. (forks is a fourth setting on the same precedence, minus the host-var layer — see Engine.Forks/ConfigDefaults — since it's a global concurrency cap, not a per-host connection detail, so it doesn't belong in this function's own resolution chain.) These four settings are the full extent of this port's ansible.cfg support — no other section or key is read at all (a real, stated gap — see go-ansible/cli's ansible-config, which reports exactly this precedence and these four settings, nothing more). Unlike real Ansible's lenient boolean parsing (yes/no/on/off/1/0/true/false, case-insensitive), ANSIBLE_HOST_KEY_CHECKING here is parsed with Go's strconv.ParseBool (true/false/1/0/t/f, case-sensitive on the word forms) — an invalid value falls back to the compiled-in default rather than erroring, matching how an unset var behaves.
Types ¶
type BaseCallback ¶ added in v0.8.0
type BaseCallback struct{}
BaseCallback implements every Callback hook as a no-op. Embed it in a callback that only cares about some of them, matching CallbackBase's own all-no-op defaults.
func (BaseCallback) OnPlayStart ¶ added in v0.8.0
func (BaseCallback) OnPlayStart(Play)
func (BaseCallback) OnStats ¶ added in v0.8.0
func (BaseCallback) OnStats(*RunResult)
func (BaseCallback) OnTaskResult ¶ added in v0.8.0
func (BaseCallback) OnTaskResult(Result)
type Callback ¶ added in v0.8.0
type Callback interface {
// OnPlayStart is raised once per play, before its hosts are
// resolved — real Ansible's v2_playbook_on_play_start.
OnPlayStart(play Play)
// OnTaskResult is raised for every task result on every host, one
// per loop iteration when a task loops. It covers what real Ansible
// splits across v2_runner_on_ok/failed/skipped, since Result already
// carries which of those it is.
OnTaskResult(r Result)
// OnStats is raised once at the end of a RunPlaybook call, whether
// or not it returned an error — real Ansible's
// v2_playbook_on_stats, the PLAY RECAP hook.
OnStats(rr *RunResult)
}
Callback is a reporting plugin: it observes a run as it happens without influencing it, matching real Ansible's own callback plugin contract (ansible.plugins.callback.CallbackBase and its v2_* hooks). An Engine carries a list of them (Engine.Callbacks), the way real Ansible loads one stdout-type callback plus any number of notification-type ones at the same time.
Every hook is called synchronously from whichever goroutine reached it, so OnTaskResult in particular arrives concurrently from several host goroutines at once. An implementation that keeps state or writes to a shared stream must serialize itself — DefaultCallback does. A hook must not block or panic.
Embed BaseCallback to implement only the hooks you care about, the way a real callback plugin overrides only the v2_* methods it needs.
This port has three hooks where real Ansible has 24, because a hook nothing in this port can trigger, and nothing can consume, would be an empty promise. Two absences worth naming: real Ansible's v2_playbook_on_start produces nothing at default verbosity (confirmed from default.py — it prints only above -v), and this port has no verbosity concept to gate it on; and handler runs are indistinguishable from ordinary task runs here, so there is nothing to raise real Ansible's separate v2_playbook_on_handler_task_start from.
type ConfigSetting ¶ added in v0.2.7
ConfigSetting is one entry of ConfigDefaults' report: a named setting, its ANSIBLE_* environment variable, its compiled-in default, and its current effective value (env var if set and valid, else the default) — the same precedence DefaultConnect itself uses, minus the inventory/host-var layer (which needs a specific host to resolve against, and so isn't part of this global view).
func ConfigDefaults ¶ added in v0.2.7
func ConfigDefaults() []ConfigSetting
ConfigDefaults reports every setting this package honors from the environment and ansible.cfg's [defaults] section — go-ansible/cli's ansible-config is a thin printer over this. This is the full list: go-ansible reads no other ansible.cfg section or key, and no other ANSIBLE_* variable, anywhere in the org.
type Connector ¶
type Connector func(ctx context.Context, hostName string, hostVars map[string]any) (remoteexec.Connection, error)
Connector dials a connection to a host, given its name and fully merged variables (before task-level rendering — this only needs the ansible_* connection variables, which are ordinary inventory/group vars).
type DefaultCallback ¶ added in v0.8.0
type DefaultCallback struct {
BaseCallback
// contains filtered or unexported fields
}
DefaultCallback prints a run the way ansible-playbook prints it to a terminal — PLAY and TASK banners, a colored per-host line for every result, and a PLAY RECAP — and is this port's equivalent of real Ansible's own ansible.builtin.default stdout callback.
Its banners are not padded out with asterisks the way real Ansible's Display.banner pads them to the terminal width; that is a cosmetic difference this port has always had, named here rather than left to be discovered.
func NewDefaultCallback ¶ added in v0.8.0
func NewDefaultCallback(w io.Writer, color bool) *DefaultCallback
NewDefaultCallback returns a DefaultCallback writing to w, with ANSI color escapes when color is true.
func (*DefaultCallback) OnPlayStart ¶ added in v0.8.0
func (c *DefaultCallback) OnPlayStart(play Play)
func (*DefaultCallback) OnStats ¶ added in v0.8.0
func (c *DefaultCallback) OnStats(rr *RunResult)
func (*DefaultCallback) OnTaskResult ¶ added in v0.8.0
func (c *DefaultCallback) OnTaskResult(r Result)
type Engine ¶
type Engine struct {
Inventory *inventory.Inventory
Modules *modules.Registry
Template *template.Engine
ExtraVars map[string]any
Connect Connector
// BaseDir resolves include_vars' file argument at run time (every
// other file-referencing directive — vars_files, roles,
// include_tasks/import_tasks, include_role/import_role — resolves
// at parse time via ParseFile's directory instead). Defaults to "."
// when unset, matching Parse.
BaseDir string
// RunTags/SkipTags filter which tasks execute, matching
// ansible-playbook's --tags/--skip-tags: a task runs if its
// effective tag set (its own tags unioned with every enclosing
// block's and the play's, computed once at parse time — see
// propagateTags) intersects RunTags, or RunTags is empty, or the
// task carries the "always" tag; and does not intersect SkipTags.
// Filtered-out tasks are reported Skipped (real Ansible omits them
// from output entirely — this port reports them instead, for
// visibility). Tag filtering does not apply to handlers.
RunTags []string
SkipTags []string
// OnResult, if set, is called synchronously as each task result is
// produced (from whichever goroutine ran that host's task) — for
// live progress reporting. It must not block or panic; callers
// wanting ordering should serialize themselves. It is the one-hook
// shorthand for a caller that only wants results; a caller wanting
// play and recap events too installs a Callback instead.
OnResult func(Result)
// Callbacks are reporting plugins observing the run as it happens —
// this port's equivalent of real Ansible's own callback plugins, and
// a list for the same reason real Ansible loads one stdout callback
// alongside any number of notification ones. See Callback for the
// concurrency contract every hook is held to.
Callbacks []Callback
// Forks caps how many hosts run concurrently at once — connecting/
// gathering facts, and each task/handler fan-out (runSingleTask,
// runFree) all respect it — matching real Ansible's own forks
// setting (default 5; New sets this field to 5 for the same
// reason). 0 or negative means unlimited concurrency, which was
// this port's only behavior before Forks existed; New's default
// changes that for anyone constructing an Engine the normal way,
// but a caller that explicitly wants the old unbounded behavior can
// still set Forks to 0 after New returns.
Forks int
// Prompt implements vars_prompt's actual interactive prompting:
// given the fully-formatted message (already combining the prompt
// text and "[default]" the way real Ansible's own do_var_prompt
// does) and whether the input should be hidden, it returns what the
// user entered. New's default (defaultPrompt) is a plain
// bufio-over-os.Stdin read with no terminal awareness at all — it
// works the same whether stdin is a real terminal or a pipe (never
// hides input, and never detects a non-interactive session the way
// real Ansible does to skip prompting and warn instead), which
// matches a raw library caller or a piped/test invocation but not
// real Ansible's actual interactive behavior. go-ansible/cli's
// ansible-playbook overrides this with a real, terminal-aware
// implementation (golang.org/x/term) for actual interactive use.
Prompt func(msg string, private bool) (string, error)
// VaultPassword decrypts a vault-encrypted file loaded at RUN time —
// today that means include_vars. Files pulled in while parsing (the
// playbook, vars_files, a role's own files) take their password from
// ParseFileWithVault instead, since parsing happens before an Engine
// exists.
VaultPassword string
}
Engine runs playbooks against an inventory. The zero value is not usable — use New.
type HostSummary ¶
type HostSummary struct {
Ok, Changed, Failed, Skipped int
// Unreachable, Rescued and Ignored are real Ansible's own further
// recap columns. Without them a rescued or ignored failure has
// nowhere to go but "failed", which is what this port used to do —
// reporting failures for a run real Ansible calls clean.
Unreachable, Rescued, Ignored int
}
Summary counts changed/failed/skipped/ok results across the whole run, keyed by host — Ansible's PLAY RECAP.
type Play ¶
type Play struct {
Name string
Hosts string
GatherFacts bool // default true
Become bool
BecomeUser string
BecomeMethod string
Vars map[string]any
VarsFiles []string // paths, resolved relative to the playbook's directory
Tasks []Task
Handlers []Task
Tags []string
Serial int // 0 means "all hosts at once" (linear strategy default)
Roles []RoleRef
// Strategy is "linear" (the default: every host finishes task N
// before any host starts task N+1) or "free" (each host runs its
// entire task list, and its own notified handlers, independently —
// a slow host never holds back a fast one). Any other named
// strategy real Ansible supports (debug, host_pinned, or a
// strategy plugin) is rejected at parse time rather than silently
// treated as linear.
Strategy string
// VarsPrompt, resolved once per play (not per host) before its
// tasks run — see Engine.applyVarsPrompt — into an ordinary play
// var, same scope as Vars. Real Ansible only accepts a list of
// maps here, never a bare-string shorthand (confirmed from
// ansible-core's own Play._load_vars_prompt/preprocess_vars: an
// item that isn't a mapping raises a parse error there too).
VarsPrompt []VarPrompt
}
Play runs a set of tasks against a pattern of inventory hosts.
type PlayResult ¶
type PlayResult struct {
Play string
Results []Result
// Rescued counts, per host, the blocks whose rescue recovered a
// failure. It is not derivable from Results: rescuing is a property
// of a BLOCK, and the failing task inside it looks the same either
// way. Real Ansible counts those under "rescued" and not "failed".
Rescued map[string]int
// contains filtered or unexported fields
}
PlayResult aggregates every Result from one play, in the order record was called — not necessarily task-list order, since the engine runs one goroutine per active host and every host's goroutine calls record on the same *PlayResult concurrently. mu is a pointer (not an embedded sync.Mutex) specifically so a PlayResult can still be copied by value — as RunPlaybook does, appending *pr into RunResult.Plays — without go vet's copylocks check firing; every copy keeps pointing at the one real mutex.
type Playbook ¶
type Playbook []Play
Playbook is an ordered list of plays, as ansible-playbook reads it.
func Parse ¶
Parse parses a playbook YAML document (a top-level list of plays). File-referencing directives (vars_files, roles, include_tasks, import_tasks, include_role, import_role) resolve their paths relative to the current working directory — use ParseFile when the playbook lives elsewhere and its includes should resolve relative to it.
func ParseFile ¶ added in v0.2.0
ParseFile reads and parses the playbook at path, resolving every file-referencing directive relative to path's directory (matching ansible-playbook, which resolves roles/ and included files relative to the playbook file, not the current working directory).
func ParseFileWithVault ¶ added in v0.14.0
ParseFileWithVault is ParseFile with a vault password, so the playbook or any file it pulls in — a vars_files target, a role's own defaults/vars/tasks — may be vault-encrypted. An empty password behaves exactly like ParseFile.
type Result ¶
type Result struct {
Host string
Task string
Module string
Changed bool
Failed bool
Skipped bool
// Ignored marks a failure the task's own ignore_errors swallowed.
// Real Ansible counts one of these under "ignored" AND under "ok",
// not under "failed" — a run whose every failure was ignored reports
// failed=0.
Ignored bool
// Unreachable marks a host that could not be connected to at all.
// Real Ansible gives it its own recap column, separate from a task
// that ran and failed.
Unreachable bool
Msg string
Extra map[string]any
}
Result is one task's outcome on one host (one per loop iteration when a task loops).
type RunResult ¶
type RunResult struct {
Plays []PlayResult
}
RunResult aggregates every play's results, in playbook order.
func (*RunResult) Failed ¶
Failed reports whether the run ended with a real failure — the question ansible-playbook's exit code asks. It is the host's FINAL state, not the history: a failure the task's own ignore_errors swallowed does not count, and neither does one a block's rescue recovered. Real ansible-playbook exits 0 for a run whose only failures were ignored or rescued, and this port used to exit 2.
func (*RunResult) Summary ¶
func (rr *RunResult) Summary() map[string]*HostSummary
type Task ¶
type Task struct {
Name string
Module string
Args map[string]any
When string // Jinja2 expression, already normalized from a string or []string
Loop any // a literal list, or a "{{ expr }}" string rendered at run time
LoopVar string // default "item"
IndexVar string // loop_control.index_var — unset means no index variable
Register string
IgnoreErrors bool
ChangedWhen string
FailedWhen string
Tags []string
Become *bool // nil means "inherit the play's setting"
BecomeUser string
Notify []string
Vars map[string]any
DelegateTo string
// Until/Retries/Delay implement the task retry loop: real Ansible
// runs the task 1+Retries times (Retries nil, the "unset" state,
// means no retry loop at all UNLESS Until is non-empty, in which
// case real Ansible defaults Retries to 3 — mirrored in
// runTaskOnHost, not here, since it needs to distinguish "Retries
// explicitly 0" from "Retries unset"), re-checking Until (or, if
// Until is empty but Retries was explicitly set, "not failed")
// after each attempt and sleeping Delay seconds before the next one
// if it didn't pass. See runTaskOnHost's retry loop for the exact
// attempt-counting algorithm, including a real, deliberately
// reproduced quirk in ansible-core's own retry loop.
Until string
Retries *int
Delay float64
// RunOnce restricts execution to the first currently-active host in
// a runSingleTask call, broadcasting any Register result to every
// other active host afterward (so a later task on ANY host can
// still read it by bare name) — matching real Ansible's own
// run_once, including its result-sharing, verified against a real
// ansible-playbook run. A real, narrower limitation under strategy:
// free: each host there calls runSingleTask with itself as the only
// active host (see runFree), so there is no cross-host "first one"
// to restrict to — every host still runs its own copy. Real Ansible
// coordinates run_once across free's independent per-host lanes;
// this port does not, and says so here rather than silently
// re-running the task on every host without comment.
RunOnce bool
// Async/Poll implement async:/poll: — see runTaskOnHost's async
// branch and modules.AsyncLaunch/AsyncCheck for the real mechanism
// and its one disclosed limitation (no active kill on timeout).
// Async <= 0 means synchronous, ordinary execution — the
// overwhelming majority of tasks. Only command/shell support
// Async > 0 at all: every other module's work happens as a
// sequence of calls from the control node, not one remote
// invocation that could be backgrounded on the target the way
// async requires — a task on any other module with Async > 0 fails
// loud rather than silently running synchronously and ignoring
// what was explicitly asked for. Poll nil means "unset": defaults
// to 15 seconds (real Ansible's own DEFAULT_POLL_INTERVAL) when
// Async > 0; Poll 0 is fire-and-forget (the task returns immediately
// once the job is launched, for a later task to check via
// async_status); Poll > 0 waits, checking every Poll seconds, until
// the job finishes or Async seconds pass (a timeout failure).
Async int
Poll *int
// RoleDefaults/RoleVars are set only on the synthetic block task
// produced for a roles: entry or include_role/import_role — the
// engine (Engine.pushRoleVars) merges them on top of the
// RoleDefaults/RoleVars layers' current content for the duration of
// the block, then restores the prior (pre-merge) content. Nesting
// (a role that itself includes another role) composes correctly to
// any depth this way: a variable the inner role doesn't define in
// its own defaults/main.yml or vars/main.yml still resolves to
// whatever the enclosing role(s) already had, matching real
// Ansible's behavior of keeping every currently active role's
// defaults/vars in scope at once.
RoleDefaults map[string]any
RoleVars map[string]any
// RoleVarsScoped limits RoleDefaults/RoleVars to this role's own
// block, unwinding them when it ends. True only for include_role,
// which is dynamic: real Ansible resolves it at run time and its
// variables leave scope with it. A roles: entry and import_role are
// both static — real Ansible injects their variables for the whole
// play, so they persist. Measured against real ansible-core 2.21.4
// by running include_role and import_role in isolation: after the
// former the role's vars read as undefined, after the latter they
// still resolve.
RoleVarsScoped bool
// RoleDir is the directory of the role this task came from, empty
// for a task written directly in a playbook. A relative src: on a
// file-carrying module resolves against it — real Ansible looks in
// the role's own files/ (or templates/ for template) before
// anything else, which is what makes "src: hello.txt" work inside a
// role at all.
RoleDir string
Block []Task
Rescue []Task
Always []Task
}
Task is one step of a play (or of a block's body/rescue/always). Module/Args are populated from whichever single non-reserved key the task's YAML mapping carried (e.g. `copy:` or `command:`) — empty for a block/meta task.
type VarPrompt ¶ added in v0.5.0
VarPrompt is one vars_prompt entry. Prompt defaults to Name when empty, Private defaults to true (confirmed from ansible-core's own playbook_executor.py: private = boolean(var.get("private", True)) — prompts hide input UNLESS private: false is explicit, the opposite of what the name alone might suggest). encrypt/salt/salt_size/unsafe (hashing the prompted value, and disabling template-escaping of it) are real ansible-core vars_prompt keys this port does not implement — accepted and parsed for shape compatibility, silently no-op'd rather than erroring on an unrecognized key, since a real playbook using only the common name/prompt/default/private/confirm subset (the overwhelming majority) should not need every knob wired to run.