February 17, 2026 · 7 min · 1328 words · Rob Washington
Table of Contents
Ansible playbooks can quickly become unwieldy spaghetti. Here are battle-tested patterns for writing infrastructure code that scales with your team and your infrastructure.
The worst Ansible code always reports “changed.” Fix it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Bad - always reports changed- name:Run database migrationsansible.builtin.command:python manage.py migrate# Good - only reports changed when something happened- name:Run database migrationsansible.builtin.command:python manage.py migrateregister:migrate_resultchanged_when:"'No migrations to apply' not in migrate_result.stdout"# Better - skip entirely if no migrations pending- name:Check for pending migrationsansible.builtin.command:python manage.py showmigrations --planregister:migration_planchanged_when:false- name:Run database migrationsansible.builtin.command:python manage.py migratewhen:"'[ ]' in migration_plan.stdout"
- name:Deploy application with rollbackblock:- name:Stop applicationansible.builtin.systemd:name:myappstate:stopped- name:Deploy new versionansible.builtin.copy:src:"{{ release_artifact }}"dest:/opt/myapp/currentregister:deploy_result- name:Run database migrationsansible.builtin.command:/opt/myapp/current/migrate.sh- name:Start applicationansible.builtin.systemd:name:myappstate:startedrescue:- name:Restore previous versionansible.builtin.copy:src:/opt/myapp/previousdest:/opt/myapp/currentwhen:deploy_result is defined and deploy_result.changed- name:Start application (rollback)ansible.builtin.systemd:name:myappstate:started- name:Fail with messageansible.builtin.fail:msg:"Deployment failed, rolled back to previous version"always:- name:Clean up temp filesansible.builtin.file:path:/tmp/deploy-stagingstate:absent
#!/usr/bin/env python3# inventory/aws_inventory.pyimportboto3importjsonfromfunctoolsimportlru_cache@lru_cache(maxsize=1)defget_instances():ec2=boto3.client('ec2')response=ec2.describe_instances(Filters=[{'Name':'tag:Environment','Values':['production']}])inventory={'_meta':{'hostvars':{}}}forreservationinresponse['Reservations']:forinstanceinreservation['Instances']:ifinstance['State']['Name']!='running':continue# Group by role tagrole=next((t['Value']fortininstance.get('Tags',[])ift['Key']=='Role'),'ungrouped')ifrolenotininventory:inventory[role]={'hosts':[]}host=instance['PrivateIpAddress']inventory[role]['hosts'].append(host)inventory['_meta']['hostvars'][host]={'instance_id':instance['InstanceId'],'instance_type':instance['InstanceType'],}returninventoryif__name__=='__main__':print(json.dumps(get_instances(),indent=2))