Introduction
Idempotence means a playbook can be run repeatedly and only changes the system when the desired state is missing or different.
In this lab, you will use common Ansible modules to create directories and configuration files for host aliases, then run the playbook twice to prove the second run is stable.
Create an Idempotence Practice Project
In this step, you will create a small Ansible project with two local managed host aliases. Each alias writes to its own directory so you can see host-specific results while staying inside one RHEL 10 VM.
Start from the default LabEx workspace.
cd /home/labex/project
The option -p tells mkdir to create parent directories when needed and to succeed if a directory already exists.
mkdir -p rhce-ansible/{inventory/group_vars,playbooks,managed,reports}
cd rhce-ansible
Create project defaults that point Ansible at your inventory and keep retry files disabled.
cat > ansible.cfg <<'EOF'
[defaults]
inventory = ./inventory/hosts
host_key_checking = False
retry_files_enabled = False
EOF
The managed directory will hold the files produced by the playbook, while reports will hold saved playbook output. Keeping those separate makes it easier to compare desired content with Ansible's run evidence.
Define Local Managed Hosts and Variables
In this step, you will create an inventory and group variables. The aliases use ansible_connection=local, which means Ansible targets named hosts while executing safely on this VM.
Before running commands in this step, return to the Ansible project directory. This keeps the commands stable even when LabEx runs each step separately.
cd /home/labex/project/rhce-ansible
Create two app host aliases in one group.
cat > inventory/hosts <<'EOF'
[app]
app1 ansible_connection=local
app2 ansible_connection=local
EOF
Group variables describe the desired state shared by every host in the group.
cat > inventory/group_vars/app.yml <<'EOF'
app_owner: labex
app_environment: training
app_message: Managed by Ansible common modules
EOF
Confirm that Ansible can see the two hosts.
ansible app --list-hosts
The output should list app1 and app2. This check is useful before writing a playbook because a play targeting app can only affect hosts that inventory resolves into that group.
Write a Desired-State Playbook
In this step, you will write a playbook that uses file, copy, and lineinfile. These modules describe the final state instead of manually running shell commands.
Before running commands in this step, return to the Ansible project directory. This keeps the commands stable even when LabEx runs each step separately.
cd /home/labex/project/rhce-ansible
Create the playbook. The paths include {{ inventory_hostname }} so each alias gets its own output directory.
cat > playbooks/common-modules.yml <<'EOF'
---
- name: Configure app host directories with common modules
hosts: app
gather_facts: false
tasks:
- name: Ensure host directory exists
ansible.builtin.file:
path: "/home/labex/project/rhce-ansible/managed/{{ inventory_hostname }}"
state: directory
mode: '0755'
- name: Install base application configuration
ansible.builtin.copy:
dest: "/home/labex/project/rhce-ansible/managed/{{ inventory_hostname }}/app.conf"
content: |
owner={{ app_owner }}
environment={{ app_environment }}
message={{ app_message }}
mode: '0644'
- name: Ensure managed message is present
ansible.builtin.lineinfile:
path: "/home/labex/project/rhce-ansible/managed/{{ inventory_hostname }}/app.conf"
line: "message={{ app_message }}"
create: true
mode: '0644'
EOF
The file task creates the directory, the copy task writes the complete configuration, and the lineinfile task demonstrates how to enforce a single line. These modules compare current state with desired state, which is why they can be idempotent.
Use syntax check before running a new playbook.
ansible-playbook playbooks/common-modules.yml --syntax-check
Run the Playbook and Inspect the Files
In this step, you will run the desired-state playbook for the first time. The first run should report changes because the target files do not exist yet. Before running commands in this step, return to the Ansible project directory. This keeps the commands stable even when LabEx runs each step separately.
cd /home/labex/project/rhce-ansible
Run the playbook and save the output for later comparison.
ansible-playbook playbooks/common-modules.yml | tee reports/first-run.txt
The first run should report changes because the host directories and app.conf files are new. tee lets you watch the run while also saving the same output for the idempotence check.
Inspect the host-specific files that Ansible created.
cat managed/app1/app.conf managed/app2/app.conf
Both files should contain the same owner, environment, and message values, but they live under different host alias directories. That shows the playbook applied the same desired state to each inventory host.
Run the Playbook Again to Prove Idempotence
In this step, you will run the same playbook again. Because the files already match the desired state, Ansible should report zero changes for both hosts. Before running commands in this step, return to the Ansible project directory. This keeps the commands stable even when LabEx runs each step separately.
cd /home/labex/project/rhce-ansible
Run the playbook a second time and save the output.
ansible-playbook playbooks/common-modules.yml | tee reports/second-run.txt
Look for changed=0 in the recap lines.
grep -E 'app[12].*changed=0' reports/second-run.txt
changed=0 on the second run is the key idempotence signal. It means the modules inspected the existing files and found that no repair was needed.
Summary
You built an Ansible project, used common modules to describe desired state, ran the playbook, and proved idempotence with a second run that reported no changes.


