mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 06:53:17 +00:00
cfn-stack-decommission.sh: report-by-default stack retirement; pre-flight predicts DeletionPolicy:Retain orphans + consumed-export blocks before delete (distilled from the LedgerFlow decommission). --execute to act. resource-usage-probe.sh: is-it-used probe (RDS connections/Lambda invocations/ DDB capacity/EBS attachment) to choose retire-vs-harden before acting on an encrypt/migrate finding (the database-1 H-19 lesson).
80 lines
4.2 KiB
Bash
Executable file
80 lines
4.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# cfn-stack-decommission.sh — safely retire a CloudFormation/CDK stack.
|
|
#
|
|
# Reports first (default), acts only with --execute. The value is the pre-flight:
|
|
# it predicts what will ORPHAN (DeletionPolicy: Retain resources survive a stack
|
|
# delete) and what will BLOCK the delete (consumed exports, non-empty buckets),
|
|
# so you don't discover surviving tables/buckets after the fact.
|
|
#
|
|
# Built from the Day 4 LedgerFlow decommission, where 4 of 5 DynamoDB tables +
|
|
# 2 of 3 S3 buckets were RemovalPolicy.RETAIN and orphaned. See feedback memory
|
|
# `feedback_cfn_decommission_and_remediation`.
|
|
#
|
|
# Usage:
|
|
# scripts/cfn-stack-decommission.sh [--profile NAME] [--execute] STACK
|
|
#
|
|
# (no --execute) REPORT only: termination protection, consumed exports,
|
|
# Retain resources (orphans-to-be), in-stack S3 buckets.
|
|
# --execute Disable termination protection, empty Delete-policy buckets,
|
|
# delete the stack, wait, then delete the Retain orphans.
|
|
#
|
|
set -euo pipefail
|
|
PROFILE_ARG=(); EXECUTE=0; STACK=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--profile) PROFILE_ARG=(--profile "$2"); shift 2 ;;
|
|
--execute) EXECUTE=1; shift ;;
|
|
-h|--help) sed -n '2,22p' "$0"; exit 0 ;;
|
|
-*) echo "unknown flag: $1" >&2; exit 2 ;;
|
|
*) STACK="$1"; shift ;;
|
|
esac
|
|
done
|
|
[[ -z "$STACK" ]] && { echo "usage: $0 [--profile NAME] [--execute] STACK" >&2; exit 2; }
|
|
aws_() { aws "${PROFILE_ARG[@]}" "$@"; }
|
|
R="us-east-1"
|
|
|
|
echo "== stack: $STACK =="
|
|
aws_ cloudformation describe-stacks --stack-name "$STACK" --region "$R" \
|
|
--query 'Stacks[0].{Status:StackStatus,TermProt:EnableTerminationProtection}' --output table
|
|
|
|
echo "-- consumed exports (any import BLOCKS the delete) --"
|
|
BLOCKED=0
|
|
for e in $(aws_ cloudformation list-exports --region "$R" \
|
|
--query "Exports[?ExportingStackId && contains(ExportingStackId,':stack/$STACK/')].Name" --output text 2>/dev/null); do
|
|
imp=$(aws_ cloudformation list-imports --export-name "$e" --region "$R" --query 'Imports' --output text 2>/dev/null || true)
|
|
if [[ -n "$imp" && "$imp" != "None" ]]; then echo " BLOCK: export $e imported by: $imp"; BLOCKED=1; fi
|
|
done
|
|
[[ $BLOCKED -eq 0 ]] && echo " none"
|
|
|
|
echo "-- DeletionPolicy: Retain resources (these ORPHAN, survive the delete) --"
|
|
TMP="$(aws_ cloudformation get-template --stack-name "$STACK" --region "$R" --query TemplateBody --output json)"
|
|
echo "$TMP" | python3 -c '
|
|
import json,sys
|
|
res=json.load(sys.stdin).get("Resources",{})
|
|
orphans=[(r.get("Type"),lid,r.get("Properties",{}).get("TableName") or r.get("Properties",{}).get("BucketName") or "")
|
|
for lid,r in res.items() if r.get("DeletionPolicy")=="Retain"]
|
|
[print(f" {t:<28} {lid} {name}") for t,lid,name in sorted(orphans)] or print(" none")
|
|
'
|
|
|
|
echo "-- in-stack S3 buckets (non-empty Delete-policy buckets block; check auto-delete) --"
|
|
for b in $(aws_ cloudformation list-stack-resources --stack-name "$STACK" --region "$R" \
|
|
--query "StackResourceSummaries[?ResourceType=='AWS::S3::Bucket'].PhysicalResourceId" --output text 2>/dev/null); do
|
|
n=$(aws_ s3api list-objects-v2 --bucket "$b" --max-items 1 --query 'KeyCount' --output text 2>/dev/null || echo "?")
|
|
v=$(aws_ s3api get-bucket-versioning --bucket "$b" --query 'Status' --output text 2>/dev/null || echo "-")
|
|
echo " $b objects~=$n versioning=$v"
|
|
done
|
|
|
|
if [[ $EXECUTE -eq 0 ]]; then
|
|
echo; echo "REPORT ONLY. Re-run with --execute to delete (after reviewing the orphans + blocks above)."
|
|
exit 0
|
|
fi
|
|
[[ $BLOCKED -eq 1 ]] && { echo "ABORT: a consumed export blocks the delete (see above)." >&2; exit 1; }
|
|
|
|
read -r -p "EXECUTE decommission of '$STACK'? [y/N] " ans; [[ "$ans" =~ ^[Yy]$ ]] || { echo "aborted"; exit 0; }
|
|
aws_ cloudformation update-termination-protection --stack-name "$STACK" --no-enable-termination-protection --region "$R" >/dev/null 2>&1 || true
|
|
echo "deleting stack..."
|
|
aws_ cloudformation delete-stack --stack-name "$STACK" --region "$R"
|
|
aws_ cloudformation wait stack-delete-complete --stack-name "$STACK" --region "$R"
|
|
echo "stack deleted. Review the Retain orphans above and remove them with delete-table / delete-bucket"
|
|
echo "(versioned buckets: purge all versions + delete-markers first — see the iam-user-delete sibling pattern)."
|