Writing Idempotent Ansible Playbooks with Honest Check Mode
Idempotency Is an Availability Property
An idempotent playbook describes the desired state and reports changed only when it moves a host toward that state. Re-running it should be safe. This matters because operators retry after partial failures, pipelines reconcile regularly, and emergency runs happen under pressure.
Prefer a module that understands the resource:
- name: Create the application directory
ansible.builtin.file:
path: /srv/catalog
state: directory
owner: catalog
group: catalog
mode: "0750"
- name: Set the listening port once
ansible.builtin.lineinfile:
path: /srv/catalog/config.ini
regexp: '^listen_port\s*='
line: 'listen_port = 8080'
create: true
mode: "0644"
Compare that with shell: mkdir ... and echo ... >> config.ini. The shell cannot infer desired state, append duplicates accumulate, and every run may look changed.
---
When a Command Is Legitimate
Some vendor tools have no module. Give Ansible enough information to report honestly:
- name: Build the search index once
ansible.builtin.command: /opt/catalog/bin/build-index
args:
creates: /srv/catalog/index.db
- name: Check application drift
ansible.builtin.command: /opt/catalog/bin/drift-check
register: drift
changed_when: "'DRIFT' in drift.stdout"
failed_when: drift.rc not in [0, 2]
Do not set changed_when: false merely to silence noisy output. That can suppress a handler that should restart a service after real change.
Understand Check Mode's Limits
--check predicts changes for modules that support it. It does not create a temporary future state, so a later task may fail because a file an earlier task would have created is still absent. Commands and external APIs may not support prediction at all.
Use --check --diff as review evidence, not proof of correctness. Mark unsupported tasks explicitly, avoid real writes during dry runs, and verify on an ephemeral target.
The Two-Run Test
CI should run the playbook once, run it again, and assert the second recap reports zero changes. Then verify the application state independently. This catches duplicate lines, dishonest commands, templates containing volatile timestamps, and handlers firing every run.
Apply the workflow in Stop the Duplicate Config, Honest Command Semantics, and Make Check Mode Safe.