Course outline

Ansible / Basic: execution, inventory, and typed state / Lesson 6

Use facts, conditions, and loops with typed inputs

Separate discovered facts from desired configuration, write Boolean conditions, and preserve per-item results and scope.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain fact availability, freshness, and collection cost.
  • Use when as an expression with explicit Boolean meaning.
  • Interpret loops and their registered result lists.

Facts are observations with scope and age

Gathered facts describe properties of a managed system and are commonly accessed through ansible_facts. They are not desired configuration and may differ from project policy. Gathering facts can connect to targets, execute discovery, require dependencies, and expose sensitive system information. Cached facts can be stale, while disabled gathering can leave expected data absent. Use the minimal relevant facts and record when they were obtained. Do not assume a top-level injected fact name is available under every configuration. Explicit ansible_facts access often makes the source clearer and reduces accidental naming collisions.

A condition should evaluate to a Boolean

when contains an expression rather than a templated display string, so ordinary conditions should not be wrapped in {{ }}. Modern ansible-core rejects many accidental truthy non-Boolean results exposed by the templating overhaul. Use explicit comparisons, tests, and deliberate conversion when the input contract allows textual flags. A list of conditions is combined according to the documented rules, which differs from an informal expectation that any item suffices. Guard undefined data appropriately and fail required missing input clearly. A task skipped by a condition is not a failed task and can produce a different result shape.

Loop data is a list with per-item meaning

loop expects a list-oriented input. Iterate dictionaries through an explicit supported transformation when needed, rather than relying on an undefined implicit conversion. Use loop_control.loop_var for nested or reusable content to avoid item collisions, and label results without leaking secret-bearing values. A condition on a loop task is evaluated in the relevant item context. Registered loop output contains a results collection, not merely one module result. Preserve item identity when aggregating success, failure, and change so an overall summary does not hide which input produced the problem.

yaml Illustrative typed loop and item-specific condition
- name: Describe selected synthetic services
  hosts: lab_nodes
  gather_facts: false
  vars:
    lab_services:
      - name: report
        enabled: true
      - name: archive
        enabled: false
  tasks:
    - name: Describe an enabled service
      ansible.builtin.debug:
        msg: "Selected {{ service.name }}"
      loop: "{{ lab_services }}"
      loop_control:
        loop_var: service
        label: "{{ service.name }}"
      when: service.enabled

The YAML uses explicit Boolean inputs and a named loop variable. It was parsed only; no managed host, fact collection, or debug task was executed.

Test selection intent with a limited synthetic model

A small offline filter can validate a project’s declared data schema and expected selected names. It does not reproduce Ansible’s Jinja evaluation, undefined behavior, or loop result machinery. Be explicit about that limitation, especially because Python and Jinja truth tests are not identical to every Ansible conditional rule. Check types before selecting rather than letting a nonempty string act as true. Keep a rejection case for malformed flags and a stable ordering contract when reports depend on order. This is a useful preflight test of data intent, not engine execution.

python Validate typed selection intent offline
services = [{'name': 'report', 'enabled': True}, {'name': 'archive', 'enabled': False}]
assert all(type(service['enabled']) is bool for service in services)
selected = [service['name'] for service in services if service['enabled']]
print(','.join(selected))
Expected output
report

The model explicitly rejects non-Boolean enabled values before filtering. It prints report only and does not claim to execute a when expression or Ansible loop.

Keep observation and policy responsibilities separate

A fact-based branch can be useful for platform adaptation, but desired deployment policy should not drift merely because an incidental fact changed. Validate supported platforms and versions explicitly. A loop over discovered resources can also expand scope unexpectedly when the discovery source changes, so review counts and exclusions before effects. Facts, conditions, and loops together can make a concise task deceptively broad. Test empty lists, missing required fields, disabled entries, and malformed types using synthetic fixtures. Any real gathering, platform discovery, or task execution belongs to a separately authorized disposable environment.

Common errors and fixes

A string flag is used as an implicit condition

Why it happens
The data schema lacks a Boolean contract.
How to fix it
Validate or deliberately convert the allowed input.
Prevent it next time
Test malformed and false values explicitly.

A loop register is accessed like one result

Why it happens
The per-item results structure was ignored.
How to fix it
Inspect results with item identity and documented fields.
Prevent it next time
Include multiple-item failure cases.

A cached fact is treated as current policy

Why it happens
Observation age and desired state were conflated.
How to fix it
Check freshness and keep policy separately owned.
Prevent it next time
Document fact source and validity assumptions.

Check your understanding

1. Why is when: "{{ enabled }}" a poor default style for a Boolean condition?

Show worked solution

when already evaluates an expression. Extra template delimiters obscure the intended type and can trigger stricter templating errors. Use a validated Boolean variable or explicit expression and test the supported engine behavior.

2. What should a loop-result report preserve besides an overall failed flag?

Show worked solution

Preserve each item’s identity, execution state, relevant output, change indication, and failure details without exposing secrets. The aggregate should be derived from a documented policy so one skipped or optional item is not confused with a required failure.

Key takeaways

  • Treat facts as scoped observations and inputs as typed data.
  • Preserve item identity through conditions and result aggregation.

Official references