a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
642 lines
22 KiB
Bash
642 lines
22 KiB
Bash
#!/usr/bin/env bash
|
|
#########################################################################################
|
|
# #
|
|
# OCI Free Tier Monitor #
|
|
# Monitor Oracle Cloud Infrastructure Always Free tier usage #
|
|
# #
|
|
# Author: Phil Connor #
|
|
# Contact: contact@mylinux.work #
|
|
# License: MIT #
|
|
# Version: 1.00 #
|
|
# #
|
|
#########################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────────────
|
|
|
|
WARN_PCT="${OFT_WARN_PCT:-80}"
|
|
CRIT_PCT="${OFT_CRIT_PCT:-95}"
|
|
FORMAT="text"
|
|
INTERVAL="${OFT_INTERVAL:-3600}"
|
|
SLACK_WEBHOOK="${OFT_SLACK_WEBHOOK:-}"
|
|
COMPARTMENT_ID="${OCI_COMPARTMENT_ID:-}"
|
|
OCI_PROFILE="${OCI_CLI_PROFILE:-DEFAULT}"
|
|
VERBOSE="${VERBOSE:-false}"
|
|
COLOR="${COLOR:-auto}"
|
|
RUN_MODE=""
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
|
|
# ── Always Free Limits ────────────────────────────────────────────────────────
|
|
|
|
LIMIT_AMD_INSTANCES=2
|
|
LIMIT_ARM_OCPU=4
|
|
LIMIT_ARM_RAM_GB=24
|
|
LIMIT_BLOCK_STORAGE_GB=200
|
|
LIMIT_VOLUME_BACKUPS=5
|
|
LIMIT_OBJECT_STORAGE_GB=10
|
|
LIMIT_AUTONOMOUS_DB=2
|
|
LIMIT_LOAD_BALANCERS=1
|
|
|
|
# ── Colors ────────────────────────────────────────────────────────────────────
|
|
|
|
setup_colors() {
|
|
if [[ "$COLOR" == "never" ]]; then
|
|
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" DIM="" RESET=""
|
|
return
|
|
fi
|
|
if [[ "$COLOR" == "auto" && ! -t 1 ]]; then
|
|
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" DIM="" RESET=""
|
|
return
|
|
fi
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
RESET='\033[0m'
|
|
}
|
|
|
|
# ── Logging ───────────────────────────────────────────────────────────────────
|
|
|
|
log() { printf "${GREEN}%s${RESET}\n" "$*"; }
|
|
warn() { printf "${YELLOW}⚠ %s${RESET}\n" "$*" >&2; }
|
|
err() { printf "${RED}✗ %s${RESET}\n" "$*" >&2; }
|
|
verbose() { [[ "$VERBOSE" == "true" ]] && printf "${DIM} %s${RESET}\n" "$*" >&2 || true; }
|
|
die() { err "$*"; exit 1; }
|
|
|
|
# ── Help ──────────────────────────────────────────────────────────────────────
|
|
|
|
show_help() {
|
|
cat <<EOF
|
|
Usage: $SCRIPT_NAME [MODE] [OPTIONS]
|
|
|
|
Modes:
|
|
--check Quick status check (exit 0=ok, 1=warn, 2=crit)
|
|
--report Detailed usage report for all resources
|
|
--watch Continuous monitoring loop
|
|
--alerts Show only resources exceeding thresholds
|
|
|
|
Options:
|
|
--format FMT Output format: text, json, prometheus (default: text)
|
|
--warn PCT Warning threshold percentage (default: 80)
|
|
--crit PCT Critical threshold percentage (default: 95)
|
|
--interval SECS Watch mode poll interval in seconds (default: 3600)
|
|
--slack-webhook URL Slack webhook URL for threshold alerts
|
|
--compartment-id ID OCI compartment OCID (default: tenancy root)
|
|
--no-color Disable colored output
|
|
--verbose Debug output
|
|
--help Show this help
|
|
|
|
Environment Variables:
|
|
OCI_CLI_PROFILE OCI CLI config profile (default: DEFAULT)
|
|
OCI_COMPARTMENT_ID Compartment OCID
|
|
OFT_WARN_PCT Warning threshold (default: 80)
|
|
OFT_CRIT_PCT Critical threshold (default: 95)
|
|
OFT_SLACK_WEBHOOK Slack webhook URL
|
|
OFT_INTERVAL Watch interval in seconds (default: 3600)
|
|
VERBOSE Debug output (true/false)
|
|
COLOR Color mode: auto, always, never
|
|
|
|
Examples:
|
|
$SCRIPT_NAME --check
|
|
$SCRIPT_NAME --report --format json
|
|
$SCRIPT_NAME --watch --interval 1800 --slack-webhook "https://hooks.slack.com/..."
|
|
$SCRIPT_NAME --alerts --warn 70 --crit 90
|
|
$SCRIPT_NAME --report --format prometheus > /var/lib/node_exporter/textfile/oci.prom
|
|
EOF
|
|
}
|
|
|
|
# ── Argument Parsing ──────────────────────────────────────────────────────────
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--check) RUN_MODE="check"; shift ;;
|
|
--report) RUN_MODE="report"; shift ;;
|
|
--watch) RUN_MODE="watch"; shift ;;
|
|
--alerts) RUN_MODE="alerts"; shift ;;
|
|
--format) FORMAT="$2"; shift 2 ;;
|
|
--warn) WARN_PCT="$2"; shift 2 ;;
|
|
--crit) CRIT_PCT="$2"; shift 2 ;;
|
|
--interval) INTERVAL="$2"; shift 2 ;;
|
|
--slack-webhook) SLACK_WEBHOOK="$2"; shift 2 ;;
|
|
--compartment-id) COMPARTMENT_ID="$2"; shift 2 ;;
|
|
--no-color) COLOR="never"; shift ;;
|
|
--verbose) VERBOSE="true"; shift ;;
|
|
--help) show_help; exit 0 ;;
|
|
*) die "Unknown option: $1" ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$RUN_MODE" ]]; then err "No mode specified"; echo ""; show_help; exit 1; fi
|
|
}
|
|
|
|
# ── Dependency Check ──────────────────────────────────────────────────────────
|
|
|
|
check_deps() {
|
|
local missing=()
|
|
command -v oci >/dev/null 2>&1 || missing+=("oci")
|
|
command -v jq >/dev/null 2>&1 || missing+=("jq")
|
|
command -v curl >/dev/null 2>&1 || missing+=("curl")
|
|
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
die "Missing required tools: ${missing[*]}"
|
|
fi
|
|
|
|
verbose "Dependencies satisfied: oci, jq, curl"
|
|
}
|
|
|
|
# ── OCI Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
oci_cmd() {
|
|
oci --profile "$OCI_PROFILE" "$@" 2>/dev/null
|
|
}
|
|
|
|
get_compartment_id() {
|
|
if [[ -n "$COMPARTMENT_ID" ]]; then
|
|
verbose "Using compartment: $COMPARTMENT_ID"
|
|
return
|
|
fi
|
|
COMPARTMENT_ID=$(oci_cmd iam compartment list \
|
|
--include-root \
|
|
--query 'data[?contains("lifecycle-state", `ACTIVE`)] | [0]."compartment-id"' \
|
|
--raw-output 2>/dev/null || true)
|
|
|
|
if [[ -z "$COMPARTMENT_ID" ]]; then
|
|
COMPARTMENT_ID=$(oci_cmd iam region-subscription list \
|
|
--query 'data[0]."tenancy-id"' --raw-output 2>/dev/null || true)
|
|
fi
|
|
|
|
if [[ -z "$COMPARTMENT_ID" ]]; then
|
|
die "Cannot determine compartment/tenancy ID. Use --compartment-id."
|
|
fi
|
|
|
|
verbose "Resolved root compartment: $COMPARTMENT_ID"
|
|
}
|
|
|
|
get_tenancy_name() {
|
|
local name
|
|
name=$(oci_cmd iam tenancy get --tenancy-id "$COMPARTMENT_ID" \
|
|
--query 'data.name' --raw-output 2>/dev/null || echo "unknown")
|
|
echo "$name"
|
|
}
|
|
|
|
# ── Resource Check Functions ──────────────────────────────────────────────────
|
|
|
|
declare -A USAGE
|
|
declare -A LIMITS
|
|
declare -A UNITS
|
|
|
|
check_compute() {
|
|
verbose "Checking compute instances..."
|
|
|
|
local instances
|
|
instances=$(oci_cmd compute instance list \
|
|
--compartment-id "$COMPARTMENT_ID" \
|
|
--lifecycle-state RUNNING \
|
|
--query 'data' 2>/dev/null || echo "[]")
|
|
|
|
local amd_count=0
|
|
local arm_ocpu=0
|
|
local arm_ram=0
|
|
|
|
while IFS= read -r line; do
|
|
local shape
|
|
shape=$(echo "$line" | jq -r '.shape // ""')
|
|
if [[ "$shape" == *"A1"* ]] || [[ "$shape" == *"Ampere"* ]]; then
|
|
local ocpu mem
|
|
ocpu=$(echo "$line" | jq -r '."shape-config"."ocpus" // 0')
|
|
mem=$(echo "$line" | jq -r '."shape-config"."memory-in-gbs" // 0')
|
|
arm_ocpu=$(echo "$arm_ocpu + $ocpu" | bc)
|
|
arm_ram=$(echo "$arm_ram + $mem" | bc)
|
|
else
|
|
((amd_count++)) || true
|
|
fi
|
|
done < <(echo "$instances" | jq -c '.[]')
|
|
|
|
USAGE[compute_amd]=$amd_count
|
|
LIMITS[compute_amd]=$LIMIT_AMD_INSTANCES
|
|
UNITS[compute_amd]="inst"
|
|
|
|
USAGE[compute_arm_ocpu]=$arm_ocpu
|
|
LIMITS[compute_arm_ocpu]=$LIMIT_ARM_OCPU
|
|
UNITS[compute_arm_ocpu]="OCPU"
|
|
|
|
USAGE[compute_arm_ram]=$arm_ram
|
|
LIMITS[compute_arm_ram]=$LIMIT_ARM_RAM_GB
|
|
UNITS[compute_arm_ram]="GB"
|
|
|
|
verbose " AMD instances: ${amd_count}/${LIMIT_AMD_INSTANCES}"
|
|
verbose " Arm OCPU: ${arm_ocpu}/${LIMIT_ARM_OCPU}"
|
|
verbose " Arm RAM: ${arm_ram}GB/${LIMIT_ARM_RAM_GB}GB"
|
|
}
|
|
|
|
check_block_storage() {
|
|
verbose "Checking block storage..."
|
|
|
|
local volumes
|
|
volumes=$(oci_cmd bv volume list \
|
|
--compartment-id "$COMPARTMENT_ID" \
|
|
--lifecycle-state AVAILABLE \
|
|
--query 'data' 2>/dev/null || echo "[]")
|
|
|
|
local total_gb=0
|
|
while IFS= read -r size; do
|
|
total_gb=$(echo "$total_gb + $size" | bc)
|
|
done < <(echo "$volumes" | jq -r '.[]."size-in-gbs" // 0')
|
|
|
|
local backups
|
|
backups=$(oci_cmd bv volume-backup list \
|
|
--compartment-id "$COMPARTMENT_ID" \
|
|
--lifecycle-state AVAILABLE \
|
|
--query 'length(data)' 2>/dev/null || echo "0")
|
|
|
|
USAGE[block_storage]=$total_gb
|
|
LIMITS[block_storage]=$LIMIT_BLOCK_STORAGE_GB
|
|
UNITS[block_storage]="GB"
|
|
|
|
USAGE[volume_backups]=$backups
|
|
LIMITS[volume_backups]=$LIMIT_VOLUME_BACKUPS
|
|
UNITS[volume_backups]=""
|
|
|
|
verbose " Block storage: ${total_gb}GB/${LIMIT_BLOCK_STORAGE_GB}GB"
|
|
verbose " Volume backups: ${backups}/${LIMIT_VOLUME_BACKUPS}"
|
|
}
|
|
|
|
check_object_storage() {
|
|
verbose "Checking object storage..."
|
|
|
|
local namespace
|
|
namespace=$(oci_cmd os ns get --query 'data' --raw-output 2>/dev/null || echo "")
|
|
|
|
if [[ -z "$namespace" ]]; then
|
|
warn "Cannot determine object storage namespace"
|
|
USAGE[object_storage]=0
|
|
LIMITS[object_storage]=$LIMIT_OBJECT_STORAGE_GB
|
|
UNITS[object_storage]="GB"
|
|
return
|
|
fi
|
|
|
|
local buckets
|
|
buckets=$(oci_cmd os bucket list \
|
|
--compartment-id "$COMPARTMENT_ID" \
|
|
--namespace-name "$namespace" \
|
|
--query 'data[].name' 2>/dev/null || echo "[]")
|
|
|
|
local total_bytes=0
|
|
while IFS= read -r bucket; do
|
|
[[ -z "$bucket" || "$bucket" == "null" ]] && continue
|
|
local size
|
|
size=$(oci_cmd os bucket get \
|
|
--namespace-name "$namespace" \
|
|
--bucket-name "$bucket" \
|
|
--fields "approximateSize" \
|
|
--query 'data."approximate-size"' --raw-output 2>/dev/null || echo "0")
|
|
[[ "$size" == "null" ]] && size=0
|
|
total_bytes=$(echo "$total_bytes + $size" | bc)
|
|
done < <(echo "$buckets" | jq -r '.[]')
|
|
|
|
local total_gb
|
|
total_gb=$(echo "scale=1; $total_bytes / 1073741824" | bc)
|
|
|
|
USAGE[object_storage]=$total_gb
|
|
LIMITS[object_storage]=$LIMIT_OBJECT_STORAGE_GB
|
|
UNITS[object_storage]="GB"
|
|
|
|
verbose " Object storage: ${total_gb}GB/${LIMIT_OBJECT_STORAGE_GB}GB"
|
|
}
|
|
|
|
check_autonomous_db() {
|
|
verbose "Checking autonomous databases..."
|
|
|
|
local count
|
|
count=$(oci_cmd db autonomous-database list \
|
|
--compartment-id "$COMPARTMENT_ID" \
|
|
--lifecycle-state AVAILABLE \
|
|
--query 'length(data)' 2>/dev/null || echo "0")
|
|
|
|
USAGE[autonomous_db]=$count
|
|
LIMITS[autonomous_db]=$LIMIT_AUTONOMOUS_DB
|
|
UNITS[autonomous_db]=""
|
|
|
|
verbose " Autonomous DB: ${count}/${LIMIT_AUTONOMOUS_DB}"
|
|
}
|
|
|
|
check_load_balancer() {
|
|
verbose "Checking load balancers..."
|
|
|
|
local count
|
|
count=$(oci_cmd lb load-balancer list \
|
|
--compartment-id "$COMPARTMENT_ID" \
|
|
--lifecycle-state ACTIVE \
|
|
--query 'length(data)' 2>/dev/null || echo "0")
|
|
|
|
USAGE[load_balancers]=$count
|
|
LIMITS[load_balancers]=$LIMIT_LOAD_BALANCERS
|
|
UNITS[load_balancers]=""
|
|
|
|
verbose " Load balancers: ${count}/${LIMIT_LOAD_BALANCERS}"
|
|
}
|
|
|
|
# ── Calculation ───────────────────────────────────────────────────────────────
|
|
|
|
calculate_pct() {
|
|
local used="$1" limit="$2"
|
|
if [[ "$limit" == "0" ]]; then
|
|
echo "0"
|
|
return
|
|
fi
|
|
echo "scale=0; ($used * 100) / $limit" | bc
|
|
}
|
|
|
|
get_status() {
|
|
local pct="$1"
|
|
if (( pct >= CRIT_PCT )); then
|
|
echo "critical"
|
|
elif (( pct >= WARN_PCT )); then
|
|
echo "warning"
|
|
else
|
|
echo "ok"
|
|
fi
|
|
}
|
|
|
|
# ── Collect All Data ──────────────────────────────────────────────────────────
|
|
|
|
RESOURCE_ORDER=(compute_amd compute_arm_ocpu compute_arm_ram block_storage volume_backups object_storage autonomous_db load_balancers)
|
|
|
|
RESOURCE_LABELS=(
|
|
"Compute (AMD)"
|
|
"Compute (Arm OCPU)"
|
|
"Compute (Arm RAM)"
|
|
"Block Storage"
|
|
"Volume Backups"
|
|
"Object Storage"
|
|
"Autonomous DB"
|
|
"Load Balancers"
|
|
)
|
|
|
|
collect_all() {
|
|
check_compute
|
|
check_block_storage
|
|
check_object_storage
|
|
check_autonomous_db
|
|
check_load_balancer
|
|
}
|
|
|
|
# ── Output Formatters ─────────────────────────────────────────────────────────
|
|
|
|
format_text() {
|
|
local alerts_only="${1:-false}"
|
|
local tenancy
|
|
tenancy=$(get_tenancy_name)
|
|
|
|
printf "\n${BOLD}OCI Free Tier Monitor${RESET}\n"
|
|
printf "Tenancy: %s\n" "$tenancy"
|
|
printf "Time: %s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
printf "\n ── Resource Usage ──\n\n"
|
|
printf " %-22s %-12s %-12s %-8s %s\n" "Resource" "Used" "Limit" "Usage" "Status"
|
|
printf " %s\n" "$(printf '%.0s─' {1..64})"
|
|
|
|
local ok_count=0 warn_count=0 crit_count=0
|
|
for i in "${!RESOURCE_ORDER[@]}"; do
|
|
local key="${RESOURCE_ORDER[$i]}"
|
|
local label="${RESOURCE_LABELS[$i]}"
|
|
local used="${USAGE[$key]}"
|
|
local limit="${LIMITS[$key]}"
|
|
local unit="${UNITS[$key]}"
|
|
local pct
|
|
pct=$(calculate_pct "$used" "$limit")
|
|
local status
|
|
status=$(get_status "$pct")
|
|
|
|
case "$status" in
|
|
ok) ((ok_count++)) || true; status_str="${GREEN}✓ OK${RESET}" ;;
|
|
warning) ((warn_count++)) || true; status_str="${YELLOW}⚠ Warning${RESET}" ;;
|
|
critical) ((crit_count++)) || true; status_str="${RED}✗ Critical${RESET}" ;;
|
|
esac
|
|
|
|
if [[ "$alerts_only" == "true" && "$status" == "ok" ]]; then
|
|
continue
|
|
fi
|
|
|
|
local used_str="${used}${unit:+ $unit}"
|
|
local limit_str="${limit}${unit:+ $unit}"
|
|
printf " %-22s %-12s %-12s %3s%% ${status_str}\n" \
|
|
"$label" "$used_str" "$limit_str" "$pct"
|
|
done
|
|
|
|
printf "\n Summary: %s OK, %s Warning, %s Critical\n\n" \
|
|
"$ok_count" "$warn_count" "$crit_count"
|
|
}
|
|
|
|
format_json() {
|
|
local alerts_only="${1:-false}"
|
|
local resources="[]"
|
|
|
|
for i in "${!RESOURCE_ORDER[@]}"; do
|
|
local key="${RESOURCE_ORDER[$i]}"
|
|
local label="${RESOURCE_LABELS[$i]}"
|
|
local used="${USAGE[$key]}"
|
|
local limit="${LIMITS[$key]}"
|
|
local pct
|
|
pct=$(calculate_pct "$used" "$limit")
|
|
local status
|
|
status=$(get_status "$pct")
|
|
|
|
if [[ "$alerts_only" == "true" && "$status" == "ok" ]]; then
|
|
continue
|
|
fi
|
|
|
|
resources=$(echo "$resources" | jq \
|
|
--arg key "$key" \
|
|
--arg label "$label" \
|
|
--argjson used "$used" \
|
|
--argjson limit "$limit" \
|
|
--argjson pct "$pct" \
|
|
--arg status "$status" \
|
|
'. + [{"resource": $key, "label": $label, "used": $used, "limit": $limit, "percent": $pct, "status": $status}]')
|
|
done
|
|
|
|
jq -n \
|
|
--arg time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--argjson warn "$WARN_PCT" \
|
|
--argjson crit "$CRIT_PCT" \
|
|
--argjson resources "$resources" \
|
|
'{"timestamp": $time, "warn_threshold": $warn, "crit_threshold": $crit, "resources": $resources}'
|
|
}
|
|
|
|
format_prometheus() {
|
|
echo "# HELP oci_free_tier_usage_percent OCI Free Tier resource usage percentage"
|
|
echo "# TYPE oci_free_tier_usage_percent gauge"
|
|
|
|
for i in "${!RESOURCE_ORDER[@]}"; do
|
|
local key="${RESOURCE_ORDER[$i]}"
|
|
local used="${USAGE[$key]}"
|
|
local limit="${LIMITS[$key]}"
|
|
local pct
|
|
pct=$(calculate_pct "$used" "$limit")
|
|
echo "oci_free_tier_usage_percent{resource=\"${key}\"} ${pct}"
|
|
done
|
|
|
|
echo "# HELP oci_free_tier_used OCI Free Tier resource current usage"
|
|
echo "# TYPE oci_free_tier_used gauge"
|
|
|
|
for i in "${!RESOURCE_ORDER[@]}"; do
|
|
local key="${RESOURCE_ORDER[$i]}"
|
|
echo "oci_free_tier_used{resource=\"${key}\"} ${USAGE[$key]}"
|
|
done
|
|
|
|
echo "# HELP oci_free_tier_limit OCI Free Tier resource limit"
|
|
echo "# TYPE oci_free_tier_limit gauge"
|
|
|
|
for i in "${!RESOURCE_ORDER[@]}"; do
|
|
local key="${RESOURCE_ORDER[$i]}"
|
|
echo "oci_free_tier_limit{resource=\"${key}\"} ${LIMITS[$key]}"
|
|
done
|
|
|
|
echo "# HELP oci_free_tier_warn_threshold Warning threshold percentage"
|
|
echo "# TYPE oci_free_tier_warn_threshold gauge"
|
|
echo "oci_free_tier_warn_threshold ${WARN_PCT}"
|
|
|
|
echo "# HELP oci_free_tier_crit_threshold Critical threshold percentage"
|
|
echo "# TYPE oci_free_tier_crit_threshold gauge"
|
|
echo "oci_free_tier_crit_threshold ${CRIT_PCT}"
|
|
}
|
|
|
|
format_output() {
|
|
local alerts_only="${1:-false}"
|
|
case "$FORMAT" in
|
|
text) format_text "$alerts_only" ;;
|
|
json) format_json "$alerts_only" ;;
|
|
prometheus) format_prometheus ;;
|
|
*) die "Unknown format: $FORMAT" ;;
|
|
esac
|
|
}
|
|
|
|
# ── Slack Notification ────────────────────────────────────────────────────────
|
|
|
|
send_slack_alert() {
|
|
[[ -z "$SLACK_WEBHOOK" ]] && return
|
|
|
|
local breached=()
|
|
for i in "${!RESOURCE_ORDER[@]}"; do
|
|
local key="${RESOURCE_ORDER[$i]}"
|
|
local label="${RESOURCE_LABELS[$i]}"
|
|
local used="${USAGE[$key]}"
|
|
local limit="${LIMITS[$key]}"
|
|
local pct
|
|
pct=$(calculate_pct "$used" "$limit")
|
|
local status
|
|
status=$(get_status "$pct")
|
|
|
|
if [[ "$status" != "ok" ]]; then
|
|
breached+=("${label}: ${used}/${limit} (${pct}%) [${status}]")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#breached[@]} -eq 0 ]]; then
|
|
verbose "No threshold breaches — skipping Slack notification"
|
|
return
|
|
fi
|
|
|
|
local message="OCI Free Tier Alert — $(date -u +%Y-%m-%dT%H:%M:%SZ)\n"
|
|
for line in "${breached[@]}"; do
|
|
message+="• ${line}\n"
|
|
done
|
|
|
|
local payload
|
|
payload=$(jq -n --arg text "$message" '{"text": $text}')
|
|
|
|
if curl -sf -o /dev/null -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-d "$payload" "$SLACK_WEBHOOK"; then
|
|
verbose "Slack notification sent"
|
|
else
|
|
warn "Failed to send Slack notification"
|
|
fi
|
|
}
|
|
|
|
# ── Exit Code ─────────────────────────────────────────────────────────────────
|
|
|
|
get_exit_code() {
|
|
local max_status="ok"
|
|
for key in "${RESOURCE_ORDER[@]}"; do
|
|
local used="${USAGE[$key]}"
|
|
local limit="${LIMITS[$key]}"
|
|
local pct
|
|
pct=$(calculate_pct "$used" "$limit")
|
|
local status
|
|
status=$(get_status "$pct")
|
|
|
|
if [[ "$status" == "critical" ]]; then
|
|
max_status="critical"
|
|
break
|
|
elif [[ "$status" == "warning" ]]; then
|
|
max_status="warning"
|
|
fi
|
|
done
|
|
|
|
case "$max_status" in
|
|
ok) echo 0 ;;
|
|
warning) echo 1 ;;
|
|
critical) echo 2 ;;
|
|
esac
|
|
}
|
|
|
|
# ── Mode Functions ────────────────────────────────────────────────────────────
|
|
|
|
run_check() {
|
|
collect_all
|
|
format_output "false"
|
|
send_slack_alert
|
|
local code
|
|
code=$(get_exit_code)
|
|
exit "$code"
|
|
}
|
|
|
|
run_report() {
|
|
collect_all
|
|
format_output "false"
|
|
send_slack_alert
|
|
}
|
|
|
|
run_alerts() {
|
|
collect_all
|
|
format_output "true"
|
|
send_slack_alert
|
|
local code
|
|
code=$(get_exit_code)
|
|
exit "$code"
|
|
}
|
|
|
|
run_watch() {
|
|
log "Starting watch mode (interval: ${INTERVAL}s)"
|
|
while true; do
|
|
collect_all
|
|
format_output "false"
|
|
send_slack_alert
|
|
verbose "Next check in ${INTERVAL}s"
|
|
sleep "$INTERVAL"
|
|
done
|
|
}
|
|
|
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
setup_colors
|
|
check_deps
|
|
get_compartment_id
|
|
|
|
case "$RUN_MODE" in
|
|
check) run_check ;;
|
|
report) run_report ;;
|
|
alerts) run_alerts ;;
|
|
watch) run_watch ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|