module

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 own changed = changed_marker in out test (with the documented link_dest exception, where a leading . itemize char means "hard-linked, no change")

Constants

CHANGED_MARKER = "<<CHANGED>>"
QUOTED_VALUE_PREFIXES = ["--rsh=", "--rsync-path=", "--out-format=", "--link-dest="]

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

bool(value : String | Nil, default : Bool = false) : Bool
Source
bool_opt(value : String | Nil) : Bool | Nil

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).

Source
build_argv(src : String, dest : String, params : Hash(String, String), private_key : String | Nil = nil, dest_port : Int32 | Nil = nil, password : String | Nil = nil, wrap_rsh_sshpass : Bool = true) : Array(String)

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.

Source
clean_output(output : String) : String

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.

Source
cmd_string(argv : Array(String)) : String
Source
format_rsh_target(host_addr : String, path : String, user : String | Nil) : String

_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.

Source
needs_rsh?(src : String, dest : String) : Bool

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.

Source
parse_list(value : String | Nil) : Array(String)

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).

Source
resolve_bin_path(name : String) : String

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.

Source
run(argv : Array(String), env : Hash(String, String) | Nil = nil) : RsyncResult
Source

Nested types