Course outline

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

Choose modules and interpret their results

Use module contracts, structured return values, and observable second-run behavior rather than assuming every task is declarative or idempotent.

About 4 min readContent reviewed
On this page

Lesson goals

  • Distinguish state modules from command and shell execution.
  • Interpret changed, failed, skipped, and registered data.
  • Define meaningful idempotence and acceptance checks.

A module is an interface with platform requirements

Read the selected module’s argument specification, return values, connection assumptions, check-mode support, and platform requirements. Fully qualified names disambiguate modules from different collections. Similar names do not imply identical semantics. A generic interface may delegate to platform-specific behavior, so verify which backend is used when that matters. Avoid state: latest as an unexamined reproducibility policy; a desired package state should reflect the project’s version and update requirements. The course’s examples illustrate module contracts only and never install software or alter a configured target.

command and shell have different parsing boundaries

ansible.builtin.command invokes a command without general shell operators such as pipelines and redirection. ansible.builtin.shell introduces shell interpretation and therefore another data-to-code boundary. Prefer a purpose-built state module when available and appropriate. If a command is necessary, use documented arguments, output interpretation, and change/failure rules; creates or removes can guard some operations but do not prove the resulting content is correct. Never interpolate untrusted values into shell text casually. A task that runs an imperative command is not automatically idempotent because the surrounding playbook is YAML.

Registered results are structured observations

register stores the result of a task for later decisions. Fields depend on the module and execution path; a skipped task or check-mode prediction may not contain the same data as a normal execution. Use tests such as is failed or is skipped where appropriate and guard access to optional fields. Do not compare a complex result dictionary to a string such as success. A return code can be expected data for one command and a failure for another, so the module and application contracts must determine interpretation. Keep useful data separate from logs and secret-bearing values.

yaml Illustrative registration and field access
- name: Describe a synthetic result
  hosts: lab_nodes
  gather_facts: false
  tasks:
    - name: Produce a harmless illustrative message
      ansible.builtin.debug:
        msg: Synthetic report
      register: lab_result
    - name: Inspect only the declared message field
      ansible.builtin.debug:
        var: lab_result.msg

The YAML demonstrates a debug-result field under the documented module behavior. It remains non-runnable; no engine result or host execution is claimed.

changed is a reported state transition

A module reports changed when it considers its operation to have altered state or predicts alteration in supported check mode. That flag drives handlers and summaries, so overriding it can affect orchestration correctness. changed_when: false may be appropriate for a genuinely read-only command, but it does not make an unsafe command harmless or idempotent. Conversely, an always-changing template can cause needless downstream actions. Examine observed state, desired state, and stable inputs. The offline model compares dictionaries only; it is not a simulation of a particular module’s full change-detection logic.

python Model convergence and a stable repeat offline
desired = {'enabled': True, 'label': 'review'}
first_observed = {'enabled': False, 'label': 'review'}
second_observed = desired.copy()
print('first_changed=' + str(first_observed != desired))
print('second_changed=' + str(second_observed != desired))
Expected output
first_changed=True
second_changed=False

The second comparison is unchanged because its synthetic state equals the desired state. No module, service, or configuration is accessed, and health remains a separate question.

Test stable repetition and useful behavior independently

A meaningful idempotence test repeats the same intended operation against a controlled disposable state and checks that unnecessary changes stop. Then verify the application’s required behavior, including configuration validation and recovery semantics. A second run can legitimately change if inputs or external state changed, so preserve those assumptions in the report. Do not suppress change reporting merely to satisfy a green test. Test partial failure and rerun behavior too: recovery may differ from a clean first run. These execution tests are described as a future authorized lab plan, not performed on this machine.

Common errors and fixes

A shell task is assumed idempotent

Why it happens
Imperative execution was mistaken for state convergence.
How to fix it
Choose a state module or explicit verified guards.
Prevent it next time
Test a stable second run honestly.

changed_when hides an unsafe effect

Why it happens
Reporting policy was confused with execution behavior.
How to fix it
Review the actual operation and keep truthful change reporting.
Prevent it next time
Never use summary flags as a safety mechanism.

A skipped result is treated as a normal result

Why it happens
Optional return fields were accessed unconditionally.
How to fix it
Check execution state and documented fields first.
Prevent it next time
Include skipped and check-mode paths in tests.

Check your understanding

1. Why is changed_when: false not an idempotence guarantee?

Show worked solution

It changes reported status, not what the operation actually does. A task can repeat side effects while claiming unchanged. Validate real state transitions and prefer a module whose contract expresses the desired state.

2. What should a test distinguish after a second unchanged run?

Show worked solution

It should distinguish configuration convergence from application acceptance. The files or settings may match while the application remains unhealthy. Verify required behavior separately and retain the exact input and environment assumptions.

Key takeaways

  • Read module contracts and preserve structured result meaning.
  • Keep truthful change detection separate from safety and health.

Official references