Last active
July 9, 2025 14:00
-
-
Save ShoGinn/61d051d03711671c8c6d7856e47392ff to your computer and use it in GitHub Desktop.
Cleanup Resources on AWS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# AWS Resource Cleanup Script | |
# This script removes AWS resources containing a selected pattern in their names | |
# USE WITH CAUTION - This will permanently delete resources | |
set -e # Exit on any error | |
# Colors for output | |
RED='\033[0;31m' | |
GREEN='\033[0;32m' | |
YELLOW='\033[1;33m' | |
NC='\033[0m' # No Color | |
# Configuration | |
SEARCH_PATTERN="" | |
DRY_RUN=false | |
REGION="us-east-1" | |
# Function to show usage | |
show_usage() { | |
cat <<EOF | |
AWS Resource Cleanup Script | |
DESCRIPTION: | |
This script removes AWS resources containing a specific pattern in their names. | |
It can clean up S3 buckets, IAM roles, IAM policies, and IAM instance profiles. | |
⚠️ WARNING: This will PERMANENTLY DELETE AWS resources! ⚠️ | |
USAGE: | |
$0 --pattern PATTERN [OPTIONS] | |
REQUIRED: | |
--pattern PATTERN Search pattern to match resource names (e.g., "myproject-", "dev-") | |
OPTIONS: | |
--dry-run Show what would be deleted without actually deleting | |
--region REGION AWS region (default: us-east-1) | |
--help Show this help message | |
EXAMPLES: | |
# Dry run to see what would be deleted | |
$0 --pattern "myproject-" --dry-run | |
# Clean up all resources with "dev-" prefix in us-west-2 | |
$0 --pattern "dev-" --region us-west-2 | |
# Clean up test environment resources | |
$0 --pattern "test-env-" --dry-run | |
PREREQUISITES: | |
- AWS CLI installed and configured | |
- jq installed (brew install jq) | |
- Appropriate AWS permissions for the resources you want to delete | |
WHAT IT CLEANS: | |
✓ S3 buckets (including versioned objects) | |
✓ IAM roles (detaches policies first) | |
✓ IAM policies (customer managed only) | |
✓ IAM instance profiles | |
EOF | |
} | |
# Function to print colored output | |
print_info() { | |
echo -e "${GREEN}[INFO]${NC} $1" | |
} | |
print_warning() { | |
echo -e "${YELLOW}[WARNING]${NC} $1" | |
} | |
print_error() { | |
echo -e "${RED}[ERROR]${NC} $1" | |
} | |
print_success() { | |
echo -e "${GREEN}[SUCCESS]${NC} $1" | |
} | |
# Function to validate required parameters | |
validate_parameters() { | |
if [ -z "$SEARCH_PATTERN" ]; then | |
print_error "Search pattern is required!" | |
echo | |
show_usage | |
exit 1 | |
fi | |
if [ ${#SEARCH_PATTERN} -lt 2 ]; then | |
print_error "Search pattern must be at least 2 characters long for safety" | |
exit 1 | |
fi | |
print_info "Search pattern: '$SEARCH_PATTERN'" | |
print_info "AWS Region: $REGION" | |
if [ "$DRY_RUN" = true ]; then | |
print_warning "DRY RUN MODE - No resources will be deleted" | |
fi | |
} | |
confirm_deletion() { | |
if [ "$DRY_RUN" = true ]; then | |
return 0 | |
fi | |
read -r -p "Are you sure you want to delete these resources? (yes/no): " confirm | |
if [ "$confirm" != "yes" ]; then | |
print_info "Skipping deletion..." | |
return 1 | |
fi | |
return 0 | |
} | |
# Check if AWS CLI is installed and configured | |
check_aws_cli() { | |
if ! command -v aws &>/dev/null; then | |
print_error "AWS CLI is not installed" | |
exit 1 | |
fi | |
if ! aws sts get-caller-identity &>/dev/null; then | |
print_error "AWS CLI is not configured or credentials are invalid" | |
exit 1 | |
fi | |
# Check for jq | |
if ! command -v jq &>/dev/null; then | |
print_error "jq is not installed. Please install it with: brew install jq" | |
exit 1 | |
fi | |
print_info "AWS CLI is configured for account: $(aws sts get-caller-identity --query Account --output text)" | |
} | |
# Remove S3 buckets | |
cleanup_s3_buckets() { | |
print_info "Finding S3 buckets containing '$SEARCH_PATTERN'..." | |
buckets=$(aws s3api list-buckets --query "Buckets[?contains(Name, '$SEARCH_PATTERN')].Name" --output text) | |
if [ -z "$buckets" ]; then | |
print_info "No S3 buckets found containing '$SEARCH_PATTERN'" | |
return | |
fi | |
echo "Found S3 buckets:" | |
for bucket in $buckets; do | |
echo " - $bucket" | |
done | |
if confirm_deletion; then | |
for bucket in $buckets; do | |
if [ "$DRY_RUN" = true ]; then | |
print_info "[DRY RUN] Would delete S3 bucket: $bucket" | |
else | |
print_info "Emptying and deleting S3 bucket: $bucket" | |
# Delete all object versions and delete markers | |
print_info "Deleting all object versions in bucket: $bucket" | |
aws s3api list-object-versions --bucket "$bucket" --output json | | |
jq -r '.Versions[]?, .DeleteMarkers[]? | select(.Key != null) | "\(.Key) \(.VersionId)"' | | |
while read -r key version; do | |
if [ -n "$key" ] && [ -n "$version" ]; then | |
aws s3api delete-object --bucket "$bucket" --key "$key" --version-id "$version" >/dev/null | |
fi | |
done | |
# Also try the simple recursive delete for any remaining objects | |
aws s3 rm s3://"$bucket" --recursive >/dev/null 2>&1 || true | |
# Delete bucket | |
if aws s3api delete-bucket --bucket "$bucket"; then | |
print_success "Successfully deleted bucket: $bucket" | |
else | |
print_error "Failed to delete bucket $bucket" | |
fi | |
fi | |
done | |
fi | |
} | |
# Remove IAM roles | |
cleanup_iam_roles() { | |
print_info "Finding IAM roles containing '$SEARCH_PATTERN'..." | |
roles=$(aws iam list-roles --query "Roles[?contains(RoleName, '$SEARCH_PATTERN')].RoleName" --output text) | |
if [ -z "$roles" ]; then | |
print_info "No IAM roles found containing '$SEARCH_PATTERN'" | |
return | |
fi | |
echo "Found IAM roles:" | |
for role in $roles; do | |
echo " - $role" | |
done | |
if confirm_deletion; then | |
for role in $roles; do | |
if [ "$DRY_RUN" = true ]; then | |
print_info "[DRY RUN] Would delete IAM role: $role" | |
else | |
print_info "Deleting IAM role: $role" | |
# Detach managed policies | |
attached_policies=$(aws iam list-attached-role-policies --role-name "$role" --query 'AttachedPolicies[].PolicyArn' --output text) | |
for policy in $attached_policies; do | |
aws iam detach-role-policy --role-name "$role" --policy-arn "$policy" | |
done | |
# Delete inline policies | |
inline_policies=$(aws iam list-role-policies --role-name "$role" --query 'PolicyNames' --output text) | |
for policy in $inline_policies; do | |
aws iam delete-role-policy --role-name "$role" --policy-name "$policy" | |
done | |
# Remove role from instance profiles | |
instance_profiles=$(aws iam list-instance-profiles-for-role --role-name "$role" --query 'InstanceProfiles[].InstanceProfileName' --output text) | |
for profile in $instance_profiles; do | |
aws iam remove-role-from-instance-profile --instance-profile-name "$profile" --role-name "$role" | |
done | |
# Delete the role | |
if aws iam delete-role --role-name "$role"; then | |
print_success "Successfully deleted role: $role" | |
else | |
print_error "Failed to delete role $role" | |
fi | |
fi | |
done | |
fi | |
} | |
# Remove IAM policies | |
cleanup_iam_policies() { | |
print_info "Finding IAM policies containing '$SEARCH_PATTERN'..." | |
# Get policies in a more reliable format | |
policies=$(aws iam list-policies --scope Local --query "Policies[?contains(PolicyName, '$SEARCH_PATTERN')]" --output json) | |
if [ "$policies" = "[]" ] || [ -z "$policies" ]; then | |
print_info "No IAM policies found containing '$SEARCH_PATTERN'" | |
return | |
fi | |
echo "Found IAM policies:" | |
echo "$policies" | jq -r '.[] | " - \(.PolicyName) (\(.Arn))"' | |
if confirm_deletion; then | |
echo "$policies" | jq -r '.[] | "\(.PolicyName) \(.Arn)"' | while read -r name arn; do | |
if [ "$DRY_RUN" = true ]; then | |
print_info "[DRY RUN] Would delete IAM policy: $name" | |
else | |
print_info "Deleting IAM policy: $name" | |
# Check if policy is attached to any entities before deleting | |
entities=$(aws iam list-entities-for-policy --policy-arn "$arn" --output json) | |
# Detach from roles | |
echo "$entities" | jq -r '.PolicyRoles[]?.RoleName // empty' | while read -r role; do | |
[ -n "$role" ] && aws iam detach-role-policy --role-name "$role" --policy-arn "$arn" | |
done | |
# Detach from users | |
echo "$entities" | jq -r '.PolicyUsers[]?.UserName // empty' | while read -r user; do | |
[ -n "$user" ] && aws iam detach-user-policy --user-name "$user" --policy-arn "$arn" | |
done | |
# Detach from groups | |
echo "$entities" | jq -r '.PolicyGroups[]?.GroupName // empty' | while read -r group; do | |
[ -n "$group" ] && aws iam detach-group-policy --group-name "$group" --policy-arn "$arn" | |
done | |
# Delete the policy | |
if aws iam delete-policy --policy-arn "$arn"; then | |
print_success "Successfully deleted policy: $name" | |
else | |
print_error "Failed to delete policy $name" | |
fi | |
fi | |
done | |
fi | |
} | |
# Remove IAM instance profiles | |
cleanup_iam_instance_profiles() { | |
print_info "Finding IAM instance profiles containing '$SEARCH_PATTERN'..." | |
profiles=$(aws iam list-instance-profiles --query "InstanceProfiles[?contains(InstanceProfileName, '$SEARCH_PATTERN')].InstanceProfileName" --output text) | |
if [ -z "$profiles" ]; then | |
print_info "No IAM instance profiles found containing '$SEARCH_PATTERN'" | |
return | |
fi | |
echo "Found IAM instance profiles:" | |
for profile in $profiles; do | |
echo " - $profile" | |
done | |
if confirm_deletion; then | |
for profile in $profiles; do | |
if [ "$DRY_RUN" = true ]; then | |
print_info "[DRY RUN] Would delete IAM instance profile: $profile" | |
else | |
print_info "Deleting IAM instance profile: $profile" | |
if aws iam delete-instance-profile --instance-profile-name "$profile"; then | |
print_success "Successfully deleted instance profile: $profile" | |
else | |
print_error "Failed to delete instance profile $profile" | |
fi | |
fi | |
done | |
fi | |
} | |
# Main function | |
main() { | |
# Parse command line arguments | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
--dry-run) | |
DRY_RUN=true | |
shift | |
;; | |
--region) | |
if [ -z "$2" ]; then | |
print_error "Region value is required" | |
exit 1 | |
fi | |
REGION="$2" | |
shift 2 | |
;; | |
--pattern) | |
if [ -z "$2" ]; then | |
print_error "Pattern value is required" | |
exit 1 | |
fi | |
SEARCH_PATTERN="$2" | |
shift 2 | |
;; | |
--help | -h) | |
show_usage | |
exit 0 | |
;; | |
*) | |
print_error "Unknown option: $1" | |
echo | |
show_usage | |
exit 1 | |
;; | |
esac | |
done | |
# Validate required parameters | |
validate_parameters | |
print_info "Starting AWS cleanup for pattern: '$SEARCH_PATTERN' in region: $REGION" | |
# Set AWS region | |
export AWS_DEFAULT_REGION=$REGION | |
# Check AWS CLI and dependencies | |
check_aws_cli | |
# Warning and confirmation for non-dry-run | |
if [ "$DRY_RUN" = false ]; then | |
echo | |
print_warning "⚠️ DANGER ZONE ⚠️" | |
print_warning "This script will PERMANENTLY DELETE AWS resources!" | |
print_warning "Resources matching pattern: '$SEARCH_PATTERN'" | |
print_warning "In region: $REGION" | |
print_warning "Make sure you have backups and understand the consequences." | |
echo | |
read -r -p "Type 'DELETE' to confirm you want to proceed: " continue_confirm | |
if [ "$continue_confirm" != "DELETE" ]; then | |
print_info "Aborted by user - safety first!" | |
exit 0 | |
fi | |
echo | |
fi | |
# Run cleanup functions | |
print_info "=== Starting resource cleanup ===" | |
cleanup_s3_buckets | |
echo | |
cleanup_iam_roles | |
echo | |
cleanup_iam_policies | |
echo | |
cleanup_iam_instance_profiles | |
echo | |
if [ "$DRY_RUN" = true ]; then | |
print_info "=== Dry run completed - no resources were deleted ===" | |
print_info "Run without --dry-run to actually delete these resources" | |
else | |
print_success "=== Cleanup completed! ===" | |
fi | |
} | |
# Run main function | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment