Files
ss-tools/examples/maintenance-api-bash.sh
busya 320f82ab95 feat(auth): implement API key authentication and management
Introduce a new API key authentication mechanism to support service-to-service
communication (e.g., Airflow, CI/CD) without browser-based JWT login.

- Add `APIKey` model and `APIKeyPrincipal` dependency for RBAC.
- Implement `require_api_key_or_jwt` dependency with environment scoping.
- Add admin CRUD endpoints for API key lifecycle management.
- Add API key management UI in System Settings.
- Update maintenance API to support explicit `environment_id` and API key auth.
- Add i18n support for API keys and connection settings.
- Include Python and Bash usage examples in the `examples/` directory.
- Update technical specifications and documentation.
2026-05-25 11:35:27 +03:00

252 lines
7.3 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# ============================================================================
# Example: External tool triggers maintenance via ss-tools API (bash)
#
# This script shows how to call the ss-tools maintenance API from any
# shell environment — CI/CD pipeline, cron job, or ad-hoc debugging.
#
# Prerequisites:
# - curl, jq (optional, for pretty-printing JSON)
# - An ss-tools API key with `maintenance:start`, `maintenance:end`
#
# Usage:
# ./maintenance-api-bash.sh start "public.messages" 4 "ss-dev"
# ./maintenance-api-bash.sh end "m-abc123"
# ./maintenance-api-bash.sh end-all "ss-dev"
# ============================================================================
set -euo pipefail
# ── Configuration ──────────────────────────────────────────────────────────
# Set these via environment variables or replace inline
BASE_URL="${SS_TOOLS_URL:-http://localhost:8000}"
API_KEY="${SS_TOOLS_API_KEY:-}"
# ── Color helpers (disabled in non-TTY) ────────────────────────────────────
if [[ -t 1 ]]; then
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
else
GREEN=''; RED=''; YELLOW=''; CYAN=''; BOLD=''; NC=''
fi
ok() { echo -e "${GREEN}${NC} $*"; }
info() { echo -e "${CYAN} ${NC} $*"; }
warn() { echo -e "${YELLOW}⚠️ ${NC} $*"; }
fail() { echo -e "${RED}${NC} $*"; exit 1; }
# ── Auth check ─────────────────────────────────────────────────────────────
check_auth() {
if [[ -z "$API_KEY" ]]; then
fail "API key not set. Export SS_TOOLS_API_KEY or edit the script."
fi
}
# ── API helpers ────────────────────────────────────────────────────────────
# Call the API with X-API-Key auth and handle common errors
api_call() {
local method="$1" # GET / POST
local endpoint="$2" # /api/maintenance/start
local data="$3" # JSON payload or empty string
local curl_args=(
-X "$method"
-sS
-H "X-API-Key: $API_KEY"
-H "Content-Type: application/json"
)
if [[ -n "$data" ]]; then
curl_args+=(-d "$data")
fi
local http_code
local response_file
response_file=$(mktemp)
http_code=$(curl -w '%{http_code}' "${curl_args[@]}" \
"${BASE_URL}${endpoint}" \
-o "$response_file" 2>/dev/null)
local body
body=$(<"$response_file")
rm -f "$response_file"
case "$http_code" in
202)
echo "$body" # success
return 0
;;
400)
warn "Validation error:"
echo "$body" | jq . 2>/dev/null || echo "$body"
return 1
;;
401)
fail "Authentication failed: invalid or revoked API key"
;;
403)
fail "Permission denied: API key lacks required permission"
;;
404)
local status
status=$(echo "$body" | jq -r '.status // "not_found"' 2>/dev/null)
if [[ "$status" == "already_completed" ]]; then
info "Event was already completed (idempotent)"
return 0
fi
warn "Not found (404):"
echo "$body" | jq . 2>/dev/null || echo "$body"
return 1
;;
*)
fail "Unexpected HTTP $http_code: $(echo "$body" | head -c 500)"
;;
esac
}
# ── Commands ───────────────────────────────────────────────────────────────
cmd_start() {
local tables="$1"
local duration_hours="${2:-4}"
local environment_id="$3"
local message="${4:-}"
local start_time
local end_time
start_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
end_time=$(date -u -d "+${duration_hours} hours" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
|| date -u -v "+${duration_hours}H" +"%Y-%m-%dT%H:%M:%SZ")
# Build JSON payload
local payload
payload=$(cat <<EOF
{
"tables": [$(echo "$tables" | sed 's/[^,]*/"&"/g')],
"start_time": "$start_time",
"end_time": "$end_time",
"environment_id": "$environment_id"
EOF
)
if [[ -n "$message" ]]; then
payload="$payload"$',\n "message": "'"$message"'"'
fi
payload="$payload"$'\n}'
info "Starting maintenance: tables=${tables}, env=${environment_id}, ${duration_hours}h"
info "Window: ${start_time}${end_time}"
local response
if response=$(api_call POST "/api/maintenance/start" "$payload"); then
local event_id
event_id=$(echo "$response" | jq -r '.maintenance_id' 2>/dev/null || echo "unknown")
local task_id
task_id=$(echo "$response" | jq -r '.task_id' 2>/dev/null || echo "unknown")
ok "Maintenance event created: ${BOLD}${event_id}${NC}"
info "Task: ${task_id}"
echo "$response" | jq . 2>/dev/null || echo "$response"
fi
}
cmd_end() {
local event_id="$1"
info "Ending maintenance event: ${event_id}"
local response
if response=$(api_call POST "/api/maintenance/${event_id}/end" ""); then
ok "Maintenance event end scheduled"
echo "$response" | jq . 2>/dev/null || echo "$response"
fi
}
cmd_end_all() {
local environment_id="${1:-}"
warn "This will remove banners from ALL affected dashboards!"
if [[ -n "$environment_id" ]]; then
info "Scope: environment=${environment_id}"
else
info "Scope: all environments (API key default)"
fi
# Prompt for confirmation in interactive mode
if [[ -t 0 ]]; then
echo -n "Continue? [y/N] "
read -r confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
info "Cancelled."
exit 0
fi
fi
local payload="{}"
if [[ -n "$environment_id" ]]; then
payload=$(cat <<EOF
{"environment_id": "$environment_id"}
EOF
)
fi
local response
if response=$(api_call POST "/api/maintenance/end-all" "$payload"); then
ok "End-all scheduled"
echo "$response" | jq . 2>/dev/null || echo "$response"
fi
}
# ── Main ───────────────────────────────────────────────────────────────────
check_auth
case "${1:-help}" in
start)
if [[ $# -lt 3 ]]; then
echo "Usage: $0 start <tables> [duration_hours] <environment> [message]"
echo ""
echo "Examples:"
echo " $0 start public.messages 4 ss-dev"
echo " $0 start public.messages,public.users 4 ss-prod \"Scheduled ETL\""
exit 1
fi
cmd_start "$2" "${3:-4}" "${4:-}" "${5:-}"
;;
end)
if [[ $# -lt 2 ]]; then
echo "Usage: $0 end <event-id>"
exit 1
fi
cmd_end "$2"
;;
end-all)
cmd_end_all "${2:-}"
;;
*)
cat <<EOF
ss-tools Maintenance CLI
Usage:
$0 start <tables> [hours=4] <environment> [message]
Start maintenance on tables (comma-separated)
$0 end <event-id>
End a specific maintenance event
$0 end-all [environment]
End ALL maintenance (use with caution)
Examples:
$0 start public.messages 4 ss-dev
$0 end m-abc123
$0 end-all ss-dev
Environment:
SS_TOOLS_URL ss-tools base URL (default: http://localhost:8000)
SS_TOOLS_API_KEY API key with required permissions (required)
EOF
exit 0
;;
esac