The server somebody fixed by hand
Idempotence means that running the same playbook again changes nothing once the machine is right. Drift is when the real machine stops matching its description. It matters because a playbook that is safe to run again can also find a hand edit and undo it.
At two in the morning a shop's notice says Open when it should say Closed. Somebody edited the file on the live server and told nobody.
By the end of this lesson
Note
I can run a playbook twice and say what changed and why.
Key words
| Word | What it means | Example |
|---|---|---|
| Idempotent | running it again changes nothing more | |
| Drift | the real thing no longer matches its description | |
| Check mode | a run that reports changes without making them | --check |
| Diff | the lines that would change | --diff |
| Recap | the totals printed at the end of a run |
The cells students fill in are left blank in this preview.
A playbook describes the end, not the steps
A task says how a file should be, not what to type. If the file is already right, the task does nothing and reports ok. If it is not, the task fixes it and reports changed.
So a good playbook is idempotent: the second run reports no changes.
Make a folder and go in
mkdir -p ~/iac-demo/drift && cd ~/iac-demo/driftShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write a playbook with one task
printf '%s\n' '- hosts: localhost' ' connection: local' ' gather_facts: false' ' vars:' ' hours: "Open 9 to 5"' ' tasks:' ' - name: write the notice' ' ansible.builtin.copy:' ' dest: ./notice.txt' ' content: "{{ hours }}\n"' ' mode: "0644"' > site.ymlShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it the first time
ansible-playbook -i localhost, site.yml | grep -E "changed="Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it again
ansible-playbook -i localhost, site.yml | grep -E "changed="Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Drift is a change nobody wrote down
Drift is when the real thing stops matching the description, often because somebody edited it by hand. Idempotence is what makes drift easy to repair.
Run the playbook in check mode first. It reports what it would change, and --diff shows the lines, but nothing is written.
Edit the file by hand
echo "Closed all week" > notice.txt; cat notice.txtShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Look first with check mode and a diff
ansible-playbook -i localhost, site.yml --check --diff | grep -E "^[-+][^-+]|changed="Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Read the file again
cat notice.txtShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it for real, then once more
ansible-playbook -i localhost, site.yml | grep -E "changed="; cat notice.txt; ansible-playbook -i localhost, site.yml | grep -E "changed="Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A shell task cannot see a change
The copy module compares the file with the wish. The shell module only runs a command, so it cannot know whether anything changed. It reports changed every time.
The Ansible lab refuses a playbook that is only shell commands for this reason.
Write the same job as a shell command
printf '%s\n' '- hosts: localhost' ' connection: local' ' gather_facts: false' ' tasks:' ' - name: write with a shell' ' ansible.builtin.shell: echo "Open 9 to 5" > ./shell.txt' > shell.ymlShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it twice
ansible-playbook -i localhost, shell.yml | grep -E "changed="; ansible-playbook -i localhost, shell.yml | grep -E "changed="Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think changed=0 on the first run means the playbook did nothing useful. It means the machine already matched the wish. A run that always says changed hides drift, because a real change looks the same as a normal one.
OpenTofu finds drift by refreshing the state
With a real provider, tofu plan first asks the real service what exists, and shows any difference from the state. That is how it notices a hand edit.
The lab's terraform_data stores a value and nothing outside OpenTofu, so there is nothing to edit by hand. This lesson uses a file instead.
A convergence worked out in Python
# Bring a machine to the wish and count what changed.
wish = {"hours": "Open 9 to 5", "phone": "0100"}
machine = {"hours": "Closed", "extra": "keep me"}
changed = 0
for name in wish:
if name not in machine or machine[name] != wish[name]:
machine[name] = wish[name]
changed = changed + 1
print("changed =", changed)
# Run the loop again, and then look at changed.
Going further: change the wish
Run ansible-playbook -i localhost, site.yml -e '{"hours": "Open 10 to 4"}' | grep -E "changed=" and then cat notice.txt. Run the same command again.
Why does the first run change the file and the second not, and which variable won?