class

Krikri::TaskExecutor

Inherits Reference < Object

TaskExecutor - Executes tasks on hosts Orchestrates task execution, variable substitution, and handler management

Constants

INLINE_COPY_MAX_BYTES = (8 * 1024) * 1024

copy.cr runs on the target host (uploaded there like every other plugin, unlike template: - an action plugin that runs on the controller and already handles this correctly by reading its own src: file locally before dispatch). A copy: task's src: names a file on the controller, so copy.cr's own File.exists?(src) check - a plain local filesystem check, from the perspective of wherever it's actually running - can never find it once the play targets a genuinely remote host: the file was never transferred there. Found via konstruktoid-hardening's "Add cracklib password list" (its only copy: task with a real src: file, previously entirely untested territory) - failed with "Source file not found" citing the exact real (and really-existing-on-the-controller) path.

Read here, on the controller, and forwarded as content: instead - copy.cr already has a fully-working content-write path (used by any copy: {content: ..., dest: ...} task), so this reuses it rather than needing a separate upload mechanism. Left alone for remote_src: true (real Ansible's own remote-to-remote copy, where src: already refers to a path on the target, not the controller - reading it here would be wrong) and for a local connection (copy.cr already runs on the same filesystem as the controller in that case, so src: already resolves correctly as-is - converting it anyway would only change check-mode's message ("Would copy SRC to DEST" -> a generic content message) for no actual correctness gain). Above this size, embedding the file as a content param string would make the JSON config too large to safely round-trip through #execute_remote_plugin's own base64 encoding of the whole config (see the comment on that call): a real bug found benchmarking ansible-community.ansible-vault's own "Install Vault" task, which copy:s a ~530MB downloaded Vault release binary - Crystal stdlib's Base64.encode_size computes str_size * 4 as native Int32 arithmetic before the final .to_i, and a base64'd-then- JSON-escaped-then-base64'd-again 530MB payload comfortably clears 2^31, crashing the whole engine with an unhandled OverflowError partway through a run - not a graceful per-task failure.

Constructors

new(hosts : Array(Krikri::Host), tasks : Array(Krikri::Task), handlers : Array(Krikri::Task) = [] of Task, check_mode : Bool = false, diff_mode : Bool = false, verbosity : Int32 = 0, play_vars : Hash(String, JSON::Any) = {} of String => JSON::Any, all_role_defaults : Hash(String, JSON::Any) = {} of String => JSON::Any, all_role_vars : Hash(String, JSON::Any) = {} of String => JSON::Any, gather_facts : Bool = true, inventory : Nil | Krikri::Inventory = nil, inventory_path : Nil | String = nil, batching_enabled : Bool = true, forks : Int32 = 5, smart_gathering : Bool = false, fact_store : Hash(String, Hash(String, JSON::Any)) | Nil = nil, set_fact_store : Hash(String, Hash(String, JSON::Any)) | Nil = nil, adhoc : Bool = false, extra_vars : Hash(String, JSON::Any) = {} of String => JSON::Any, force_handlers : Bool = false, vars_files : Array(Array(String)) = [] of Array(String), vars_files_dir : String = ".", playbook_dir : String = ".", any_errors_fatal : Bool = false, max_fail_percentage : Float64 | Nil = nil, unreachable_hosts : Set(String) = Set(String).new, strategy : String | Nil = nil, gather_subset : Array(String) = [] of String, gather_timeout : Int64 | Nil = nil, fact_path : String | Nil = nil, remote_user : String | Nil = nil, debugger : String | Nil = nil, playbook_file : String | Nil = nil)
Source

Instance methods

check_mode=(check_mode : Bool)
Source
check_mode?
Source
cleared_error_hosts

Hosts a real failure halted in this play whose error state was since cleared via meta: clear_host_errors. Real Ansible's own documented semantics: "makes them available for targeting in subsequent plays, but not continue execution in the current play" - so, unlike ended_hosts, these stay in halted_hosts (the current play still stops for them) but are excluded from krikri-playbook.cr's cross-play carry-forward the same way.

Source
diff_mode=(diff_mode : Bool)
Source
diff_mode?
Source
ended_hosts

Subset of halted_hosts that got there via a CLEAN meta: end_host/ end_play, not a real task failure. Real Ansible's own semantics: "causes the play to end WITHOUT FAILING the host(s)" - such a host must still be excluded from the REST OF THIS PLAY (the existing halted_hosts mechanism already does that for free, including correctly propagating out of block:/rescue:/always: nesting and suppressing its own pending notified handlers - both verified against real ansible-playbook to behave identically to a real failure for THIS play), but must NOT be treated as a failure by krikri-playbook.cr's cross-play carry-forward (permanently_failed_ hosts) or count toward the run's overall failed/exit-code status.

Source
gather_facts=(gather_facts : Bool)
Source
gather_facts?
Source
halted_hosts

Hosts that hit a failed task without ignore_errors: further tasks in the play are skipped for them (Ansible's default "a failure aborts the rest of the play for that host" behavior). Public - crystal- play.cr reads this after #run to carry a failed host forward and exclude it from every remaining play in the whole run too, not just the rest of this one (real Ansible's actual behavior; see git log's 0.9.61-found, 0.9.64-fixed cross-cutting engine gap commit).

Source
handlers
Source
handlers=(handlers : Array(Task))
Source
hosts
Source
hosts=(hosts : Array(Host))
Source
play_aborted

True once this play should stop for EVERY host. Also records the decision so krikri-playbook.cr can skip the remaining serial: batches

  • real Ansible does not start the next batch after an abort (verified: serial: 1 + any_errors_fatal with h2 failing runs h1's batch fully, then stops; h3 never runs at all).
Source
play_vars
Source
play_vars=(play_vars : Hash(String, JSON::Any))
Source
reachable_unavailable_modules

Modules referenced by a task whose own when: (independent of the forced-skip #when_passes? always takes for an unavailable module) would have evaluated true for at least one host - i.e. genuinely REACHED, not merely present somewhere in the playbook text. Real Ansible only ever attempts module resolution for a task it's about to run, so a module referenced only inside a branch that's unreached on every host (when: ansible_os_family == "Suse" on an Ubuntu run) never contributes to its exit code - the previous whole-playbook static scan (PlaybookParser.unavailable_modules) flagged ANY unresolvable module name found anywhere in the file regardless of reachability, producing a false-positive exit 4 divergence from real Ansible's own (often 0 or 2) exit code on every such role. Found via buluma.jenkins's own zypper_repository task (round 180), gated behind an OS-family branch that's false on every host this project benchmarks against (Ubuntu/RHEL-family).

Source
results

Track results for recap

Source
run

Main execution loop

Source
show_recap
Source
tasks
Source
tasks=(tasks : Array(Task))
Source
verbosity

Real ansible-playbook's -v/-vv/-vvv/... count, exposed to task templating as the ansible_verbosity magic var. See #build_vars_context's own assignment for the full rationale.

Source
verbosity=(verbosity : Int32)

Real ansible-playbook's -v/-vv/-vvv/... count, exposed to task templating as the ansible_verbosity magic var. See #build_vars_context's own assignment for the full rationale.

Source

Nested types