#!/usr/bin/env bash # ============================================================================= # cost-audit.sh — OSVauco billable resource snapshot # Read-only, safe to run anytime. # Usage: source .env && bash scripts/cost-audit.sh # ============================================================================= set -euo pipefail : "${PROJECT_ID:?Set PROJECT_ID}" : "${REGION:?Set REGION}" GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' CYAN='\033[0;36m' DIM='\033[2m' BOLD='\033[1m' NC='\033[0m' OK="${GREEN}[OK]${NC}" WARN="${YELLOW}[!!]${NC}" FAIL="${RED}[XX]${NC}" DOT="${DIM}[--]${NC}" clear printf "${CYAN}${BOLD}" echo " -------------------------------------------------------" echo " OSVauco COST AUDIT" printf " -------------------------------------------------------${NC}\n" printf " ${DIM}Project : ${PROJECT_ID} | Region : ${REGION}${NC}\n" printf " ${DIM}$(date '+%Y-%m-%d %H:%M %Z')${NC}\n" echo "" ANY_RISK=false # ----------------------------------------------------------------------------- # 1. CLOUD RUN # ----------------------------------------------------------------------------- printf "${BOLD}${CYAN} [ CLOUD RUN ]${NC} ${DIM}(scale-to-zero = no idle cost)${NC}\n" CR=$(gcloud run services list \ --project="${PROJECT_ID}" \ --region="${REGION}" \ --format="value(name,status.url)" 2>/dev/null || true) if [[ -z "$CR" ]]; then printf " ${OK} No services deployed\n" else while IFS=$'\t' read -r NAME URL; do MIN=$(gcloud run services describe "$NAME" \ --project="${PROJECT_ID}" --region="${REGION}" \ --format="value(spec.template.metadata.annotations.'autoscaling.knative.dev/minScale')" \ 2>/dev/null || echo "0") MIN=${MIN:-0} if [[ "$MIN" -gt 0 ]]; then printf " ${WARN} %-30s ${YELLOW}min-instances=%s (idle cost!)${NC}\n" "$NAME" "$MIN" ANY_RISK=true else printf " ${OK} %-30s ${DIM}scale-to-zero${NC}\n" "$NAME" fi done <<< "$CR" fi echo "" # ----------------------------------------------------------------------------- # 2. COMPUTE ENGINE # ----------------------------------------------------------------------------- printf "${BOLD}${CYAN} [ COMPUTE ENGINE ]${NC} ${DIM}(VMs always-on = always costs)${NC}\n" VMS=$(gcloud compute instances list \ --project="${PROJECT_ID}" \ --format="value(name,zone,status,machineType.basename())" 2>/dev/null || true) if [[ -z "$VMS" ]]; then printf " ${OK} No VM instances\n" else ANY_RISK=true while IFS=$'\t' read -r NAME ZONE STATUS MTYPE; do if [[ "$STATUS" == "RUNNING" ]]; then printf " ${FAIL} %-24s ${RED}RUNNING${NC} zone:%-20s type:%s\n" "$NAME" "$ZONE" "$MTYPE" else printf " ${WARN} %-24s ${YELLOW}%-8s${NC} zone:%-20s type:%s\n" "$NAME" "$STATUS" "$ZONE" "$MTYPE" fi done <<< "$VMS" fi echo "" # ----------------------------------------------------------------------------- # 3. CLOUD SQL # ----------------------------------------------------------------------------- printf "${BOLD}${CYAN} [ CLOUD SQL ]${NC} ${DIM}(instances always-on when RUNNABLE)${NC}\n" SQL=$(gcloud sql instances list \ --project="${PROJECT_ID}" \ --format="value(name,region,databaseVersion,settings.tier,state)" 2>/dev/null || true) if [[ -z "$SQL" ]]; then printf " ${OK} No Cloud SQL instances\n" else ANY_RISK=true while IFS=$'\t' read -r NAME SQLREGION DBVER TIER STATE; do if [[ "$STATE" == "RUNNABLE" ]]; then printf " ${FAIL} %-22s ${RED}RUNNABLE${NC} tier:%-14s db:%s\n" "$NAME" "$TIER" "$DBVER" else printf " ${WARN} %-22s ${YELLOW}%-10s${NC} tier:%-14s db:%s\n" "$NAME" "$STATE" "$TIER" "$DBVER" fi done <<< "$SQL" fi echo "" # ----------------------------------------------------------------------------- # 4. CLOUD COMPOSER # ----------------------------------------------------------------------------- printf "${BOLD}${CYAN} [ CLOUD COMPOSER ]${NC} ${DIM}(managed Airflow = very expensive)${NC}\n" COMP=$(gcloud composer environments list \ --project="${PROJECT_ID}" \ --locations="${REGION}" \ --format="value(name,location,state)" 2>/dev/null || true) if [[ -z "$COMP" ]]; then printf " ${OK} No Composer environments\n" else ANY_RISK=true while IFS=$'\t' read -r NAME LOC STATE; do printf " ${FAIL} %-24s ${RED}%-10s${NC} loc:%s\n" "$NAME" "$STATE" "$LOC" done <<< "$COMP" fi echo "" # ----------------------------------------------------------------------------- # 5. MEMORY BANK / REASONING ENGINES # ----------------------------------------------------------------------------- printf "${BOLD}${CYAN} [ MEMORY BANK ]${NC} ${DIM}(\$0.0994/vCPU-hr continuous billing)${NC}\n" ENGINES=$(gcloud ai reasoning-engines list \ --project="${PROJECT_ID}" \ --region="${REGION}" \ --format="value(name,displayName)" 2>/dev/null || true) if [[ -z "$ENGINES" ]]; then printf " ${OK} No active Memory Bank instances\n" else ANY_RISK=true ENGINE_COUNT=$(echo "$ENGINES" | grep -c . || true) printf " ${FAIL} ${RED}${BOLD}${ENGINE_COUNT} INSTANCE(S) RUNNING — DELETE WHEN NOT NEEDED${NC}\n" while IFS=$'\t' read -r ENAME EDISPLAY; do SHORT=$(echo "$ENAME" | awk -F'/' '{print $NF}') printf " ${WARN} %-22s ${DIM}id:%s${NC}\n" "${EDISPLAY:-unnamed}" "$SHORT" printf " ${DIM} Delete: gcloud ai reasoning-engines delete %s --project=%s --region=%s --quiet${NC}\n" "$SHORT" "${PROJECT_ID}" "${REGION}" done <<< "$ENGINES" fi echo "" # ----------------------------------------------------------------------------- # 6. FIRESTORE # ----------------------------------------------------------------------------- printf "${BOLD}${CYAN} [ FIRESTORE ]${NC}\n" gcloud firestore databases list \ --project="${PROJECT_ID}" \ --format="table[no-heading](name,type,locationId,freeTier)" 2>/dev/null | while read -r LINE; do if echo "$LINE" | grep -q "True\|true"; then printf " ${OK} %-40s ${DIM}(free tier)${NC}\n" "$LINE" else printf " ${WARN} %-40s ${YELLOW}(paid tier — watch usage)${NC}\n" "$LINE" fi done || printf " ${DOT} No Firestore databases\n" echo "" # ----------------------------------------------------------------------------- # 7. ARTIFACT REGISTRY # ----------------------------------------------------------------------------- printf "${BOLD}${CYAN} [ ARTIFACT REGISTRY ]${NC} ${DIM}(storage cost per GB)${NC}\n" REPOS=$(gcloud artifacts repositories list \ --project="${PROJECT_ID}" \ --location="${REGION}" \ --format="value(name.basename(),format,sizeBytes)" 2>/dev/null || true) if [[ -z "$REPOS" ]]; then printf " ${OK} No Artifact Registry repos\n" else while IFS=$'\t' read -r NAME FMT SIZE; do # sizeBytes kan være float (f.eks. 345900.5) — bruk awk for sikker MB-konvertering SIZE_MB=$(awk -v s="${SIZE:-0}" 'BEGIN { printf "%.1f", s / 1048576 }') printf " ${OK} %-28s fmt:%-8s size:${SIZE_MB} MB\n" "$NAME" "$FMT" done <<< "$REPOS" fi echo "" # ----------------------------------------------------------------------------- # SUMMARY # ----------------------------------------------------------------------------- printf "${DIM} -------------------------------------------------------${NC}\n" if [[ "$ANY_RISK" == true ]]; then printf " ${WARN} ${YELLOW}${BOLD}ATTENTION: Billable resources detected above — review!${NC}\n" else printf " ${OK} ${GREEN}${BOLD}All clear — no unexpected idle resources detected.${NC}\n" fi printf " ${DIM}Full billing: https://console.cloud.google.com/billing?project=${PROJECT_ID}${NC}\n" printf "${DIM} -------------------------------------------------------${NC}\n" echo ""