#!/usr/bin/env bash
# Read-only audit for AWS cost launches from October 14 to 27, 2026.
#   1. ECS Gateway: ALBs that front a single ECS service (consolidation candidates)
#   2. Lambda recursive loop detection: functions opted out with RecursiveLoop=Allow
#   3. DynamoDB global tables: replica Regions per table (disassociation candidates)
#   4. ECS GPU sharing (NVIDIA MPS): task definitions that reserve whole GPUs
#   5. EMR on EKS: virtual clusters that get the 30% EMR charge cut automatically
#   6. Aurora Serverless PostgreSQL: clusters capped at 256 ACUs (384 now allowed)
#
# Requires: AWS CLI v2, jq, bash 4+. Only Describe/List/Get calls; it changes nothing.
# Usage:    REGION=us-east-1 ./cost-audit.sh
# Override: ALB_HOURLY=0.0225 (us-east-1 list price, October 2026)
set -euo pipefail

REGION="${REGION:-${AWS_REGION:-us-east-1}}"
ALB_HOURLY="${ALB_HOURLY:-0.0225}"
HOURS_PER_MONTH=730

echo "Region: $REGION"
echo

echo "== 1. ALBs fronting ECS services =="
declare -A svc_per_alb=()
for cluster in $(aws ecs list-clusters --region "$REGION" --query 'clusterArns[]' --output text); do
  aws ecs list-services --cluster "$cluster" --region "$REGION" --output json |
    jq -r '.serviceArns[]' | xargs -n 10 | while read -r batch; do
      [ -z "$batch" ] && continue
      # shellcheck disable=SC2086
      aws ecs describe-services --cluster "$cluster" --services $batch --region "$REGION" --output json |
        jq -r '.services[] | .loadBalancers[]? | .targetGroupArn // empty'
    done
done | sort -u > /tmp/ecs-tgs.$$

while read -r tg; do
  [ -z "$tg" ] && continue
  alb=$(aws elbv2 describe-target-groups --target-group-arns "$tg" --region "$REGION" \
    --query 'TargetGroups[0].LoadBalancerArns[0]' --output text)
  [ "$alb" = "None" ] && continue
  svc_per_alb["$alb"]=$(( ${svc_per_alb["$alb"]:-0} + 1 ))
done < /tmp/ecs-tgs.$$
rm -f /tmp/ecs-tgs.$$

single=0
for alb in "${!svc_per_alb[@]}"; do
  name=${alb#*loadbalancer/app/}
  printf '  %-45s ecs_target_groups=%s\n' "${name%%/*}" "${svc_per_alb[$alb]}"
  [ "${svc_per_alb[$alb]}" -eq 1 ] && single=$((single + 1))
done
echo "  ALBs serving exactly one ECS target group: $single"
awk -v n="$single" -v h="$ALB_HOURLY" -v m="$HOURS_PER_MONTH" \
  'BEGIN { printf "  Hourly charge on those ALBs: ~$%.2f/month (LCUs excluded)\n", n * h * m }'
echo "  ECS Gateway needs awsvpc network mode and HTTPS on 443; check each service before moving it."
echo

echo "== 2. Lambda functions with recursive loop detection turned off =="
aws lambda list-functions --region "$REGION" --query 'Functions[].FunctionName' --output text |
  tr '\t' '\n' | while read -r fn; do
    [ -z "$fn" ] && continue
    mode=$(aws lambda get-function-recursion-config --function-name "$fn" --region "$REGION" \
      --query 'RecursiveLoop' --output text)
    [ "$mode" = "Allow" ] && echo "  $fn (RecursiveLoop=Allow)"
  done
echo "  (no output above means every function uses the default, Terminate)"
echo

echo "== 3. DynamoDB global tables and their replica Regions =="
for table in $(aws dynamodb list-tables --region "$REGION" --query 'TableNames[]' --output text); do
  aws dynamodb describe-table --table-name "$table" --region "$REGION" --output json |
    jq -r --arg t "$table" '.Table | select(.Replicas) |
      "  \($t): version=\(.GlobalTableVersion // "n/a") replicas=\([.Replicas[].RegionName] | join(","))"'
done
echo "  Disassociation applies to MREC tables on version 2019.11.21 only, and cannot be undone."
echo

echo "== 4. ECS task definitions that reserve whole GPUs (MPS sharing candidates) =="
aws ecs list-task-definition-families --status ACTIVE --region "$REGION" \
  --query 'families[]' --output text | tr '\t' '\n' | while read -r fam; do
    [ -z "$fam" ] && continue
    aws ecs describe-task-definition --task-definition "$fam" --region "$REGION" --output json |
      jq -r '.taskDefinition | . as $td | [.containerDefinitions[].resourceRequirements[]?
        | select(.type == "GPU" and (.value // "") != "")] | select(length > 0)
        | "  \($td.family):\($td.revision) gpus=\(map(.value | tonumber) | add)"'
  done
echo "  MPS sharing needs ECS on EC2 or Managed Instances (not Fargate) and a supported GPU family."
echo

echo "== 5. EMR on EKS virtual clusters (30% EMR charge cut applies automatically) =="
aws emr-containers list-virtual-clusters --states RUNNING --region "$REGION" --output json |
  jq -r '.virtualClusters[]? | "  \(.name) eks=\(.containerProvider.id)"'
echo "  The cut applies to the EMR per-vCPU-hour and per-GB-hour charge, not EC2 or EKS."
echo

echo "== 6. Aurora Serverless PostgreSQL clusters at the old 256 ACU ceiling =="
aws rds describe-db-clusters --region "$REGION" --output json |
  jq -r '.DBClusters[] | select(.Engine == "aurora-postgresql")
    | select((.ServerlessV2ScalingConfiguration.MaxCapacity // 0) >= 256)
    | "  \(.DBClusterIdentifier) engine=\(.EngineVersion) max_acu=\(.ServerlessV2ScalingConfiguration.MaxCapacity)"'
echo "  384 ACUs needs PostgreSQL 18.4, 17.10, 16.14, 15.18 or 14.23 on Platform Version 4."
echo "  A higher ceiling is headroom, not a discount: every ACU-hour still bills."
