Krikri::SynchronizeRsync
The rsync-invocation core of ansible.posix.synchronize, shared by both entry points that need it:
- SynchronizeActionPlugin (controller-side action plugin - the normal task-execution path; see its own file for why synchronize is controller-side here, same as Ansible)
- plugins/synchronize.cr (the standalone/fat plugin binary, kept for
--async/manual invocation on whatever host the "local" rsync end is - same split as debug/pause)
Behavior matched to Ansible.posix's the Ansible module (the
module half; the action-plugin half only munges src/dest into remote
user@host:path form and resolves the private key/port before handing
over). Argument order, flag spelling, and the itemize-changes protocol
all follow that source verbatim:
- every flag rsync gets is the Ansible module's own (including the non-obvious ones: --delete-after for delete:, --delay-updates -F by default, --archive plus explicit --no-X for each toggle that was EXPLICITLY turned off under a default-on archive:)
- changed detection is rsync's own: the command always runs with
--out-format=<<CHANGED>>%i %n%L, so every item rsync actually created/updated/attribute-changed/deleted gets a<<CHANGED>>- prefixed itemize line and an untouched tree prints nothing at all - "any marker line => changed" is exactly Ansible's ownchanged = changed_marker in outtest (with the documented link_dest exception, where a leading.itemize char means "hard-linked, no change")
Constants
The command STRING the Ansible module reports as cmd: every element
space-joined, with shlex.quote applied exactly where the real
module applies it - the values of --rsh=, --rsync-path=,
--out-format= and --link-dest=, plus the trailing src and dest
(cmd.append(shlex_quote(source)) / cmd.append(shlex_quote(dest))).
Safe tokens stay bare (Shell.quote_arg mirrors shlex.quote), so
plain local paths render unquoted exactly like Ansible's.
Class methods
Tri-state form: nil when the param is absent (the caller decides what "absent" follows - for the archive toggles that's archive's own value, per the Ansible module's type: bool-without-default spec).
Builds the full rsync argv for one synchronize invocation. src and
dest must already be FINAL (the caller munged remote ends into
user@host:path form first); everything else is read from params
with the Ansible module's own defaults. private_key/dest_port are
the caller-resolved connection values (param override, then
inventory vars) that feed the --rsh= ssh command when either path
is remote (contains ':'). password (the caller-resolved
ansible_password/ansible_ssh_pass - controller-side rsync only) wraps
that rsh ssh in sshpass -e - but only when wrap_rsh_sshpass is
also true (the controller-side caller passes
SSHManager.sshpass_available?; without sshpass the ssh stays
unwrapped and inherits the askpass env overlay from #run's env,
matching the delivery mechanism every other spawn site picks); the
password itself travels in SSHPASS (see #run's env), never in
the argv or the rsh string.
The Ansible module's changed test: any itemize line at all - except the
link_dest case, where rsync prints a leading-. itemize char for
each file it hard-linked WITHOUT changing, and the Ansible module's own
test is literally (changed_marker + '.') not in out.
Strips the markers back off, keeping one itemize line (marker
removed) per real change - the Ansible module's msg/stdout_lines/diff
payload. Ansible's msg is out.replace(changed_marker, ''): the raw
rsync stdout WITH its trailing newline (round 995004
synchronize_push: real msg ends 'a\n', a join("\n") loses that).
stdout_lines still drops empty lines, exactly like Ansible's split +
remove-'' loop.
_format_rsync_rsh_target - builds user@host:path for the remote
end, preserving an already-qualified path (user@host:path as written
in the task, or an rsync:// URL) untouched. IPv6 hosts get the
[user@addr]:path bracket form rsync's ssh transport requires.
True when either final path is an rsync remote spec (host:path,
user@host:path, or rsync:// URL) - the Ansible module's
is_rsh_needed. Two plain local paths (the delegate_to: localhost
idiom) need no remote shell.
List-shaped params arrive as strings (the wire format every plugin gets) - either a JSON array or comma-separated. Same defensive re-parse find.cr/apt.cr document (a YAML one-element list otherwise becomes ONE string containing literal brackets/quotes).
Ansible module: if '/' not in rsync: rsync = get_bin_path(rsync, required=True) - the reported cmd carries the RESOLVED path
(/usr/bin/rsync), not the bare name. Runs wherever the module
itself runs: controller-side in the action plugin's local-rsync
branches, on the host in the standalone module binary.