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