480 lines
20 KiB
YAML
480 lines
20 KiB
YAML
---
|
|
# AWS Cleanup Playbook - Comprehensive version with robust VPC removal
|
|
- name: Clean up AWS resources
|
|
hosts: localhost
|
|
gather_facts: false
|
|
connection: local
|
|
vars_files:
|
|
- vars.yaml
|
|
vars:
|
|
aws_region: "{{ aws_region | default(aws_region_choices | random) }}"
|
|
confirm_cleanup: "{{ confirm_cleanup | default(true) }}"
|
|
deployment_id: "{{ deployment_id | default('') }}"
|
|
redirector_name: "{{ redirector_name | default('r-' + deployment_id) }}"
|
|
c2_name: "{{ c2_name | default('s-' + deployment_id) }}"
|
|
tracker_name: "{{ tracker_name | default('t-' + deployment_id) }}"
|
|
cleanup_summary: {}
|
|
|
|
tasks:
|
|
# Confirmation step (if enabled)
|
|
- name: Confirm cleanup
|
|
pause:
|
|
prompt: "Are you sure you want to delete all AWS resources for deployment ID {{ deployment_id }}? This action cannot be undone. Type 'yes' to confirm"
|
|
register: cleanup_confirmation
|
|
when: confirm_cleanup | bool
|
|
|
|
- name: Check confirmation
|
|
assert:
|
|
that:
|
|
- cleanup_confirmation.user_input | default('yes') == 'yes'
|
|
fail_msg: "Cleanup cancelled by user"
|
|
when: confirm_cleanup | bool
|
|
|
|
# STEP 1: Find all instances by deployment ID
|
|
- name: Find all EC2 instances for this deployment
|
|
amazon.aws.ec2_instance_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
"tag:deployment_id": "{{ deployment_id }}"
|
|
register: deployment_instances
|
|
|
|
- name: Set fact for instances found
|
|
set_fact:
|
|
cleanup_summary: "{{ cleanup_summary | combine({'instances_found': deployment_instances.instances | length}) }}"
|
|
|
|
# STEP 2: Terminate all instances with proper tagging
|
|
- name: Terminate all instances for this deployment
|
|
amazon.aws.ec2_instance:
|
|
instance_ids: "{{ item.instance_id }}"
|
|
region: "{{ aws_region }}"
|
|
state: absent
|
|
loop: "{{ deployment_instances.instances }}"
|
|
register: terminated_instances
|
|
when: deployment_instances.instances | length > 0
|
|
|
|
- name: Wait for instances to be terminated
|
|
pause:
|
|
seconds: 30
|
|
when: deployment_instances.instances | length > 0
|
|
|
|
# STEP 3: Find all security groups by deployment ID
|
|
- name: Find all security groups for this deployment
|
|
amazon.aws.ec2_security_group_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
"tag:deployment_id": "{{ deployment_id }}"
|
|
register: deployment_sgs
|
|
|
|
# Also find SGs by name pattern
|
|
- name: Find security groups by name pattern
|
|
amazon.aws.ec2_security_group_info:
|
|
region: "{{ aws_region }}"
|
|
register: all_sgs
|
|
|
|
- name: Filter SGs by name pattern
|
|
set_fact:
|
|
named_sgs: "{{ all_sgs.security_groups | selectattr('group_name', 'search', redirector_name + '-sg|' + c2_name + '-sg') | list }}"
|
|
|
|
- name: Combine all security groups to delete
|
|
set_fact:
|
|
all_sgs_to_delete: "{{ deployment_sgs.security_groups + named_sgs }}"
|
|
cleanup_summary: "{{ cleanup_summary | combine({'security_groups_found': (deployment_sgs.security_groups + named_sgs) | length}) }}"
|
|
|
|
# STEP 4: Delete all security groups
|
|
- name: Delete security groups
|
|
amazon.aws.ec2_security_group:
|
|
group_id: "{{ item.group_id }}"
|
|
region: "{{ aws_region }}"
|
|
state: absent
|
|
loop: "{{ all_sgs_to_delete }}"
|
|
when: all_sgs_to_delete | length > 0
|
|
ignore_errors: yes
|
|
register: deleted_sgs
|
|
|
|
# STEP 5: Find and delete all ENIs
|
|
- name: Find network interfaces by tag
|
|
amazon.aws.ec2_eni_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
"tag:deployment_id": "{{ deployment_id }}"
|
|
register: deployment_enis
|
|
|
|
- name: Delete ENIs
|
|
amazon.aws.ec2_eni:
|
|
region: "{{ aws_region }}"
|
|
eni_id: "{{ item.id }}"
|
|
state: absent
|
|
force_detach: true
|
|
loop: "{{ deployment_enis.network_interfaces }}"
|
|
ignore_errors: yes
|
|
register: deleted_enis
|
|
when: deployment_enis.network_interfaces | length > 0
|
|
|
|
- name: Set ENIs count in summary
|
|
set_fact:
|
|
cleanup_summary: "{{ cleanup_summary | combine({'enis_found': deployment_enis.network_interfaces | length}) }}"
|
|
|
|
# STEP 6: Find all VPCs by deployment ID
|
|
- name: Find all VPCs for this deployment
|
|
amazon.aws.ec2_vpc_net_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
"tag:deployment_id": "{{ deployment_id }}"
|
|
register: deployment_vpcs
|
|
|
|
# STEP 7: Find VPCs by name pattern as fallback
|
|
- name: Find all VPCs by name pattern
|
|
amazon.aws.ec2_vpc_net_info:
|
|
region: "{{ aws_region }}"
|
|
register: all_vpcs
|
|
|
|
- name: Filter VPCs by name pattern
|
|
set_fact:
|
|
named_vpcs: "{{ all_vpcs.vpcs | selectattr('tags', 'defined') | selectattr('tags.Name', 'defined') | selectattr('tags.Name', 'search', redirector_name + '-vpc|' + c2_name + '-vpc') | list }}"
|
|
|
|
- name: Combine all VPCs to delete
|
|
set_fact:
|
|
all_vpcs_to_delete: "{{ deployment_vpcs.vpcs + named_vpcs | unique(attribute='vpc_id') }}"
|
|
cleanup_summary: "{{ cleanup_summary | combine({'vpcs_found': (deployment_vpcs.vpcs + named_vpcs | unique(attribute='vpc_id')) | length}) }}"
|
|
|
|
# STEP 8: Find and delete NAT Gateways for each VPC separately
|
|
- name: Find NAT gateways in each VPC
|
|
amazon.aws.ec2_vpc_nat_gateway_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
vpc-id: "{{ item.vpc_id }}"
|
|
register: natgw_results
|
|
loop: "{{ all_vpcs_to_delete }}"
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
- name: Delete NAT gateways
|
|
amazon.aws.ec2_vpc_nat_gateway:
|
|
region: "{{ aws_region }}"
|
|
nat_gateway_id: "{{ item.1.nat_gateway_id }}"
|
|
state: absent
|
|
release_eip: true
|
|
loop: "{{ natgw_results.results | default([]) | selectattr('skipped', 'undefined') | selectattr('nat_gateways', 'defined') | subelements('nat_gateways') }}"
|
|
ignore_errors: yes
|
|
register: deleted_natgws
|
|
when: natgw_results.results is defined
|
|
|
|
- name: Wait after NAT deletion
|
|
pause:
|
|
seconds: 15
|
|
when: deleted_natgws.results is defined and deleted_natgws.results | length > 0
|
|
|
|
# STEP 9: Find and delete Internet Gateways
|
|
- name: Find internet gateways for each VPC
|
|
amazon.aws.ec2_vpc_igw_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
attachment.vpc-id: "{{ item.vpc_id }}"
|
|
register: igw_results
|
|
loop: "{{ all_vpcs_to_delete }}"
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
- name: Detach and delete internet gateways
|
|
# Fix the reference to attachment - should use item.1.attachments or similar
|
|
ec2_vpc_igw:
|
|
vpc_id: "{{ item.1.attachments[0].vpc_id }}" # Fix this line
|
|
state: absent
|
|
region: "{{ aws_region }}"
|
|
with_together: "{{ find_igws.results|default([]) }}"
|
|
register: deleted_igws
|
|
ignore_errors: yes
|
|
|
|
- name: Wait after IGW deletion
|
|
pause:
|
|
seconds: 15
|
|
when: deleted_igws.results is defined and deleted_igws.results | length > 0
|
|
|
|
# STEP 10: Find and delete Route Tables
|
|
- name: Find route tables for each VPC
|
|
amazon.aws.ec2_vpc_route_table_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
vpc-id: "{{ item.vpc_id }}"
|
|
register: rtb_results
|
|
loop: "{{ all_vpcs_to_delete }}"
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
- name: Delete non-main route tables
|
|
amazon.aws.ec2_vpc_route_table:
|
|
region: "{{ aws_region }}"
|
|
route_table_id: "{{ item.1.id }}"
|
|
lookup: id
|
|
state: absent
|
|
loop: "{{ rtb_results.results | default([]) | selectattr('skipped', 'undefined') | selectattr('route_tables', 'defined') | subelements('route_tables') }}"
|
|
when: not item.1.associations[0].main | default(false)
|
|
ignore_errors: yes
|
|
register: deleted_rtbs
|
|
|
|
# Add this after your existing route table deletion
|
|
- name: Delete main route tables with AWS CLI
|
|
shell: |
|
|
for rtb in $(aws ec2 describe-route-tables --region {{ aws_region }} --filters "Name=vpc-id,Values={{ item.vpc_id }}" --query 'RouteTables[?Associations[?Main==`true`]].RouteTableId' --output text); do
|
|
aws ec2 delete-route --route-table-id $rtb --destination-cidr-block 0.0.0.0/0 --region {{ aws_region }} || true
|
|
done
|
|
environment:
|
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
|
loop: "{{ all_vpcs_to_delete }}"
|
|
ignore_errors: yes
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
# STEP 11: Find and delete Subnets
|
|
- name: Find subnets for each VPC
|
|
ec2_vpc_subnet_info:
|
|
filters:
|
|
vpc-id: "{{ item.vpc_id }}" # Fix this line, should be vpc_id not vpc_idvpc
|
|
region: "{{ aws_region }}"
|
|
with_items: "{{ all_vpcs_to_delete }}"
|
|
register: find_subnets
|
|
|
|
- name: Delete subnets
|
|
amazon.aws.ec2_vpc_subnet:
|
|
region: "{{ aws_region }}"
|
|
vpc_id: "{{ item.1.vpc_id }}"
|
|
cidr: "{{ item.1.cidr_block }}"
|
|
state: absent
|
|
loop: "{{ subnet_results.results | default([]) | selectattr('skipped', 'undefined') | selectattr('subnets', 'defined') | subelements('subnets') }}"
|
|
ignore_errors: yes
|
|
register: deleted_subnets
|
|
when: subnet_results.results is defined
|
|
|
|
# STEP 12: Find and delete VPC Endpoints
|
|
- name: Find VPC endpoints for each VPC
|
|
amazon.aws.ec2_vpc_endpoint_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
vpc-id: "{{ item.vpc_id }}"
|
|
register: endpoint_results
|
|
loop: "{{ all_vpcs_to_delete }}"
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
- name: Delete VPC endpoints
|
|
amazon.aws.ec2_vpc_endpoint:
|
|
region: "{{ aws_region }}"
|
|
vpc_endpoint_id: "{{ item.1.vpc_endpoint_id }}"
|
|
state: absent
|
|
loop: "{{ endpoint_results.results | default([]) | selectattr('skipped', 'undefined') | selectattr('vpc_endpoints', 'defined') | subelements('vpc_endpoints') }}"
|
|
ignore_errors: yes
|
|
register: deleted_endpoints
|
|
when: endpoint_results.results is defined
|
|
|
|
# Add before STEP 13
|
|
- name: Check for remaining VPC dependencies
|
|
shell: |
|
|
aws ec2 describe-network-interfaces --region {{ aws_region }} --filters "Name=vpc-id,Values={{ item.vpc_id }}" --output json
|
|
environment:
|
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
|
register: remaining_deps
|
|
loop: "{{ all_vpcs_to_delete }}"
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
- name: Display any remaining dependencies
|
|
debug:
|
|
msg: "VPC {{ item.item.vpc_id }} still has dependencies that need to be removed"
|
|
loop: "{{ remaining_deps.results }}"
|
|
when: item.stdout | from_json | json_query('NetworkInterfaces') | length > 0
|
|
|
|
# Add this before the force delete of network interfaces
|
|
- name: Detach remaining network interfaces
|
|
shell: |
|
|
aws ec2 detach-network-interface --attachment-id $(aws ec2 describe-network-interfaces --network-interface-ids {{ item.1 }} --query 'NetworkInterfaces[0].Attachment.AttachmentId' --output text) --region {{ aws_region }} --force
|
|
environment:
|
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
|
loop: "{{ remaining_deps.results | selectattr('stdout', 'defined') |
|
|
map('attr', 'stdout') | map('from_json') |
|
|
map('json_query', 'NetworkInterfaces[?Status==`in-use`].NetworkInterfaceId') |
|
|
zip(remaining_deps.results | map('attr', 'item')) | list }}"
|
|
ignore_errors: yes
|
|
when: item.0 | length > 0
|
|
|
|
- name: Force delete any remaining network interfaces
|
|
shell: |
|
|
aws ec2 delete-network-interface --network-interface-id {{ item.1 }} --region {{ aws_region }}
|
|
environment:
|
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
|
loop: "{{ remaining_deps.results | selectattr('stdout', 'defined') |
|
|
map('attr', 'stdout') | map('from_json') |
|
|
map('json_query', 'NetworkInterfaces[].NetworkInterfaceId') |
|
|
zip(remaining_deps.results | map('attr', 'item')) | list }}"
|
|
ignore_errors: yes
|
|
when: item.0 | length > 0
|
|
|
|
# STEP 13: Final VPC deletion with multiple retries
|
|
- name: Wait for all dependencies to clear
|
|
pause:
|
|
seconds: 20
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
# First attempt with normal module - with error display
|
|
- name: Delete all VPCs (first attempt)
|
|
amazon.aws.ec2_vpc_net:
|
|
vpc_id: "{{ item.vpc_id }}"
|
|
region: "{{ aws_region }}"
|
|
state: absent
|
|
loop: "{{ all_vpcs_to_delete }}"
|
|
register: vpc_deletion
|
|
when: all_vpcs_to_delete | length > 0
|
|
ignore_errors: yes
|
|
|
|
- name: Display VPC deletion errors
|
|
debug:
|
|
msg: "Failed to delete VPC {{ item.item.vpc_id }}: {{ item.msg }}"
|
|
loop: "{{ vpc_deletion.results | default([]) }}"
|
|
when: item.failed is defined and item.failed
|
|
|
|
# Direct API call for any VPCs that failed
|
|
- name: Find which VPCs still exist
|
|
amazon.aws.ec2_vpc_net_info:
|
|
region: "{{ aws_region }}"
|
|
vpc_ids: "{{ all_vpcs_to_delete | map(attribute='vpc_id') | list }}"
|
|
register: remaining_vpcs
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
# Forcibly delete with direct AWS CLI command
|
|
- name: Force delete remaining VPCs with CLI
|
|
shell: |
|
|
aws ec2 delete-vpc --vpc-id {{ item.vpc_id }} --region {{ aws_region }}
|
|
environment:
|
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
|
loop: "{{ remaining_vpcs.vpcs }}"
|
|
ignore_errors: yes
|
|
when: remaining_vpcs is defined and remaining_vpcs.vpcs | length > 0
|
|
register: force_vpc_delete
|
|
|
|
# Add after the VPC deletion attempts - more aggressive approach
|
|
- name: Force delete remaining VPCs with AWS CLI and debug output
|
|
shell: |
|
|
aws ec2 delete-vpc --vpc-id {{ item.vpc_id }} --region {{ aws_region }} 2>&1 || echo "Failed with: $?"
|
|
environment:
|
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
|
loop: "{{ remaining_vpcs.vpcs }}"
|
|
register: force_vpc_delete_debug
|
|
when: remaining_vpcs is defined and remaining_vpcs.vpcs | length > 0
|
|
|
|
- name: Display debug output from force delete
|
|
debug:
|
|
msg: "{{ item.stdout }}"
|
|
loop: "{{ force_vpc_delete_debug.results | default([]) }}"
|
|
when: item.stdout is defined and item.stdout | trim != ""
|
|
|
|
# Track deleted VPCs in summary
|
|
- name: Set VPC deletion results in summary
|
|
set_fact:
|
|
cleanup_summary: "{{ cleanup_summary | combine({
|
|
'vpcs_deleted': ((vpc_deletion.results | default([]) | selectattr('failed', 'undefined') | list | length) + (force_vpc_delete.results | default([]) | selectattr('failed', 'undefined') | list | length))}) }}"
|
|
when: all_vpcs_to_delete | length > 0
|
|
|
|
# Add these tasks to confirm VPC deletion
|
|
- name: Verify VPC deletion
|
|
amazon.aws.ec2_vpc_net_info:
|
|
region: "{{ aws_region }}"
|
|
filters:
|
|
"tag:deployment_id": "{{ deployment_id }}"
|
|
register: vpc_check
|
|
|
|
- name: Display cleanup summary
|
|
debug:
|
|
msg:
|
|
- "Cleanup Summary:"
|
|
- "Redirector instance deleted: {{ redirector_deleted | default('N/A') }}"
|
|
- "C2 instance deleted: {{ c2_deleted | default('N/A') }}"
|
|
- "VPC resources deleted: {{ vpc_check.vpcs | length == 0 }}"
|
|
when: not disable_summary | default(false)
|
|
|
|
# STEP 14: Delete key pairs
|
|
- name: Find key pairs by name patterns
|
|
amazon.aws.ec2_key:
|
|
name: "{{ item }}"
|
|
region: "{{ aws_region }}"
|
|
state: present
|
|
register: keys_check
|
|
ignore_errors: yes
|
|
with_items:
|
|
- "{{ redirector_name }}"
|
|
- "{{ c2_name }}"
|
|
- "{{ tracker_name }}"
|
|
|
|
- name: Delete key pairs
|
|
amazon.aws.ec2_key:
|
|
name: "{{ item.invocation.module_args.name }}"
|
|
region: "{{ aws_region }}"
|
|
state: absent
|
|
loop: "{{ keys_check.results }}"
|
|
when: keys_check.results | length > 0 and item.failed is not defined and item.key is defined
|
|
register: deleted_keys
|
|
|
|
- name: Count deleted keys
|
|
set_fact:
|
|
cleanup_summary: "{{ cleanup_summary | combine({
|
|
'keypairs_deleted': (deleted_keys.results | default([]) | selectattr('changed', 'defined') | selectattr('changed') | list | length)}) }}"
|
|
|
|
# Add this after your existing key pair finding task
|
|
- name: Find c2deploy key pairs
|
|
amazon.aws.ec2_key:
|
|
name: "c2deploy_{{ deployment_id }}"
|
|
region: "{{ aws_region }}"
|
|
state: present
|
|
register: c2deploy_key_check
|
|
ignore_errors: yes
|
|
|
|
- name: Delete c2deploy key pairs
|
|
amazon.aws.ec2_key:
|
|
name: "c2deploy_{{ deployment_id }}"
|
|
region: "{{ aws_region }}"
|
|
state: absent
|
|
when: not c2deploy_key_check.failed | default(true)
|
|
register: deleted_c2deploy_key
|
|
|
|
# STEP 15: Delete SSH key files
|
|
- name: Delete SSH key files
|
|
file:
|
|
path: "{{ item }}"
|
|
state: absent
|
|
with_items:
|
|
- "~/.ssh/{{ redirector_name }}.pem"
|
|
- "~/.ssh/{{ c2_name }}.pem"
|
|
- "~/.ssh/{{ tracker_name }}.pem"
|
|
- "~/.ssh/c2deploy_{{ deployment_id }}.pem" # Fix: add .pem extension
|
|
- "~/.ssh/c2deploy_{{ deployment_id }}.pub"
|
|
ignore_errors: yes
|
|
register: deleted_ssh_files
|
|
|
|
- name: Count deleted SSH files
|
|
set_fact:
|
|
cleanup_summary: "{{ cleanup_summary | combine({
|
|
'ssh_files_deleted': (deleted_ssh_files.results | selectattr('changed', 'defined') | selectattr('changed') | list | length)}) }}"
|
|
|
|
# Remove infrastructure state file - fix path to include deployment_id
|
|
- name: Remove infrastructure state file
|
|
file:
|
|
path: "infrastructure_state_{{ deployment_id }}.json"
|
|
state: absent
|
|
ignore_errors: yes
|
|
register: infra_file
|
|
|
|
# STEP 16: Enhanced and Accurate Cleanup Summary
|
|
- name: Enhanced cleanup summary
|
|
debug:
|
|
msg:
|
|
- "=========================================================="
|
|
- " AWS CLEANUP SUMMARY: {{ deployment_id }} "
|
|
- "=========================================================="
|
|
- "EC2 Instances: {{ cleanup_summary.instances_found | default(0) }} found, {{ terminated_instances.results | default([]) | length }} terminated"
|
|
- "Security Groups: {{ cleanup_summary.security_groups_found | default(0) }} found, {{ deleted_sgs.results | default([]) | length }} deleted"
|
|
- "Network Interfaces: {{ cleanup_summary.enis_found | default(0) }} found, {{ deleted_enis.results | default([]) | length }} deleted"
|
|
- "VPCs: {{ cleanup_summary.vpcs_found | default(0) }} found, {{ cleanup_summary.vpcs_deleted | default(0) }} deleted"
|
|
- "Key Pairs: {{ cleanup_summary.keypairs_deleted | default(0) }} deleted"
|
|
- "SSH Key Files: {{ cleanup_summary.ssh_files_deleted | default(0) }} deleted"
|
|
- "Infrastructure file: {{ 'Removed' if infra_file.changed else 'Not found' }}"
|
|
- "=========================================================="
|
|
- "CLEANUP {{ 'COMPLETED' if (cleanup_summary.vpcs_deleted | default(0) == cleanup_summary.vpcs_found | default(0)) else 'PARTIAL - SOME RESOURCES MAY REMAIN' }}"
|
|
- "=========================================================" |