Skip to content

Commit 05defbb

Browse files
committed
Add cleanup-tutorial.sh: find and delete tutorial resources
Scans for MQ brokers, OpenSearch domains, MSK clusters, RDS instances, ElastiCache caches, Redshift workgroups, ECS clusters/services, and Q Business apps. Identifies by doc-smith tags or naming patterns. Usage: bash cleanup-tutorial.sh # list resources bash cleanup-tutorial.sh --delete # delete all bash cleanup-tutorial.sh --tutorial 043 # filter by tutorial
1 parent 087281d commit 05defbb

1 file changed

Lines changed: 187 additions & 0 deletions

File tree

cleanup-tutorial.sh

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/bin/bash
2+
# cleanup-tutorial.sh — Find and clean up resources created by a tutorial
3+
#
4+
# Usage:
5+
# bash cleanup-tutorial.sh # List all tutorial resources
6+
# bash cleanup-tutorial.sh --tutorial 043 # List resources for tutorial 043
7+
# bash cleanup-tutorial.sh --delete # Delete all tutorial resources
8+
# bash cleanup-tutorial.sh --delete --tutorial 043 # Delete resources for tutorial 043
9+
#
10+
# Resources are identified by the tag: project=doc-smith, tutorial={id}
11+
# Untagged resources are identified by naming patterns (tutorial-*, mq-broker-*, etc.)
12+
13+
set -uo pipefail
14+
15+
DELETE=false
16+
TUTORIAL_FILTER=""
17+
REGION="${AWS_REGION:-$(aws configure get region 2>/dev/null || echo us-west-2)}"
18+
19+
while [[ $# -gt 0 ]]; do
20+
case $1 in
21+
--delete) DELETE=true; shift ;;
22+
--tutorial) TUTORIAL_FILTER="$2"; shift 2 ;;
23+
--region) REGION="$2"; shift 2 ;;
24+
*) echo "Unknown option: $1"; exit 1 ;;
25+
esac
26+
done
27+
28+
FOUND=0
29+
DELETED=0
30+
31+
found() {
32+
local type=$1 id=$2 name=$3 tutorial=$4 cost=$5
33+
FOUND=$((FOUND+1))
34+
if $DELETE; then
35+
printf " 🗑️ %-20s %-40s %s\n" "$type" "$id" "$name"
36+
else
37+
printf " %-20s %-40s %-30s %-20s %s\n" "$type" "$id" "$name" "$tutorial" "$cost"
38+
fi
39+
}
40+
41+
delete_resource() {
42+
local type=$1 id=$2
43+
if ! $DELETE; then return; fi
44+
DELETED=$((DELETED+1))
45+
case $type in
46+
mq-broker)
47+
aws mq delete-broker --broker-id "$id" --region "$REGION" --output text 2>/dev/null ;;
48+
opensearch-domain)
49+
aws opensearch delete-domain --domain-name "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
50+
msk-cluster)
51+
aws kafka delete-cluster --cluster-arn "$id" --region "$REGION" --output text 2>/dev/null ;;
52+
rds-instance)
53+
aws rds delete-db-instance --db-instance-identifier "$id" --skip-final-snapshot --delete-automated-backups --region "$REGION" --output text 2>/dev/null > /dev/null ;;
54+
rds-cluster)
55+
aws rds delete-db-cluster --db-cluster-identifier "$id" --skip-final-snapshot --region "$REGION" --output text 2>/dev/null > /dev/null ;;
56+
rds-subnet-group)
57+
aws rds delete-db-subnet-group --db-subnet-group-name "$id" --region "$REGION" 2>/dev/null ;;
58+
docdb-instance)
59+
aws docdb delete-db-instance --db-instance-identifier "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
60+
docdb-cluster)
61+
aws docdb delete-db-cluster --db-cluster-identifier "$id" --skip-final-snapshot --region "$REGION" --output text 2>/dev/null > /dev/null ;;
62+
neptune-instance)
63+
aws neptune delete-db-instance --db-instance-identifier "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
64+
neptune-cluster)
65+
aws neptune delete-db-cluster --db-cluster-identifier "$id" --skip-final-snapshot --region "$REGION" --output text 2>/dev/null > /dev/null ;;
66+
elasticache)
67+
aws elasticache delete-serverless-cache --serverless-cache-name "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
68+
redshift-serverless-wg)
69+
aws redshift-serverless delete-workgroup --workgroup-name "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
70+
redshift-serverless-ns)
71+
aws redshift-serverless delete-namespace --namespace-name "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
72+
redshift-cluster)
73+
aws redshift delete-cluster --cluster-identifier "$id" --skip-final-cluster-snapshot --region "$REGION" --output text 2>/dev/null > /dev/null ;;
74+
emr-cluster)
75+
aws emr terminate-clusters --cluster-ids "$id" --region "$REGION" 2>/dev/null ;;
76+
ecs-service)
77+
local cluster=$(echo "$id" | cut -d/ -f1)
78+
local svc=$(echo "$id" | cut -d/ -f2)
79+
aws ecs update-service --cluster "$cluster" --service "$svc" --desired-count 0 --region "$REGION" --no-cli-pager 2>/dev/null > /dev/null
80+
aws ecs delete-service --cluster "$cluster" --service "$svc" --force --region "$REGION" --no-cli-pager 2>/dev/null > /dev/null ;;
81+
ecs-cluster)
82+
aws ecs delete-cluster --cluster "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
83+
network-firewall)
84+
aws network-firewall delete-firewall --firewall-name "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
85+
vpc-lattice-service)
86+
aws vpc-lattice delete-service --service-identifier "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
87+
dms-instance)
88+
aws dms delete-replication-instance --replication-instance-arn "$id" --region "$REGION" --output text 2>/dev/null > /dev/null ;;
89+
qbusiness-app)
90+
aws qbusiness delete-application --application-id "$id" --region us-east-1 2>/dev/null ;;
91+
esac
92+
}
93+
94+
match_tutorial() {
95+
local tutorial=$1
96+
[ -z "$TUTORIAL_FILTER" ] && return 0
97+
[[ "$tutorial" == *"$TUTORIAL_FILTER"* ]] && return 0
98+
return 1
99+
}
100+
101+
echo "Scanning $REGION for tutorial resources..."
102+
$DELETE || printf " %-20s %-40s %-30s %-20s %s\n" "TYPE" "ID" "NAME" "TUTORIAL" "COST"
103+
$DELETE || echo " $(printf '%.0s-' {1..130})"
104+
105+
# --- MQ Brokers ---
106+
while IFS=$'\t' read -r bid bname bstate; do
107+
[ -z "$bid" ] && continue
108+
tags=$(aws mq describe-broker --broker-id "$bid" --region "$REGION" --query 'Tags' --output json 2>/dev/null)
109+
tutorial=$(echo "$tags" | python3 -c "import sys,json; t=json.load(sys.stdin) or {}; print(t.get('tutorial',''))" 2>/dev/null)
110+
match_tutorial "$tutorial" || continue
111+
found "mq-broker" "$bid" "$bname" "${tutorial:-untagged}" "~\$0.20/hr"
112+
delete_resource "mq-broker" "$bid"
113+
done < <(aws mq list-brokers --region "$REGION" --query 'BrokerSummaries[*].[BrokerId,BrokerName,BrokerState]' --output text 2>/dev/null)
114+
115+
# --- OpenSearch Domains ---
116+
for domain in $(aws opensearch list-domain-names --region "$REGION" --query 'DomainNames[*].DomainName' --output text 2>/dev/null); do
117+
tags=$(aws opensearch list-tags --arn "arn:aws:es:${REGION}:$(aws sts get-caller-identity --query Account --output text):domain/$domain" --region "$REGION" --query 'TagList' --output json 2>/dev/null)
118+
tutorial=$(echo "$tags" | python3 -c "import sys,json; t={i['Key']:i['Value'] for i in json.load(sys.stdin) or []}; print(t.get('tutorial',''))" 2>/dev/null)
119+
match_tutorial "$tutorial" || continue
120+
found "opensearch-domain" "$domain" "$domain" "${tutorial:-untagged}" "~\$0.10/hr"
121+
delete_resource "opensearch-domain" "$domain"
122+
done
123+
124+
# --- MSK Clusters ---
125+
while IFS=$'\t' read -r carn cname cstate; do
126+
[ -z "$carn" ] && continue
127+
tags=$(aws kafka list-tags-for-resource --resource-arn "$carn" --region "$REGION" --query 'Tags' --output json 2>/dev/null)
128+
tutorial=$(echo "$tags" | python3 -c "import sys,json; t=json.load(sys.stdin) or {}; print(t.get('tutorial',''))" 2>/dev/null)
129+
match_tutorial "$tutorial" || continue
130+
found "msk-cluster" "$carn" "$cname" "${tutorial:-untagged}" "~\$0.25/hr"
131+
delete_resource "msk-cluster" "$carn"
132+
done < <(aws kafka list-clusters-v2 --region "$REGION" --query 'ClusterInfoList[*].[ClusterArn,ClusterName,State]' --output text 2>/dev/null)
133+
134+
# --- RDS Instances ---
135+
while IFS=$'\t' read -r dbid engine status; do
136+
[ -z "$dbid" ] && continue
137+
tags=$(aws rds list-tags-for-resource --resource-name "arn:aws:rds:${REGION}:$(aws sts get-caller-identity --query Account --output text):db:$dbid" --region "$REGION" --query 'TagList' --output json 2>/dev/null)
138+
tutorial=$(echo "$tags" | python3 -c "import sys,json; t={i['Key']:i['Value'] for i in json.load(sys.stdin) or []}; print(t.get('tutorial',''))" 2>/dev/null)
139+
match_tutorial "$tutorial" || continue
140+
found "rds-instance" "$dbid" "$engine" "${tutorial:-untagged}" "~\$0.10/hr"
141+
delete_resource "rds-instance" "$dbid"
142+
done < <(aws rds describe-db-instances --region "$REGION" --query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus]' --output text 2>/dev/null)
143+
144+
# --- ElastiCache Serverless ---
145+
for cache in $(aws elasticache describe-serverless-caches --region "$REGION" --query 'ServerlessCaches[*].ServerlessCacheName' --output text 2>/dev/null); do
146+
found "elasticache" "$cache" "$cache" "check-tags" "~\$0.05/hr"
147+
delete_resource "elasticache" "$cache"
148+
done
149+
150+
# --- Redshift Serverless ---
151+
for wg in $(aws redshift-serverless list-workgroups --region "$REGION" --query 'workgroups[*].workgroupName' --output text 2>/dev/null); do
152+
found "redshift-serverless-wg" "$wg" "$wg" "check-tags" "~\$0.25/hr"
153+
delete_resource "redshift-serverless-wg" "$wg"
154+
done
155+
156+
# --- ECS Clusters with services ---
157+
for cluster_arn in $(aws ecs list-clusters --region "$REGION" --query 'clusterArns[]' --output text 2>/dev/null); do
158+
cluster=$(echo "$cluster_arn" | awk -F/ '{print $NF}')
159+
[[ "$cluster" == "DocSmith"* ]] && continue # Skip pipeline cluster
160+
svcs=$(aws ecs list-services --region "$REGION" --cluster "$cluster" --query 'serviceArns[]' --output text 2>/dev/null)
161+
for svc_arn in $svcs; do
162+
svc=$(echo "$svc_arn" | awk -F/ '{print $NF}')
163+
found "ecs-service" "$cluster/$svc" "$svc" "check-tags" "per-task"
164+
delete_resource "ecs-service" "$cluster/$svc"
165+
done
166+
# Delete empty clusters
167+
task_count=$(aws ecs describe-clusters --region "$REGION" --clusters "$cluster" --query 'clusters[0].runningTasksCount' --output text 2>/dev/null)
168+
svc_count=$(aws ecs describe-clusters --region "$REGION" --clusters "$cluster" --query 'clusters[0].activeServicesCount' --output text 2>/dev/null)
169+
if [ "${task_count:-0}" = "0" ] && [ "${svc_count:-0}" = "0" ]; then
170+
found "ecs-cluster" "$cluster" "(empty)" "check-tags" "free"
171+
delete_resource "ecs-cluster" "$cluster"
172+
fi
173+
done
174+
175+
# --- Q Business Apps (us-east-1) ---
176+
while IFS=$'\t' read -r appid appname status; do
177+
[ -z "$appid" ] && continue
178+
found "qbusiness-app" "$appid" "$appname" "check-tags" "per-query"
179+
delete_resource "qbusiness-app" "$appid"
180+
done < <(aws qbusiness list-applications --region us-east-1 --query 'applications[*].[applicationId,displayName,status]' --output text 2>/dev/null)
181+
182+
echo ""
183+
if $DELETE; then
184+
echo "Found $FOUND resources, deleted $DELETED."
185+
else
186+
echo "Found $FOUND tutorial resources. Run with --delete to remove them."
187+
fi

0 commit comments

Comments
 (0)