Documentation
¶
Overview ¶
Package modules implements Ansible's module execution model: each module is a Go function that takes a target connection (github.com/go-remoteexec/transport) and a set of arguments (already Jinja2-rendered by the caller) and returns a Result — Ansible's changed/failed/msg triple plus any module-specific fields.
Unlike real Ansible, which copies a Python script to the target and runs it there, a module here runs its logic on the control node and reaches the target only through the Connection's Exec/Put/Fetch primitives. The observable behavior is the same (the target ends up in the same state); the difference is architectural, not behavioral, and it means a module needs no Go toolchain on the target.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var Docs = map[string]string{
"apt": "moduleApt implements (a subset of) Ansible's `apt` module for\nDebian/Ubuntu package management.\n\nArgs: name (string or []string, required); state (present|installed|\nabsent|latest, default \"present\"); update_cache (bool, default\nfalse) — run `apt-get update` first.\n",
"apt_key": "moduleAptKey implements (a subset of) Ansible's `apt_key` module:\nadds or removes an APT signing key on a Debian/Ubuntu target via the\nclassic `apt-key` command.\n\nReal ansible.builtin.apt_key is itself documented as deprecated\nupstream — Debian/Ubuntu's own apt tooling has deprecated apt-key in\nfavor of keyring files under /etc/apt/keyrings/, and apt-key is slated\nfor eventual removal. Real Ansible still ships apt_key for\ncompatibility with existing playbooks; this port does the same,\nimplementing the classic apt-key behavior rather than the newer\nkeyring-file approach.\n\nArgs: id (string) — the key's ID/fingerprint, used with keyserver to\nfetch a key, or alone with state=absent to remove one; keyserver\n(string, paired with id) — fetch the key from this keyserver; url\n(string) — fetch key data from this URL instead of a keyserver;\nstate (present|absent, default \"present\").\n\nSimplifications vs real apt_key: no `data` (inline key material),\n`file` (local key file), `keyring` (alternate keyring path), or\n`validate_certs` support. Idempotency for state=present greps\n`apt-key list`'s human-readable output for id as a plain substring —\nweaker than real apt_key's exact fingerprint comparison, but it\navoids parsing apt-key's multi-line output format, which varies by\ngnupg version.\n",
"apt_repository": "moduleAptRepository implements (a subset of) Ansible's\n`apt_repository` module: adds or removes an APT source, either a\nliteral `deb ...` line (written to a file derived from the line,\nunder /etc/apt/sources.list.d/) or a `ppa:user/name` shorthand\n(delegated to `add-apt-repository`, matching what real Ansible itself\ndoes under the hood for that form rather than reimplementing PPA URL\nconstruction).\n\nArgs: repo (string, required) — a `deb ...` source line, or a\n`ppa:user/name` shorthand; state (present|absent, default \"present\");\nupdate_cache (bool, default false — real ansible.builtin.apt_repository\ndefaults this to true; this port defaults to false per this batch's\ntask spec, a deliberate deviation documented here).\n\nSimplifications vs real apt_repository: no `filename` (the\ndestination filename is always derived from repo, not user-settable),\n`mode`, `codename`, `validate_certs`, or cache-retry knobs. For the\n`ppa:` form, idempotency is not checked before invoking\nadd-apt-repository — like apt.go's \"latest\" state, a no-op\nadd-apt-repository still exits 0 and this port can't cheaply tell\n\"already added\" apart without parsing its output, so that form is\nalways reported changed. For the plain `deb ...` line form,\nidempotency IS checked, by comparing the derived file's existing\ncontent against the wanted line.\n",
"assemble": "moduleAssemble implements (a subset of) Ansible's `assemble` module:\nconcatenates every file fragment found directly under a source\ndirectory (sorted by name) into one destination file.\n\nUnlike most modules in this package (which operate on data the\ncontrol node already has, or that it moves verbatim via Put/Fetch),\nassemble's whole job is manipulating files that already exist on the\nTARGET — this port therefore composes the listing/filtering/\nconcatenation as a shell pipeline over conn.Exec, rather than\nfetching fragments to the control node and reassembling them\nlocally only to re-upload the result.\n\nArgs: src (string, required) — a directory on the target holding the\nfragments; dest (string, required); regexp (string, optional) — only\nfragment filenames matching this ERE (passed to `grep -E`) are\nincluded; delimiter (string, optional) — inserted between fragments\n(including after the last one, unlike real assemble, which places it\nonly between fragments — see below).\n\nSimplifications vs real ansible.builtin.assemble: no backup, decrypt,\nignore_hidden, mode/owner/group/attributes, or validate support. Real\nassemble's action plugin can also source fragments from the control\nnode (copying them to the target first when they aren't already\nthere); this port always assumes src already exists on the target,\nmatching the common case this batch's task spec calls out. Real\nassemble is also idempotent — it hashes the assembled content and\nonly rewrites dest if it differs (like copy.go's fetch-and-compare\npattern); this port always rewrites dest and reports changed, since\ncomposing \"assemble remotely into a temp file, fetch it back to\ncompare, then conditionally rename\" in one round trip added\ncomplexity this batch didn't budget for — a real gap versus real\nAnsible's idempotent behavior, documented rather than silently\nclaimed.\n",
"assert": "moduleAssert implements Ansible's `assert` module: fails unless every\ncondition in `that` evaluates true. Conditions here are already\nbooleans (the caller — the playbook engine — evaluates each Jinja2\nexpression in `that` before invoking the module, matching how\nAnsible's own assert action plugin works).\n\nArgs: that ([]bool); fail_msg/msg (string); success_msg (string).\n",
"async_status": "moduleAsyncStatus implements Ansible's `async_status` module: checks\non a previously started asynchronous task by its job ID.\n\nReal ansible.builtin.async_status only works together with the\n`async`/`poll` task-level mechanism: a task launched with `async:` is\nbackgrounded on the target with its own results file under\n`~/.ansible_async/`, and async_status polls that file for `jid`. This\nport implements no such mechanism at all — `async`/`poll` is an\nengine-level feature (how a task is launched and subsequently\nawaited), out of scope for a single module, and go-ansible's engine\nnever backgrounds a task that way. Rather than silently returning a\nfabricated \"finished\" result (which would misrepresent an\nunimplemented feature as a working one), async_status always fails\nwith a clear, on-topic message — this package's convention of\nfailing loud instead of being silently wrong.\n\nArgs: jid (string, required); mode (status|cleanup, default\n\"status\") — accepted and validated so a real playbook's async_status\ntask gets a specific failure naming the actual gap, rather than a\ngeneric argument error; both modes fail identically here, since\nneither has anything to check or clean up.\n",
"blockinfile": "moduleBlockinfile implements (a subset of) Ansible's `blockinfile`\nmodule: ensures a marked, multi-line block of text is present in (or\nabsent from) a file, wrapped between two marker-comment lines so a\nlater run can find and replace exactly the block it wrote.\n\nArgs: path (string, required); block (string, default \"\" — an empty\nblock is treated as state=absent regardless of the state argument,\nper this batch's task spec; real ansible.builtin.blockinfile instead\ninserts an empty marked block in that case, so this is a deliberate\ndeviation, documented here); marker (string, default \"# {mark}\nANSIBLE MANAGED BLOCK\" — \"{mark}\" is replaced with \"BEGIN\"/\"END\" to\nform the two marker lines); state (present|absent, default\n\"present\"); insertafter/insertbefore (string, optional — a regexp\nmatched against existing lines; the block is inserted after/before\nthe first match, or at EOF if neither is given or the regexp doesn't\nmatch; the literal values \"BOF\"/\"EOF\" are also accepted, matching\nreal blockinfile's own special-cased anchors); create (bool, default\nfalse — create the file if it doesn't exist).\n\nIf a marked block already exists, it is replaced in place at its\nexisting location, ignoring insertafter/insertbefore for that run —\ninsertafter/insertbefore only decide where a new block is inserted\nthe first time, matching real blockinfile's own behavior.\n\nSimplifications vs real blockinfile: no backup, owner/group/mode/\nattributes, SELinux context, validate, encoding, or\nappend_newline/prepend_newline support. insertafter/insertbefore are\nfull regexps (via the same engine lineinfile/replace use), not\nPython's re — differences are expected to be rare for the anchor\npatterns this module is typically given.\n",
"command": "moduleCommand implements Ansible's `command` module: runs a program\nwith an argument list, never interpreting shell metacharacters in\nthose arguments (pipes, redirects, `;` are passed through as literal\nargv entries, not executed) — for shell features, use `shell`.\n\nArgs: cmd (string) or argv (list) — the command; chdir; creates;\nremoves.\n",
"copy": "moduleCopy implements Ansible's `copy` module: writes literal content\nor a local file to a path on the target, idempotently (skips the\ntransfer when the destination already holds the same bytes) and\noptionally sets its mode.\n\nArgs: dest (string, required); content (string) or src (local file\npath) — exactly one; mode (octal string).\n",
"cron": "moduleCron implements (a subset of) Ansible's `cron` module: manages\none entry in a crontab, identified by a `# ansible: <name>` comment\nline immediately above it (Ansible's own marker convention).\n\nArgs: name (string, required) — the entry's identifying comment; job\n(string, required unless state=absent) — the command; minute, hour,\nday, month, weekday (string, default \"*\" each); state (present|\nabsent, default \"present\"); user (string) — manage this user's\ncrontab via `crontab -u` (requires privilege) instead of the\nconnection's own user's.\n",
"deb822_repository": "moduleDeb822Repository implements (a subset of) Ansible's\n`deb822_repository` module: writes (or removes) a structured\nRFC822/deb822-format APT source file under\n/etc/apt/sources.list.d/<name>.sources — the modern replacement for\nthe one-line `deb ...` sources apt_repository.go's plain-line path\ncomposes.\n\nArgs: name (string, required) — used only as the destination\nfilename stem, matching how real deb822_repository derives its\ndefault filename from `name` when `filename` isn't given (this port\nhas no separate `filename` argument at all: filename is always\nderived from name); types ([]string, default [\"deb\"]) — \"deb\" or\n\"deb-src\"; uris ([]string, required); suites ([]string, required);\ncomponents ([]string, optional); signed_by (string, optional) — a\nURL, path, fingerprint, or inline key block, written verbatim into\nthe stanza's Signed-By field; state (present|absent, default\n\"present\").\n\nSimplifications vs real deb822_repository: none of allow_insecure,\nallow_weak, architectures, by_hash, check_date, check_valid_until,\ndate_max_future, enabled, trusted, or the dozen other apt-preferences\nknobs real deb822_repository exposes are supported — this port\nwrites the handful of fields that make a source resolvable\n(Types/URIs/Suites/Components/Signed-By) and nothing else. Real\ndeb822_repository also validates and normalizes signed_by (fetching\na URL into a keyring file under /etc/apt/keyrings/ when it looks like\none); this port writes whatever string it's given straight into the\nSigned-By field, which is only valid deb822 syntax when signed_by is\nitself a path, a fingerprint, or an inline ASCII-armored block —\npassing a bare URL here does NOT fetch and materialize a keyring\nfile the way real deb822_repository does.\n\nIdempotency: like apt_repository.go's plain-line path, present is\nchecked by comparing the destination file's existing content against\nthe wanted stanza byte-for-byte.\n",
"debconf": "moduleDebconf implements (a subset of) Ansible's `debconf` module:\npre-seeds a debconf question/answer via `debconf-set-selections`.\n\nArgs: name (string, required) — the package name; question (string,\nrequired) — the debconf question key; value (string, required for\nstate \"present\"); vtype (string, default \"string\" — one of string,\nboolean, select, multiselect, password, note); state (present|absent,\ndefault \"present\").\n\nReal ansible.builtin.debconf has no `state` argument at all — setting\na debconf answer is the module's only operation, and there is no\nsupported way to \"unset\" one (debconf keeps whatever was last set\nuntil something else overwrites it). This port accepts state for\nshape-symmetry with the other modules in this batch, but treats\n\"absent\" as a documented no-op (returns Ok without touching the\ntarget) rather than inventing a removal mechanism real debconf\ndoesn't have.\n\nIdempotency: this port makes a best-effort check via `debconf-show\n<name>`, grepping for a \"<question>: <value>\" substring. debconf-show's\noutput format varies by vtype (multiselect values are comma-joined,\nboolean/select values are the raw stored token, and each line is\nmarked seen \"*\" or unseen \" \") and this check does not parse any of\nthat — it is a plain substring grep, so it can false-negative (report\nchanged when the value was already set in a different textual form).\nReal ansible.builtin.debconf has essentially the same limitation:\nrobustly parsing debconf-show requires understanding each vtype's own\nserialization, which isn't cheap to do from shell. Where this check\ncan't be trusted, unconditionally (re-)setting the selection is a\nstrictly safer failure mode than wrongly skipping a needed change.\n",
"debug": "moduleDebug implements Ansible's `debug` module: prints a message\n(or the value of `var`) without changing anything.\n\nArgs: msg (string, default \"Hello world!\"); var (any) — a value to\nprint by its own repr instead of a fixed message.\n",
"dnf": "moduleDnf implements (a subset of) Ansible's `dnf` module for\nRPM-based package management via the classic `dnf` CLI.\n\nArgs: name (string or []string, required); state (present|latest|\nabsent, default \"present\").\n\nThis is a thin wrapper around dnfLike (see below), shared with\nmoduleDnf5.\n",
"dnf5": "moduleDnf5 implements (a subset of) Ansible's `dnf5` module: RHEL9+/\nFedora's rewritten package manager. dnf5's CLI is compatible with\nclassic dnf for the install/remove/update operations this port\ncomposes, so this is a thin wrapper around the shared dnfLike helper\n(see dnf.go) with the binary name swapped — there is no behavioral\ndifference worth keeping separate at this port's level of fidelity\n(real dnf5 does differ from dnf in module-stream handling, weak-deps\ndefaults, and its own set of CLI flags, none of which this port's\nsimplified install/remove/upgrade composition touches).\n\nArgs: name (string or []string, required); state (present|latest|\nabsent, default \"present\").\n",
"dpkg_selections": "moduleDpkgSelections implements Ansible's `dpkg_selections` module:\nsets a package's dpkg selection state via `dpkg --set-selections`.\n\nArgs: name (string, required); selection (string, required — one of\ninstall, hold, deinstall, purge).\n",
"expect": "moduleExpect implements (a best-effort approximation of) Ansible's\n`expect` module: runs a command and responds to interactive prompts.\n\nReal ansible.builtin.expect is a limited pexpect: it launches the\ncommand and, as each prompt (matched by a Python regex) appears on\nits output stream, writes the corresponding answer back to its\nstdin — a genuinely interactive, conditional exchange. This port's\nConnection.Exec is a single blocking round trip (cmd in, one\ncaptured Result out) with no way to observe output as it arrives or\nwrite to stdin mid-command, so true prompt-matching is not\nimplementable against this abstraction as currently designed.\n\nWhat this port does instead: run the command via conn.Exec, and if\n`responses` is given, concatenate its values (in map-key sorted\norder, for determinism — real expect answers prompts in the order\nthey're seen on the command's actual output, which this port cannot\nobserve) into stdin, one per line, joined with newlines. This is a\n\"feed some stdin and hope the command's prompts consume it in the\nsame order\" approximation, NOT real expect/prompt-matching behavior:\nthe `responses` keys (each a regex matched against a specific\nprompt) are not matched against anything here, and a command whose\nprompts arrive in a different order, or that reads from a tty\ninstead of stdin, will not behave as it would under real expect.\n\nArgs: command (string, required); responses (map[string]any,\noptional) — each value a string or list of strings; only the first\nelement of a list value is used (a real conditional\n\"different answer for the Nth occurrence\" is exactly the interactive\nbehavior this port can't provide); chdir (string, optional); creates,\nremoves (string, optional, same short-circuit as command/shell).\n",
"fail": "moduleFail implements Ansible's `fail` module: always fails with msg.\n\nArgs: msg (string, default \"Failed as requested from task\").\n",
"fetch": "moduleFetch implements Ansible's `fetch` module: copies a file from\nthe target to the control node, idempotently (skips overwriting dest\nwhen its content already matches the fetched bytes).\n\nReal ansible.builtin.fetch lays files out under dest/hostname/src,\nkeyed by inventory_hostname, because one control-node run fans out\nover many target hosts. This port's module signature has no\ninventory/hostname concept threaded through it — a module only sees\none Connection at a time, with nothing identifying which inventory\nhost it is — so dest is treated as a literal local file path,\nmatching how copy's src/dest already work in this port. A caller\nthat wants the per-host tree can build dest itself before invoking\nthis module.\n\nArgs: src (string, required, remote path); dest (string, required,\nlocal path); fail_on_missing (bool, default true).\n",
"file": "moduleFile implements (a subset of) Ansible's `file` module: manages\na path's existence/type and permissions.\n\nArgs: path (string, required); state (file|directory|absent|touch|\nlink, default \"file\"); mode (octal string); owner; group; src (the\nlink target, for state=link).\n",
"find": "moduleFind implements (a subset of) Ansible's `find` module: lists\nfiles under one or more paths matching simple criteria, by composing\na POSIX `find` invocation on the target — unlike real\nansible.builtin.find, which is a pure-Python walk that doesn't shell\nout to `find` at all; the observable result (a list of matching\npaths) is the same.\n\nArgs: paths (string or []string, required); patterns (string or\n[]string, glob, optional — matches everything when empty); recurse\n(bool, default false); file_type (file|directory|any, default\n\"file\").\n\nEach matched entry in Extra[\"files\"] carries only \"path\" — real\nfind's per-file dict also includes size/mode/mtime/checksum/etc via\nPython's os.stat, which this port does not replicate (a caller\nwanting that can `stat` each returned path itself).\n\nA path that doesn't exist, or a permission-denied subdirectory while\nrecursing, makes POSIX find exit non-zero while still printing\neverything it did manage to find; unlike most other modules in this\npackage, moduleFind does not treat that as a hard failure — it\nparses whatever stdout it got, matching find's own \"best effort\"\nbehavior more closely than failing the whole task would.\n",
"gather_facts": "moduleGatherFacts implements Ansible's `gather_facts` module: a thin\nwrapper that calls the `setup` module. Real ansible.builtin.gather_facts\nexists mostly as its own callable name for the engine's implicit\nfacts-collection step (and, per its argspec, a `parallel` toggle for\nrunning multiple fact modules concurrently) — it delegates the actual\nprobing to setup (or whatever module(s) ansible_facts_modules names).\nThis port has exactly one fact-gathering implementation (moduleSetup),\nso gather_facts is a pure delegation to it.\n\nArgs: parallel (accepted, silently ignored — meaningless with a\nsingle fact-gathering module and one Connection); every other\nargument is passed straight through to moduleSetup.\n",
"get_url": "moduleGetURL implements (a subset of) Ansible's `get_url` module:\ndownloads a URL to a path on the target.\n\nReal get_url runs on the target already (Python was copied there\nbefore the module ran); this port has no separate module-copy step,\nbut the download must still happen on the target rather than the\ncontrol node — downloading here and Put-ing the bytes over would go\nthrough a network path real get_url never takes, and would silently\nbreak for a URL only the target can reach. So this composes a remote\ncurl/wget invocation via conn.Exec, for the same architectural reason\ndocumented on moduleURI.\n\nArgs: url (string, required); dest (string, required, remote path);\nmode (octal string, optional); force (bool, default false).\n\nIdempotency: real get_url can compare an ETag/Last-Modified header or\nan explicit checksum against the existing destination to decide\nwhether to re-download. This port simplifies that to an existence\ncheck only — it skips the download whenever dest already exists,\nunless force is set. That is weaker than real Ansible (a changed\nremote resource at the same URL is not detected without force),\nwhich is documented here deliberately.\n",
"getent": "moduleGetent implements (a subset of) Ansible's `getent` module: runs\n`getent <database> [<key>]` on the target and parses the output into\na map keyed by each entry's first field (the entry's own name),\nvalued by its remaining fields.\n\nReal ansible.builtin.getent nests its result under\nansible_facts.getent_<database>. This port, following how set_fact\nand stat already shape their results, puts it under\nExtra[\"getent_<database>\"] instead of Facts — a deliberate deviation\nfrom real Ansible's exact contract, documented here.\n\nArgs: database (string, required); key (string, optional); split\n(string, optional — the field separator; defaults to \":\" for every\ndatabase except \"hosts\", which getent prints whitespace-separated,\nnot colon-separated); fail_key (bool, default true — fail when key is\ngiven but not found).\n",
"git": "moduleGit implements (a subset of) Ansible's `git` module: clones a\nrepository, or updates an existing clone to the requested version.\n\nArgs: repo (string, required); dest (string, required); version\n(string, default \"HEAD\" — a branch, tag, or commit).\n",
"group": "moduleGroup implements Ansible's `group` module: ensures a group\nexists or is removed.\n\nArgs: name (string, required); state (present|absent, default\n\"present\"); system (bool) — pass -r/-system to groupadd.\n",
"hostname": "moduleHostname implements (a subset of) Ansible's `hostname` module:\nsets the target's hostname, idempotently (checks the current\nhostname first via the plain `hostname` command, portable across\nsystemd/BSD/macOS targets alike).\n\nArgs: name (string, required).\n\nSimplification: real hostname auto-detects the right strategy per\ndistribution/OS (`use` picks among alpine/debian/freebsd/macos/\nredhat/systemd/... backends, and on macOS specifically it drives\n`scutil` for HostName/ComputerName/LocalHostName rather than the\n`hostname` command at all). This port always tries `hostnamectl\nset-hostname` (systemd, the modern majority) first and falls back to\nthe plain `hostname` command otherwise, matching the task's\nspecified two-tier strategy rather than the full per-OS matrix.\n",
"iptables": "moduleIptables implements (a subset of) Ansible's `iptables` module:\ncomposes an `iptables` invocation from a handful of common match/\ntarget fields, checking whether the rule already exists via\n`iptables -C` before adding or removing it — the same idempotency\napproach real ansible.builtin.iptables itself uses (it also shells\nout to iptables and relies on -C for existence checking, rather than\nparsing `iptables-save` output).\n\nArgs: chain (string, required); protocol (string, optional);\nsource, destination (string, optional); destination_port (string,\noptional); jump (string, optional — e.g. ACCEPT, DROP, REJECT);\nstate (present|absent, default \"present\"); action (append|insert,\ndefault \"append\" — only meaningful for state=present: append uses\n-A, insert uses -I); table (string, optional — passed as `-t table`\nwhen given).\n\nThis port does not attempt real iptables' full flag surface: no\nctstate, in/out interface, icmp-type, limit/comment matching, IPv6\n(ip6tables) support, or chain_management (auto-creating a\nuser-defined chain). A reasonable common-case subset, documented as\nsuch — this is the same \"cover the frequent case, fail cleanly\nrather than silently drop a flag\" tradeoff apt_key.go and others in\nthis batch make.\n",
"known_hosts": "moduleKnownHosts implements (a subset of) Ansible's `known_hosts`\nmodule: adds or removes an SSH host key line in a known_hosts-style\nfile.\n\nArgs: name (string, required) — the host (aliased from `host` in\nreal Ansible; this port only accepts `name`, since args here are\nalready resolved by the caller before reaching a module — see\nmodule.go's doc comment); key (string, required when state=present)\n— the full known_hosts line content (hostname/pattern plus key type\nplus key data, exactly as it would appear in the file); path\n(string, default \"~/.ssh/known_hosts\"); state (present|absent,\ndefault \"present\").\n\nSimplifications vs real known_hosts: no hash_host (real known_hosts\ncan store a hashed hostname instead of plaintext via `ssh-keygen\n-H`; this port always writes/matches the plaintext name); state=\nabsent removes every line for `name` that this port can find via a\nplain substring grep for name (not `ssh-keygen -R`'s own matching\nlogic, which understands hashed entries and comma-separated\nhostname/IP pairs) — a real known_hosts entry using a hashed\nhostname will not be found or removed by this port. Idempotency for\nstate=present is an exact full-line match (grep -qxF): a key\nre-formatted or re-ordered but semantically identical to an existing\nentry is NOT recognized as already present, and will be appended\nas a (functionally redundant, but textually different) duplicate\nline — real known_hosts avoids this by parsing and comparing the\nkey material itself.\n",
"lineinfile": "moduleLineinfile implements (a subset of) Ansible's `lineinfile`\nmodule: ensures a particular line is present or absent in a file.\n\nArgs: path (string, required); line (string, required unless\nstate=absent with regexp); regexp (string) — when set, the line\nreplacing/removed is whichever existing line matches it, otherwise an\nexact-line match is used; state (present|absent, default \"present\");\ncreate (bool, default false) — create the file if it doesn't exist.\n",
"mount_facts": "moduleMountFacts implements (a subset of) Ansible's `mount_facts`\nmodule: gathers the target's currently mounted filesystems into\nExtra[\"mounts\"], a list of maps each with \"device\", \"mount_point\",\n\"fstype\", and \"options\".\n\nArgs: devices, fstypes ([]string, optional) — glob patterns filtering\nthe result by device or filesystem type, matched with Go's\npath.Match, which is close to (but not identical to) real\nmount_facts' Python fnmatch — both support `*`/`?`/`[...]`, but\npath.Match additionally treats `/` specially (it won't let `*` match\nacross a `/`), which rarely matters for the flat device/fstype\nstrings this filters against.\n\nThis port tries Linux's /proc/mounts first (one cat, trivially\nparsed: whitespace-separated device/mount_point/fstype/options/dump/\npass, matching stat.go's own GNU-first/BSD-fallback pattern), then\nfalls back to parsing plain `mount` command output for targets\nwithout /proc/mounts (macOS/*BSD). Real mount_facts supports many\nmore `sources` (/etc/mtab, /etc/fstab, /etc/vfstab, getmntent, etc.),\nselectable and orderable via its own `sources` argument, plus\naggregate_mounts, mount_binary, on_timeout/timeout handling — none of\nthat is implemented here; this port always tries exactly the two\nsources above, in that fixed order, and fails cleanly if neither\nworks, rather than replicating that whole source-priority system.\n",
"package": "modulePackage implements Ansible's OS-agnostic `package` module:\ndetects the target's package manager and delegates to the matching\nmodule already implemented in this package.\n\nArgs: name (string or []string, required); state (string, required —\npassed through to the delegate as-is, so its accepted values are\nwhatever the resolved delegate accepts).\n\nDetection order: apt-get, then dnf, then yum (treated as a dnf-family\nalias — real Ansible documents that on a modern dnf-based system, yum\nis itself just dnf under a symlink/shim). Real ansible.builtin.package\nadditionally supports zypper (SUSE), pacman (Arch), apk (Alpine), and\nmore via ansible_facts.pkg_mgr; this port covers only the\napt/dnf/yum-as-dnf-alias families, since those are the only\npackage-manager modules implemented in this package. An unrecognized\ntarget fails cleanly rather than silently no-op'ing.\n\nUnlike real package (which resolves and re-executes the target module\nas its own Ansible task, going back through the plugin/connection\nlayers), this port calls the delegate's Go function directly —\ncheaper and simpler, and observably identical since both paths end up\nrunning the same target-side commands.\n",
"package_facts": "modulePackageFacts implements (a subset of) Ansible's `package_facts`\nmodule: gathers the list of installed packages into\nExtra[\"packages\"], shaped like real Ansible's ansible_facts.packages —\na map from package name to a list of entries (one per installed\nversion), each entry a map with at least \"version\".\n\nArgs: manager (string, default \"auto\" — \"auto\" triggers detection\n(see detectPackageManager); \"apt\" forces the dpkg-query path; any of\n\"dnf\", \"dnf5\", \"yum\", \"rpm\" force the rpm -qa path; anything else\nfails cleanly).\n\nReal package_facts additionally supports apk (Alpine), pacman\n(Arch), FreeBSD pkg, OpenBSD pkg_info, and portage — none of those\nare implemented here, matching modulePackage's own apt/dnf-family-only\ncoverage. Real package_facts' entries also carry \"release\", \"epoch\",\n\"arch\", and \"source\"; this port only fills \"version\" (dpkg/rpm's own\ncombined version-release string for rpm, or dpkg's version field for\napt), since that's the only field cheaply available from a\nsingle-line dpkg-query/rpm -qa format string without an additional\nper-package query.\n",
"pause": "modulePause implements (a subset of) Ansible's `pause` module: pauses\nfor a given duration.\n\nReal ansible.builtin.pause runs entirely on the control node (it's a\npure action-plugin module with no target-side component at all) and,\nwhen neither `seconds` nor `minutes` is given, prompts interactively\non the controller's own terminal and blocks until the user presses\nEnter (or Ctrl-C). This port has no interactive control-node\nmechanism to hook into — a module here is a Go function returning a\nResult, not a live process attached to a terminal — so the\nno-duration form cannot be honestly implemented: rather than hang\nforever waiting for input that can never arrive in this\narchitecture, or silently return immediately (misrepresenting a real\nprompt-and-wait as a no-op), modulePause fails cleanly when neither\nseconds nor minutes is given.\n\nArgs: seconds (int, optional); minutes (int, optional) — when both\nare given, they are summed (matching real pause's own documented\nbehavior of combining them); prompt, echo (accepted for\nshape-compatibility with real pause's argspec, but unused — they\nonly affect the interactive-prompt form this port doesn't support).\n\nThe sleep itself is delegated to `sleep N` on the TARGET via\nconn.Exec, not a control-node time.Sleep — unlike real pause (which\nsleeps on the controller and touches the target not at all), this is\nan architectural deviation forced by module.go's Func signature\nhaving no way to block the engine directly; the observable delay is\nthe same either way.\n",
"ping": "modulePing implements Ansible's `ping` module: a trivial connectivity\ncheck that returns \"pong\" (or the given `data`) on success.\n\nReal ansible.builtin.ping proves nothing beyond \"Python was\nsuccessfully copied to and executed on the target\" — it does no\nseparate round-trip over the wire. This port has no analogous\nmodule-copy step (every module already reaches the target only\nthrough conn.Exec), so there is nothing extra to prove by that\nmeasure. For parity we still perform a cheap no-op exec (`:`, the\nshell no-op builtin) via conn before returning, so a broken\nconnection surfaces as a `ping` failure the same way it would in\nreal Ansible, even though the command itself does nothing.\n\nArgs: data (string, default \"pong\") — echoed back in Extra[\"ping\"].\nIf data == \"crash\", fails deliberately, matching real\nansible.builtin.ping's documented crash-testing behavior.\n",
"pip": "modulePip implements (a subset of) Ansible's `pip` module.\n\nArgs: name (string or []string, required); state (present|absent,\ndefault \"present\"); executable (string, default \"pip3\").\n",
"raw": "moduleRaw implements Ansible's `raw` module: runs a command directly\nthrough the target's shell, with no module-wrapping at all.\n\nIn real Ansible, raw's whole reason to exist is that it (like\nscript) requires no Python on the target, unlike every other module\nwhich normally gets assembled into a Python script and copied over —\nthat distinction is meaningless in this port, since NO module in\nthis package has ever needed a target-side interpreter: every module\nhere, including command and shell, already runs on the control node\nand reaches the target only through conn.Exec/Put/Fetch (see\nmodule.go's package doc comment). So this port's raw is a thin\nnear-duplicate of moduleShell's core logic (run cmdStr through\nconn.Exec, no argv-tokenizing) — the two are behaviorally identical\nhere, which is a real, documented flattening of a distinction real\nAnsible cares about deeply and this architecture makes moot.\n\nArgs: cmd (or `_raw_params`, matching real raw's free_form parameter\nname as it's normally passed) — the command line, required. Unlike\nmoduleCommand/moduleShell, real ansible.builtin.raw's argspec has no\nchdir/creates/removes at all, so this port doesn't accept them\neither — passing them is silently ignored, not an error, since a\ncaller migrating a shell task to raw by habit shouldn't get a\nsurprising argument-validation failure.\n",
"reboot": "moduleReboot implements (a best-effort, DELIBERATELY INCOMPLETE\nsubset of) Ansible's `reboot` module: triggers a reboot on the\ntarget.\n\nReal ansible.builtin.reboot issues the reboot command, then closes\nand repeatedly re-opens its OWN connection until the target answers\nagain (comparing a boot-time marker to confirm it's a genuinely new\nboot, not the same one still shutting down) — real reboot's whole\nvalue is that \"wait for it to come back\" half. This port CANNOT do\nthat: a module here only has the single remoteexec.Connection it was\nhanded (see module.go's package doc comment); reconnecting a fresh\nConnection is the caller's (the playbook engine's) job, not a single\nmodule's, and this module has no way to ask the engine to do it.\nSo moduleReboot triggers the reboot and stops there — it does NOT,\nand architecturally cannot, wait for the host to come back. This is\na real, meaningful capability gap versus real reboot, not a cosmetic\none: a playbook task that runs immediately after this module in the\nsame play will very likely fail, since nothing here has confirmed\nthe target is reachable again. Documented plainly rather than faked\nwith a synthetic wait loop that can't actually observe reconnection.\n\nIssuing the reboot command is itself expected to make conn.Exec\nreturn a non-nil error (the target closes the connection out from\nunder the in-flight command as it goes down) — this module treats\nTHAT specific outcome as success (the reboot was, in fact,\ntriggered), not as a transport failure to propagate. A reboot\ncommand that returns cleanly before the connection drops (some\ntargets/transports do respond before tearing down) is treated as\nsuccess too.\n\nArgs: reboot_command (string, default \"reboot\"); msg,\nboot_time_command, pre_reboot_delay, post_reboot_delay,\nconnect_timeout, reboot_timeout, test_command (accepted for\nshape-compatibility with real reboot's argspec, but unused beyond\nreboot_command — see the wait-for-reconnect gap above; msg is\naccepted but not passed to reboot_command, since POSIX `reboot`\ntakes no message argument the way `shutdown` does).\n",
"replace": "moduleReplace implements Ansible's `replace` module: replaces every\nmatch of a regexp in a file's content with a replacement string\n(Go's regexp $1/${name} backreference syntax).\n\nArgs: path (string, required); regexp (string, required); replace\n(string, default \"\").\n",
"rpm_key": "moduleRpmKey implements (a subset of) Ansible's `rpm_key` module:\nimports or removes a GPG key from the RPM database, the RPM-family\nequivalent of apt_key.go.\n\nArgs: key (string, required) — a URL, a path to a key file on the\ntarget, or (for state=absent only) a key ID/fingerprint identifying\nan already-imported key; state (present|absent, default \"present\").\n\nSimplifications vs real rpm_key: no `fingerprint` verification (real\nrpm_key can cross-check an imported key's fingerprint against a\ncaller-supplied value; this port trusts `rpm --import` outright) and\nno `validate_certs`.\n\nIdempotency for state=present is NOT checked: rpm stores each\nimported key as its own pseudo-package named\n\"gpg-pubkey-<8-hex-id>-<8-hex-date>\", and telling whether `key`\n(a URL or file path) corresponds to an already-imported package\nrequires actually extracting the key's ID first (via `gpg` or `rpm\n-qp --qf`, neither guaranteed present) — out of scope for this\nbatch. `rpm --import` of an already-present key is itself a safe\nno-op server-side, so this port always runs it and reports changed,\nthe same \"can't cheaply tell already-there apart, so always act,\nwhich is safe but not idempotent-in-reporting\" tradeoff apt_repository\nPPA and dnf/apt \"latest\" already make elsewhere in this package.\n\nstate=absent looks up the matching gpg-pubkey-* package by a\ncase-insensitive substring match of `key` against `rpm -qa\n'gpg-pubkey-*'`'s output, then removes every match found — a\nbest-effort approach since `key` at this point is expected to be a\nshort ID/fingerprint substring of that generated package name, not\nthe URL/path form only valid for state=present.\n",
"script": "moduleScript implements (a subset of) Ansible's `script` module:\nuploads a local script to the target and runs it there.\n\nArgs: cmd (or `free_form`/`_raw_params`, matching how real script's\nfree-form parameter is normally passed) — the LOCAL script's path,\nfollowed by optional space-delimited arguments to pass it, required;\nchdir (string, optional) — a directory on the TARGET to run the\nscript from; creates, removes (string, optional, TARGET paths, same\nshort-circuit as command/shell); executable (string, optional) — an\ninterpreter to invoke the uploaded script with (e.g.\n\"/usr/bin/python3\"); when unset, the script is executed directly\n(relying on its own shebang line and the +x bit conn.Put sets via\nPutOptions.Executable).\n\nSplitting cmd into \"local path\" plus \"trailing args\" is done with a\nplain whitespace split (strings.Fields) — unlike real script (and\nunlike moduleCommand's tokenize, which understands quoting), a local\npath containing a space is not supported here.\n\nThe uploaded copy is removed from the target after running,\nbest-effort: a failure to remove it does not fail the task (the\nscript's own exit status is what matters; leaving a stray temp file\nbehind on a cleanup error is judged the lesser problem versus\nreporting an otherwise-successful script run as failed).\n",
"service": "moduleSystemd implements (a subset of) Ansible's `systemd` module.\n\nArgs: name (string, required); state (started|stopped|restarted|\nreloaded — optional, no default: when unset only `enabled` is\napplied); enabled (bool, optional).\n",
"service_facts": "moduleServiceFacts implements (a subset of) Ansible's `service_facts`\nmodule: gathers services into Extra[\"services\"], shaped like real\nansible_facts.services — a map from unit name to a map with \"name\",\n\"state\" (started|stopped, derived from systemd's ACTIVE column), and\n\"source\" (\"systemd\").\n\nArgs: none.\n\nOnly systemd-managed hosts are supported (checked via `command -v\nsystemctl`). Real ansible.builtin.service_facts additionally supports\nSysV, OpenRC, AIX SRC, and Solaris SMF backends via a chain of Python\nimplementations tried in turn; this port implements only the systemd\npath — the modern majority case — and fails cleanly (rather than\nsilently returning an empty list) when systemctl isn't found. This is\na real simplification versus real Ansible's broader backend coverage,\nnot a claim of equivalence.\n",
"set_fact": "moduleSetFact implements Ansible's `set_fact` module: every argument\nbecomes a fact (merged into ansible_facts / the variable scope by the\ncaller). It touches nothing on the target and is never reported as\n\"changed\", matching Ansible.\n",
"set_stats": "moduleSetStats implements (a subset of) Ansible's `set_stats` module:\naccumulates custom stats into the current run.\n\nReal ansible.builtin.set_stats works by handing `data` to the\nengine's core stats accumulator, which merges (or replaces, per\n`aggregate`) it into a running total that's visible at the end of the\nwhole play/run — a play-wide, cross-task accumulator this package's\nResult type has no equivalent of (Result carries one task's own\noutcome, not shared run-wide state; see module.go's Result doc\ncomment). Implementing real accumulation would mean either adding\nmutable global state to this package (a module.go-level change well\nbeyond what one module should decide) or having the playbook engine\nnotice `Extra[\"set_stats\"]` and thread it through itself — neither of\nwhich this batch's scope covers.\n\nSo this port stores `data` in Extra[\"set_stats\"] and stops there:\nnothing currently reads or aggregates it across tasks. A caller\nwanting play-wide stats today has to collect each task's\nExtra[\"set_stats\"] itself. This is a real, documented limitation, not\nfull parity with real set_stats — set_stats here behaves like a\nslightly fancier debug module, not an accumulator.\n\nArgs: data (map[string]any, required); per_host, aggregate (bool,\naccepted for shape-compatibility with real set_stats' argspec, but\nunused — both only matter to the accumulation this port doesn't do).\n",
"setup": "moduleSetup implements Ansible's `setup` module: gathers a portable\nsubset of system facts from the target — what `gather_facts:` uses\nunder the hood, and also what real Ansible lets a caller invoke\ndirectly (`ansible all -m setup`).\n\ngo-ansible already has a separate github.com/go-ansible/facts\npackage doing this same job for the playbook engine's own implicit\n`gather_facts:` step (see engine.go in the playbook module). This\nmodule delegates to that package's Gather function directly rather\nthan reimplementing fact-gathering here: facts.go's go.mod depends\nonly on github.com/go-remoteexec/transport (checked before adding\nthis dependency), so modules -> facts is a new one-directional edge,\nnot a cycle — facts does not, and structurally cannot, depend back on\nmodules or playbook. This keeps the probing logic in exactly one\nplace instead of forking it.\n\nArgs: fact_path, filter, gather_subset (accepted for shape-\ncompatibility with real ansible.builtin.setup's argument spec, but\nNOT implemented — this port's facts.Gather always collects its own\nfixed, portable subset and has no filtering, subsetting, or\nlocal-facts-directory support; passing any of these arguments is a\nsilent no-op, not an error, since real Ansible callers frequently\npass e.g. gather_subset even when every fact is wanted).\n",
"shell": "moduleShell implements Ansible's `shell` module: runs cmd through the\ntarget's real shell, so pipes/redirects/globs/`;` behave as they\nwould typed at a prompt.\n\nArgs: cmd (string) — the command line; chdir; creates; removes.\n",
"slurp": "moduleSlurp implements Ansible's `slurp` module: reads a file from\nthe target and returns its content base64-encoded — always base64,\nmatching real ansible.builtin.slurp, since the content may be binary\nand the transport in real Ansible is JSON (this port doesn't share\nthat constraint, but keeps the same contract for a caller expecting\nit).\n\nArgs: src (string, required; `path` accepted as an alias, matching\nreal ansible.builtin.slurp).\n",
"stat": "moduleStat implements Ansible's `stat` module: reports whether a path\nexists and, if so, its size/mode/type. Never changes anything.\n\nArgs: path (string, required).\n",
"subversion": "moduleSubversion implements (a subset of) Ansible's `subversion`\nmodule: checks out (or exports, or updates an existing checkout of)\na Subversion repository — the svn counterpart to git.go, following\nits same shape.\n\nArgs: repo (string, required — real subversion also accepts this as\n`name`/`repository`; this port only accepts `repo`, since aliasing is\nresolved by the caller before args reach a module — see module.go's\npackage doc comment); dest (string, required); revision (string,\ndefault \"HEAD\" — a real svn revision keyword/number); export (bool,\ndefault false) — export a clean tree with no .svn metadata instead\nof a working checkout.\n\nSimplifications vs real subversion: no `force` (discard local\nmodifications), `in_place`, `switch`, `password`, or `executable`\noverride support. export=true does NOT check whether dest already\nholds the wanted export — it always re-exports and reports changed,\nthe same \"can't cheaply tell already-there apart, so always act\"\ntradeoff apt_repository's PPA path and dnf/apt's \"latest\" state make\nelsewhere in this package (a real export has no metadata directory\nlike .svn to probe for staleness the way a checkout's svnversion\ndoes).\n",
"systemd": "moduleSystemd implements (a subset of) Ansible's `systemd` module.\n\nArgs: name (string, required); state (started|stopped|restarted|\nreloaded — optional, no default: when unset only `enabled` is\napplied); enabled (bool, optional).\n",
"systemd_service": "moduleSystemd implements (a subset of) Ansible's `systemd` module.\n\nArgs: name (string, required); state (started|stopped|restarted|\nreloaded — optional, no default: when unset only `enabled` is\napplied); enabled (bool, optional).\n",
"sysvinit": "moduleSysvinit implements (a narrow subset of) Ansible's `sysvinit`\nmodule: manages a service via its /etc/init.d/<name> script — the\nfallback for hosts that don't have systemd (systemd.go's module,\nwhich this port's `service`/`systemd`/`systemd_service` names all\nresolve to). Real sysvinit's whole value is coping with the wide\nvariety of init-script quality found in the wild (scripts with no\n`status` verb, daemons that need `daemonize`d supervision, distro-\nspecific enable/disable tooling); this port covers only the common,\nwell-behaved case.\n\nArgs: name (string, required); state (started|stopped|restarted|\nreloaded, optional — like systemd.go, when unset only `enabled` is\napplied); enabled (bool, optional); pattern (string, optional) — a\nsubstring to grep for in `ps` output, used as the \"is it running\"\ncheck INSTEAD of `/etc/init.d/<name> status` for init scripts that\ndon't implement status (matching real sysvinit's own documented\npurpose for this argument); sleep (int, default 1) — seconds to wait\nbetween an explicit stop and start when state=restarted; arguments\n(string, optional) — appended verbatim to the init script invocation.\n\nSimplifications vs real sysvinit: no `daemonize` (this port never\ndouble-forks/supervises anything itself — a module here always runs\non the control node, see module.go's package doc comment, so\n\"holding the tty\" doesn't apply the same way) and no `runlevels`\noverride (enable/disable always targets whatever chkconfig/\nupdate-rc.d's own defaults are). Unlike moduleSystemd's is-enabled\ncheck, enabling/disabling here is NOT idempotency-checked — this\nport always runs the enable/disable command and reports changed,\nsince reliably detecting \"already enabled\" differs by which of\nchkconfig or update-rc.d is present and isn't cheap to unify; the\nsame \"can't cheaply tell already-there apart, so always act, which\nis safe but not idempotent-in-reporting\" tradeoff apt_repository's\nPPA path makes elsewhere in this package.\n",
"tempfile": "moduleTempfile implements Ansible's `tempfile` module: creates a\ntemporary file or directory on the target via `mktemp`, always\nreported as changed (it always creates something new).\n\nArgs: state (file|directory, default \"file\"); path (directory to\nplace it in, default \"/tmp\" — real ansible.builtin.tempfile defaults\nto the system temp directory; conn.TempPath is deliberately not used\nhere, since it already builds a control-node-style unique path of its\nown, which doesn't compose with mktemp's XXXXXX templating); prefix\n(default \"ansible.\"); suffix (default \"\").\n\nThe template (dir/prefixXXXXXXsuffix) is passed to mktemp as a plain\npositional argument rather than via -p/--tmpdir: GNU mktemp supports\n-p but BSD/macOS mktemp does not, while a bare template path works\nidentically on both.\n",
"template": "moduleTemplate implements Ansible's `template` module: renders a\nlocal Jinja2 template file against the full variable context and\nwrites the result to dest, idempotently.\n\nArgs: src (string, required) — local template file path; dest\n(string, required); mode (octal string); _vars (map[string]any) —\nthe full variable scope to render with, distinct from this module's\nown args (matching Ansible's template action plugin, which renders\nagainst the whole variable scope, not just its own arguments — the\ncaller populates this key rather than letting normal per-arg\ntemplating handle it, since the FILE's content needs rendering, not\nan argument's string value).\n",
"unarchive": "moduleUnarchive implements (a subset of) Ansible's `unarchive`\nmodule: extracts an archive on the target, picking `tar` or `unzip`\nby the archive's file extension.\n\nArgs: src (string, required) — a local path (uploaded first via\nconn.Put) or, when remote_src=true, a path already on the target;\ndest (string, required) — must already exist, matching real\nunarchive's own documented requirement (this port does not create\nit); remote_src (bool, default false); creates (string, optional,\ntarget path — same short-circuit as command/shell/script).\n\nSupported archive types, by extension: .tar, .tar.gz/.tgz,\n.tar.bz2/.tbz2, .tar.xz/.txz (all via `tar`), and .zip (via\n`unzip`) — an unrecognized extension fails cleanly rather than\nguessing from the file's contents the way real unarchive (via\nPython's tarfile/zipfile sniffing) can.\n\nSimplifications vs real unarchive: no `include`/`exclude` member\nfiltering, `list_files`, `extra_opts`, `owner`/`group`/`mode`\npost-extraction, `validate_certs`, or `decrypt`. Idempotency is NOT\nchecked — real unarchive compares each archive member's checksum\nagainst what's already at dest and only extracts what changed; this\nport always extracts and reports changed, since replicating a\nper-member checksum comparison purely through shell composition was\njudged out of scope for this batch — a real gap versus real\nunarchive's idempotent behavior, documented rather than silently\nclaimed (the same tradeoff assemble.go makes, for the same reason).\n",
"uri": "moduleURI implements (a subset of) Ansible's `uri` module: issues an\nHTTP(S) request and checks the response status code.\n\nReal uri runs on the target already (Python was copied there before\nthe module ran); this port composes the request as a remote curl\ninvocation via conn.Exec instead of doing the HTTP request from the\ncontrol node — the control node may not share the target's network\nreachability (an internal service, a proxy only the target can see),\nand issuing the request from the wrong place would silently change\nwhat real uri observes.\n\nArgs: url (string, required); method (string, default \"GET\");\nstatus_code (int or []int, default [200]); body (string, optional);\nheaders (map[string]any, optional).\n\nSimplifications vs real uri: no digest/basic/WSSE auth, no\nbody_format encoding (body is sent as-is via curl's -d), no\nreturn_content/dest/redirect/timeout/SSL-tuning knobs. Status and\nbody are both captured from a single curl invocation using a\ntrailing marker line (\"\\nHTTPSTATUS:<code>\") rather than two separate\nrequests, so a non-idempotent method (POST, etc.) is only ever sent\nonce.\n",
"user": "moduleUser implements (a subset of) Ansible's `user` module.\n\nArgs: name (string, required); state (present|absent, default\n\"present\"); shell; home; groups ([]string, supplementary groups via\nusermod -G); system (bool) — pass -r/-system to useradd;\ncreate_home (bool, default true).\n",
"validate_argument_spec": "moduleValidateArgumentSpec implements (a subset of) Ansible's\n`validate_argument_spec` module: checks a set of provided arguments\nagainst an argument-spec dictionary shaped like AnsibleModule's own\n`argument_spec` (used inside roles for self-validation).\n\nArgs: argument_spec (map[string]any, required) — argument name ->\n{type, required, default, choices}; provided_arguments\n(map[string]any) — the arguments to validate. Real\nvalidate_argument_spec defaults provided_arguments to the calling\ntask's own args when the argument is omitted (via its action\nplugin's access to the task context); this port has no such\ncontext — a module here only ever sees the args map it was called\nwith (module.go's Func signature) — so provided_arguments is treated\nas empty (every non-defaulted arg reported missing) rather than\nmagically discovering \"the caller's own arguments\", a deliberate,\ndocumented deviation from real Ansible's exact contract.\n\nPer-argument checks: required (fails if absent and no default is\ngiven); type (a coarse Go-type check — see argMatchesType — for str,\nint, float, bool, list, dict, path, raw; an unrecognized type string\nis not itself an error, matching real Ansible's own leniency toward\ncustom/plugin-defined types this port doesn't know about); choices\n(value must equal one of the listed choices, compared via fmt.Sprint\nfor a loose string-shaped match rather than real Ansible's\ntype-aware equality).\n\nSimplifications vs real validate_argument_spec: no support for\nnested `options` (sub-argument specs for dict/list-of-dict\narguments), `elements` (per-element type checking within a list),\n`mutually_exclusive`/`required_together`/`required_one_of`/\n`required_if`/`required_by`, or argument aliasing. Every violation\nfound is collected and reported together in one Fail message\n(semicolon-joined) rather than stopping at the first one, so a\ncaller sees every problem in one run.\n",
"wait_for": "moduleWaitFor implements (a subset of) Ansible's `wait_for` module:\npolls the target until a port opens/closes or a path appears/\ndisappears, or (with neither given) just sleeps for `timeout`.\n\nThe poll loop is composed as a shell script and run once via\nconn.Exec, rather than as repeated Exec calls from the control node\n— a single command lets the target enforce its own timeout and\navoids one round-trip per poll. The port-reachability check uses\nbash's `/dev/tcp/HOST/PORT` pseudo-device, which is a bash\nextension, not POSIX sh — this port therefore explicitly invokes\n`bash -c` for the whole script (rather than relying on whatever\nshell the connection's Exec happens to use by default), so it needs\nbash to be present on the target regardless of the connection's own\ndefault shell.\n\nArgs: host (string, default \"127.0.0.1\"); port (int, optional); path\n(string, optional, mutually exclusive with port); timeout (int\nseconds, default 300); delay (int seconds, default 0); state\n(started|present|stopped|absent, default \"started\").\n\nSimplifications vs real wait_for: no search_regex, no\nactive_connection_states/drained handling, no exclude_hosts, no\nconnect_timeout distinct from the overall timeout.\n",
"wait_for_connection": "moduleWaitForConnection implements (a subset of) Ansible's\n`wait_for_connection` module: polls until the connection ITSELF is\nusable — unlike wait_for.go's `wait_for`, which polls for a port or\npath on an already-working connection.\n\nReal wait_for_connection repeatedly tears down and re-establishes\nits own transport connection (SSH/WinRM/etc.), which is exactly the\nscenario this needs to handle (a target mid-boot, or right after a\n`reboot` task, may refuse or reset connections entirely). This port\ncannot do that: a module here is handed one already-connected\nremoteexec.Connection and has no way to ask for a fresh one — only\nthe playbook engine dials connections (see moduleReboot's doc\ncomment for the same structural gap). The simplest HONEST\nimplementation available at a single module's level: retry a trivial\n`true` command on the connection ALREADY HELD, in a loop, until it\nsucceeds or the timeout elapses. This only actually detects \"the\nexisting connection recovered\" (e.g. an SSH multiplexed session that\nsilently reconnects underneath), NOT \"a freshly dialed connection to\na target that finished rebooting\" — if the underlying transport\ndoesn't itself retry/reconnect, this loop will just keep failing the\nsame way until it times out, which is a real, documented gap versus\nreal wait_for_connection's behavior.\n\nArgs: connect_timeout (int, default 5) — accepted for\nshape-compatibility with real wait_for_connection's argspec, but\nunused: Connection.Exec has no per-call timeout knob distinct from\nctx's own deadline, so there is nothing to apply this to. delay (int,\ndefault 0) — seconds to wait before the first attempt. sleep (int,\ndefault 1) — seconds between attempts. timeout (int, default 600) —\noverall deadline in seconds.\n",
"yum_repository": "moduleYumRepository implements (a subset of) Ansible's\n`yum_repository` module: writes (or removes) a `.repo` INI-style\nfile under /etc/yum.repos.d/ — the RPM-family counterpart of\napt_repository.go's plain-line path.\n\nArgs: name (string, required) — the repo's section id AND (when\n`file` is unset) its destination filename stem, matching real\nyum_repository's own default; file (string, optional, default =\nname); description (string, required when state=present — real\nyum_repository requires it too, since it becomes the repo's `name=`\nINI field); baseurl ([]string, required when state=present — this\nport does not support metalink/mirrorlist as alternatives, only\nbaseurl); gpgcheck (bool, optional); gpgkey ([]string, optional);\nenabled (bool, optional); state (present|absent, default \"present\").\n\nSimplifications vs real yum_repository: none of async, bandwidth,\ncost, countme, exclude, proxy, throttle, or the several dozen other\nyum.conf-section keys real yum_repository exposes are supported —\nonly the handful written above. Multi-value fields (baseurl, gpgkey)\nare written one value per line with NO leading whitespace on\ncontinuation lines; real yum_repository (and yum.conf's own INI\ndialect) indents continuation lines to mark them as part of the\nprevious key — omitting that indentation is invalid multi-value INI\nsyntax for a real yum.conf parser when more than one baseurl/gpgkey\nis given, a real, documented gap versus real yum_repository's exact\noutput.\n\nstate=absent removes the ENTIRE destination file, not just this\nrepo's `[name]` section — a deviation that only matters when\nmultiple repos share one `file` (real yum_repository can add/remove\none repo's section while leaving sibling sections in the same file\nuntouched; this port cannot, since it never parses the file's\nexisting sections, only ever overwrites or deletes it whole).\n\nIdempotency for state=present is checked by comparing the\ndestination file's existing content against the wanted stanza\nbyte-for-byte, the same pattern apt_repository.go and\ndeb822_repository.go use.\n",
}
Docs maps every registered module name to its Go doc comment — the same argument/deviation documentation this project has always written on each module<Name> function, extracted once at build time so ansible-doc can print it without the source tree present.
Functions ¶
This section is empty.
Types ¶
type Func ¶
type Func func(ctx context.Context, conn remoteexec.Connection, args map[string]any) (Result, error)
Func is a module's entry point. ctx carries cancellation/timeout; conn is already connected to the task's target; args is the task's parameters, already Jinja2-rendered by the caller (this package never templates anything itself). A non-nil error means the module could not determine an outcome at all (a transport failure); an expected failure is a Result with Failed=true and a nil error.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps module names to their Func.
func Default ¶
func Default() *Registry
Default returns a Registry pre-populated with this package's built-in module set.
func (*Registry) Register ¶
Register adds fn under name, replacing any existing module of the same name (so a caller can override a built-in with a custom module).
func (*Registry) Run ¶
func (r *Registry) Run(ctx context.Context, name string, conn remoteexec.Connection, args map[string]any) (Result, error)
Run looks up name and runs it, returning a Result{Failed:true} (not a Go error) for an unknown module name — matching Ansible's own "couldn't resolve module" being a task failure, not a crash.
type Result ¶
type Result struct {
Changed bool
Failed bool
Msg string
Facts map[string]any
Extra map[string]any
}
Result is a module's outcome: Ansible's changed/failed/msg triple, plus optional facts (merged into ansible_facts, e.g. by set_fact) and module-specific extra fields (e.g. command's stdout/stderr/rc).
func Fail ¶
Fail returns a failed result. Modules normally return this alongside a non-nil error only when the failure is unexpected (a connection error, an unreadable file); an expected, well-formed failure (e.g. the `fail` module itself, or `assert` on a false condition) returns it with a nil error, since it is not the module's own execution that went wrong.
Source Files
¶
- apt.go
- apt_key.go
- apt_repository.go
- args.go
- assemble.go
- async_status.go
- blockinfile.go
- command.go
- copy.go
- cron.go
- deb822_repository.go
- debconf.go
- debug.go
- dnf.go
- dnf5.go
- docs_generated.go
- dpkg_selections.go
- exec.go
- expect.go
- fetch.go
- file.go
- find.go
- gather_facts.go
- get_url.go
- getent.go
- git.go
- group.go
- hostname.go
- iptables.go
- known_hosts.go
- lineinfile.go
- module.go
- mount_facts.go
- package.go
- package_facts.go
- pause.go
- ping.go
- pip.go
- raw.go
- reboot.go
- registry.go
- replace.go
- rpm_key.go
- script.go
- service_facts.go
- set_stats.go
- setfact.go
- setup.go
- shellquote.go
- slurp.go
- stat.go
- subversion.go
- systemd.go
- sysvinit.go
- tempfile.go
- template.go
- unarchive.go
- uri.go
- user.go
- validate_argument_spec.go
- wait_for.go
- wait_for_connection.go
- yum_repository.go
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
gendocs
command
Command gendocs extracts each registered module's Go doc comment (already written on its module<Name> function — this project's convention has always been to document arguments and every deviation from real Ansible's behavior there) and emits docs_generated.go: a map from the module's registered name to that comment text, for ansible-doc to print without needing the source tree at runtime.
|
Command gendocs extracts each registered module's Go doc comment (already written on its module<Name> function — this project's convention has always been to document arguments and every deviation from real Ansible's behavior there) and emits docs_generated.go: a map from the module's registered name to that comment text, for ansible-doc to print without needing the source tree at runtime. |