Skip to content
Plan dependencies and loop outcomes

Plan dependencies and loop outcomes

Plan inspects resources without executing arbitrary commands. A command’s changed_when expression controls reporting; changed_when: false does not prove that the command is safe to execute during plan.

- !command
  name: Read current setting
  cmd: /usr/bin/printf enabled
  save: current_setting
  changed_when: false

- !command
  name: Converge setting
  cmd: /usr/bin/true
  with:
    condition: '(not (contains current_setting.out "enabled"))'

During plan the first task reports would run, and the dependent task reports deferred: depends on output unavailable until apply. Neither command executes. The recap has a separate Deferred count and reports an incomplete plan, rather than claiming that every host matched policy. A deferred outcome is neither a failure, a known change, an empty result, nor a matched resource. Plan can finish successfully while reporting these unknown outcomes.

Unavailable output bindings are tracked separately from actual saved host variables. Previously saved values cannot stand in for skipped producers. Tracking is scoped to each host; one host or item cannot overwrite another host’s availability. Known templated save names are resolved per invocation. Actual assignments clear the corresponding dependency, including passive tasks that execute normally during plan. Normal apply still executes commands and saves their actual results.

The shared condition evaluator checks unavailable values only when an operand is reached, preserving lazy and and or. For example, (and false (eq current_setting.out "enabled")) is false without reading the unavailable value. isdefined on an unavailable result is deferred, rather than treating an unknown future value as absent. Ordinary undefined variables and malformed conditions remain errors.

Dependent module parameters, iteration sources, aliases, and downstream saved results also propagate deferral. Template dependency checks are conservative: a reference to unavailable data in a template can defer it even if a template branch would not use that reference. Dynamic lookup into the root context checks the selected key at render time. No result is fabricated to make the condition pass or fail.

Prefer native resource queries and HTTP preflight check.matches when they express the desired state. Those inspect state through their established plan behavior. There is no implicit plan-time command execution or new read-only command assertion in this change.

Item loops

Each task declaration produces one aggregate result per host. Iteration still executes one item at a time; package batching and saved-result semantics are unchanged.

  • Any item needing work makes the plan report work, regardless of item order.
  • Any changed item makes apply report changed, including when later items match or skip. Standard handler notifications retain changes from earlier items.
  • All matched items report matched. Matched plus skipped reports matched; all skipped reports skipped. Passive results remain unchanged.
  • Different change categories combine as modification, with the union of changed fields. Failures retain normal immediate-stop/retry behavior; ignored failures remain visible in the aggregate.
  • Mixed known changes and deferred items retain both facts. The task reports the known work and its partial deferral; its Deferred recap count can overlap the change count. Purely deferred tasks do not count as unchanged.

A loop’s saved output continues to reflect normal per-item assignments; Jetpack also avoids presenting the final item’s command output as though it described the entire aggregate task.