Sync all scripts from website downloads — 352 scripts total
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.
This commit is contained in:
@@ -0,0 +1,848 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
# Script Name: alertmanager-silence-manager.sh
|
||||
# Version: 1.0
|
||||
# Description: CLI tool for managing Prometheus Alertmanager silences.
|
||||
# Create, bulk-create, extend, expire, list, audit, and export
|
||||
# silences via the Alertmanager API v2. Supports dry-run mode,
|
||||
# pattern-based operations, and YAML bulk silence files.
|
||||
#
|
||||
# Author: Phil Connor
|
||||
# Contact: contact@mylinux.work
|
||||
# Website: https://mylinux.work
|
||||
# License: MIT
|
||||
#
|
||||
# Prerequisites:
|
||||
# - curl
|
||||
# - jq
|
||||
# - Alertmanager running and accessible
|
||||
#
|
||||
# Usage:
|
||||
# # Create a single silence
|
||||
# ./alertmanager-silence-manager.sh create --matcher 'alertname=HighCPU' --duration 2h --comment "Maintenance"
|
||||
#
|
||||
# # Bulk create from YAML
|
||||
# ./alertmanager-silence-manager.sh bulk-create --file maintenance.yaml
|
||||
#
|
||||
# # List active silences
|
||||
# ./alertmanager-silence-manager.sh list --state active
|
||||
#
|
||||
# # Extend a silence
|
||||
# ./alertmanager-silence-manager.sh extend --id abc12345 --duration 1h
|
||||
#
|
||||
# # Expire a silence
|
||||
# ./alertmanager-silence-manager.sh expire --id abc12345
|
||||
#
|
||||
# # Export active silences to YAML
|
||||
# ./alertmanager-silence-manager.sh export --output silences.yaml
|
||||
#
|
||||
# # Audit silences
|
||||
# ./alertmanager-silence-manager.sh audit
|
||||
#
|
||||
# Configuration:
|
||||
# ALERTMANAGER_URL Alertmanager base URL (default: http://localhost:9093)
|
||||
# SILENCE_AUTHOR Author name for silences (default: current user)
|
||||
# SILENCE_COMMENT_PREFIX Prefix for all silence comments (default: none)
|
||||
#
|
||||
################################################################################
|
||||
|
||||
# ============================================================================
|
||||
# CONFIGURATION VARIABLES
|
||||
# ============================================================================
|
||||
|
||||
AM_URL="${ALERTMANAGER_URL:-http://localhost:9093}"
|
||||
AUTHOR="${SILENCE_AUTHOR:-$(whoami)}"
|
||||
COMMENT_PREFIX="${SILENCE_COMMENT_PREFIX:-}"
|
||||
DRY_RUN=false
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
show_usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 <command> [OPTIONS]
|
||||
|
||||
Manage Prometheus Alertmanager silences via the API.
|
||||
|
||||
COMMANDS:
|
||||
create Create a single silence
|
||||
bulk-create Create silences from a YAML file
|
||||
list List silences in table format
|
||||
extend Extend a silence by duration
|
||||
expire Expire silences by ID or pattern
|
||||
export Export active silences to YAML
|
||||
audit Show detailed silence audit info
|
||||
|
||||
CREATE OPTIONS:
|
||||
--matcher STR Label matcher (e.g., 'alertname=HighCPU'), repeatable
|
||||
--duration STR Duration (e.g., 2h, 30m, 1d)
|
||||
--comment STR Silence comment/reason
|
||||
--dry-run Preview without creating
|
||||
|
||||
BULK-CREATE OPTIONS:
|
||||
--file PATH Path to YAML silence definitions
|
||||
--dry-run Preview without creating
|
||||
|
||||
LIST OPTIONS:
|
||||
--state STR Filter: active, pending, expired, all (default: active)
|
||||
|
||||
EXTEND OPTIONS:
|
||||
--id ID Silence ID to extend
|
||||
--duration STR Additional duration (e.g., 1h)
|
||||
--dry-run Preview without extending
|
||||
|
||||
EXPIRE OPTIONS:
|
||||
--id ID Silence ID to expire
|
||||
--match STR Pattern match on comment (e.g., 'comment=~maintenance.*')
|
||||
--dry-run Preview without expiring
|
||||
|
||||
EXPORT OPTIONS:
|
||||
--output PATH Output file (default: stdout)
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
ALERTMANAGER_URL Base URL (default: http://localhost:9093)
|
||||
SILENCE_AUTHOR Author name (default: current user)
|
||||
SILENCE_COMMENT_PREFIX Prefix for comments
|
||||
|
||||
EXAMPLES:
|
||||
$0 create --matcher 'alertname=HighCPU' --matcher 'instance=web-01' --duration 2h --comment "Maintenance"
|
||||
$0 bulk-create --file maintenance-window.yaml --dry-run
|
||||
$0 list --state active
|
||||
$0 extend --id 4a2f8c3e --duration 1h
|
||||
$0 expire --match 'comment=~maintenance.*'
|
||||
$0 export --output silences-backup.yaml
|
||||
$0 audit
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
log_dry_run() { echo -e "${YELLOW}[DRY RUN]${NC} $*"; }
|
||||
|
||||
check_requirements() {
|
||||
local missing=0
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
log_error "curl not found"
|
||||
missing=1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
log_error "jq not found"
|
||||
missing=1
|
||||
fi
|
||||
|
||||
return $missing
|
||||
}
|
||||
|
||||
check_connectivity() {
|
||||
local status
|
||||
status=$(curl -sf --connect-timeout 5 --max-time 10 -o /dev/null -w "%{http_code}" "${AM_URL}/api/v2/status" 2>/dev/null)
|
||||
|
||||
if [ "$status" != "200" ]; then
|
||||
log_error "Cannot reach Alertmanager at ${AM_URL} (HTTP $status)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Query Alertmanager API
|
||||
# Args: $1 - method, $2 - endpoint, $3 - data (optional)
|
||||
am_api() {
|
||||
local method="$1" endpoint="$2" data="$3"
|
||||
|
||||
if [ -n "$data" ]; then
|
||||
curl -sf --connect-timeout 5 --max-time 15 \
|
||||
-X "$method" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" \
|
||||
"${AM_URL}${endpoint}" 2>/dev/null
|
||||
else
|
||||
curl -sf --connect-timeout 5 --max-time 15 \
|
||||
-X "$method" \
|
||||
"${AM_URL}${endpoint}" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse duration string to seconds
|
||||
# Supports: 30s, 5m, 2h, 1d
|
||||
parse_duration() {
|
||||
local input="$1"
|
||||
local num unit
|
||||
|
||||
num=$(echo "$input" | sed 's/[^0-9]//g')
|
||||
unit=$(echo "$input" | sed 's/[0-9]//g')
|
||||
|
||||
case "$unit" in
|
||||
s) echo "$num" ;;
|
||||
m) echo $((num * 60)) ;;
|
||||
h) echo $((num * 3600)) ;;
|
||||
d) echo $((num * 86400)) ;;
|
||||
*) log_error "Invalid duration unit: $unit (use s/m/h/d)"; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Get ISO 8601 timestamp for now + offset seconds
|
||||
# Args: $1 - offset in seconds (default: 0)
|
||||
iso_timestamp() {
|
||||
local offset="${1:-0}"
|
||||
date -u -d "+${offset} seconds" "+%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null || \
|
||||
date -u -v "+${offset}S" "+%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null
|
||||
}
|
||||
|
||||
# Parse a matcher string like 'alertname=HighCPU' or 'instance=~web-0[1-3]'
|
||||
# Returns JSON object
|
||||
parse_matcher() {
|
||||
local input="$1"
|
||||
local name value is_regex is_equal
|
||||
|
||||
if [[ "$input" == *"=~"* ]]; then
|
||||
name="${input%%=~*}"
|
||||
value="${input#*=~}"
|
||||
is_regex="true"
|
||||
is_equal="true"
|
||||
elif [[ "$input" == *"!~"* ]]; then
|
||||
name="${input%%!~*}"
|
||||
value="${input#*!~}"
|
||||
is_regex="true"
|
||||
is_equal="false"
|
||||
elif [[ "$input" == *"!="* ]]; then
|
||||
name="${input%%!=*}"
|
||||
value="${input#*!=}"
|
||||
is_regex="false"
|
||||
is_equal="false"
|
||||
elif [[ "$input" == *"="* ]]; then
|
||||
name="${input%%=*}"
|
||||
value="${input#*=}"
|
||||
is_regex="false"
|
||||
is_equal="true"
|
||||
else
|
||||
log_error "Invalid matcher format: $input"
|
||||
return 1
|
||||
fi
|
||||
|
||||
jq -n --arg n "$name" --arg v "$value" \
|
||||
--argjson r "$is_regex" --argjson e "$is_equal" \
|
||||
'{name: $n, value: $v, isRegex: $r, isEqual: $e}'
|
||||
}
|
||||
|
||||
# Truncate string to max length
|
||||
truncate() {
|
||||
local str="$1" max="${2:-30}"
|
||||
if [ ${#str} -gt "$max" ]; then
|
||||
echo "${str:0:$((max-2))}.."
|
||||
else
|
||||
echo "$str"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CREATE COMMAND
|
||||
# ============================================================================
|
||||
|
||||
cmd_create() {
|
||||
local matchers_json="[]"
|
||||
local duration=""
|
||||
local comment=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--matcher)
|
||||
local m
|
||||
m=$(parse_matcher "$2") || exit 1
|
||||
matchers_json=$(echo "$matchers_json" | jq --argjson m "$m" '. + [$m]')
|
||||
shift 2 ;;
|
||||
--duration) duration="$2"; shift 2 ;;
|
||||
--comment) comment="$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
*) log_error "Unknown option for create: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$(echo "$matchers_json" | jq 'length')" -eq 0 ]; then
|
||||
log_error "At least one --matcher is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$duration" ]; then
|
||||
log_error "--duration is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$comment" ]; then
|
||||
log_error "--comment is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local duration_secs
|
||||
duration_secs=$(parse_duration "$duration") || exit 1
|
||||
|
||||
local starts_at ends_at
|
||||
starts_at=$(iso_timestamp 0)
|
||||
ends_at=$(iso_timestamp "$duration_secs")
|
||||
|
||||
local full_comment="${COMMENT_PREFIX}${comment}"
|
||||
|
||||
local payload
|
||||
payload=$(jq -n \
|
||||
--argjson matchers "$matchers_json" \
|
||||
--arg startsAt "$starts_at" \
|
||||
--arg endsAt "$ends_at" \
|
||||
--arg createdBy "$AUTHOR" \
|
||||
--arg comment "$full_comment" \
|
||||
'{matchers: $matchers, startsAt: $startsAt, endsAt: $endsAt, createdBy: $createdBy, comment: $comment}')
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_dry_run "Would create silence:"
|
||||
echo "$payload" | jq .
|
||||
return
|
||||
fi
|
||||
|
||||
local response
|
||||
response=$(am_api POST "/api/v2/silences" "$payload")
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$response" ]; then
|
||||
local sid
|
||||
sid=$(echo "$response" | jq -r '.silenceID // empty')
|
||||
if [ -n "$sid" ]; then
|
||||
log_info "Silence created: ${sid}"
|
||||
else
|
||||
log_info "Silence created"
|
||||
fi
|
||||
else
|
||||
log_error "Failed to create silence"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# BULK-CREATE COMMAND
|
||||
# ============================================================================
|
||||
|
||||
cmd_bulk_create() {
|
||||
local file=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--file) file="$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
*) log_error "Unknown option for bulk-create: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$file" ] || [ ! -f "$file" ]; then
|
||||
log_error "Valid --file is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local success=0 failed=0
|
||||
local in_silence=false
|
||||
local in_matchers=false
|
||||
local matchers_json="[]"
|
||||
local duration="" comment=""
|
||||
local current_matcher_name="" current_matcher_value="" current_matcher_regex="false"
|
||||
|
||||
flush_silence() {
|
||||
if [ -z "$duration" ] || [ "$(echo "$matchers_json" | jq 'length')" -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local duration_secs starts_at ends_at full_comment payload
|
||||
duration_secs=$(parse_duration "$duration") || { ((failed++)); return; }
|
||||
starts_at=$(iso_timestamp 0)
|
||||
ends_at=$(iso_timestamp "$duration_secs")
|
||||
full_comment="${COMMENT_PREFIX}${comment}"
|
||||
|
||||
payload=$(jq -n \
|
||||
--argjson matchers "$matchers_json" \
|
||||
--arg startsAt "$starts_at" \
|
||||
--arg endsAt "$ends_at" \
|
||||
--arg createdBy "$AUTHOR" \
|
||||
--arg comment "$full_comment" \
|
||||
'{matchers: $matchers, startsAt: $startsAt, endsAt: $endsAt, createdBy: $createdBy, comment: $comment}')
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_dry_run "Would create silence:"
|
||||
echo "$payload" | jq .
|
||||
echo ""
|
||||
((success++))
|
||||
return
|
||||
fi
|
||||
|
||||
local response
|
||||
response=$(am_api POST "/api/v2/silences" "$payload")
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$response" ]; then
|
||||
local sid
|
||||
sid=$(echo "$response" | jq -r '.silenceID // empty')
|
||||
log_info "Silence created: ${sid:-ok} (${comment})"
|
||||
((success++))
|
||||
else
|
||||
log_error "Failed to create silence: ${comment}"
|
||||
((failed++))
|
||||
fi
|
||||
}
|
||||
|
||||
flush_matcher() {
|
||||
if [ -n "$current_matcher_name" ]; then
|
||||
local m
|
||||
m=$(jq -n --arg n "$current_matcher_name" --arg v "$current_matcher_value" \
|
||||
--argjson r "$current_matcher_regex" \
|
||||
'{name: $n, value: $v, isRegex: $r, isEqual: true}')
|
||||
matchers_json=$(echo "$matchers_json" | jq --argjson m "$m" '. + [$m]')
|
||||
current_matcher_name=""
|
||||
current_matcher_value=""
|
||||
current_matcher_regex="false"
|
||||
fi
|
||||
}
|
||||
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
# Strip leading/trailing whitespace for comparison
|
||||
local trimmed
|
||||
trimmed=$(echo "$line" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
||||
|
||||
# Skip comments and empty lines
|
||||
[[ "$trimmed" =~ ^# ]] && continue
|
||||
[ -z "$trimmed" ] && continue
|
||||
|
||||
# New silence block
|
||||
if [[ "$trimmed" == "- matchers:" ]]; then
|
||||
flush_matcher
|
||||
if [ "$in_silence" = true ]; then
|
||||
flush_silence
|
||||
fi
|
||||
in_silence=true
|
||||
in_matchers=true
|
||||
matchers_json="[]"
|
||||
duration=""
|
||||
comment=""
|
||||
continue
|
||||
fi
|
||||
|
||||
# Matcher entry
|
||||
if [[ "$trimmed" == "- name:"* ]] && [ "$in_matchers" = true ]; then
|
||||
flush_matcher
|
||||
current_matcher_name=$(echo "$trimmed" | sed 's/^- name:[[:space:]]*//')
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$trimmed" == "value:"* ]] && [ "$in_matchers" = true ]; then
|
||||
current_matcher_value=$(echo "$trimmed" | sed 's/^value:[[:space:]]*//')
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$trimmed" == "isRegex:"* ]] && [ "$in_matchers" = true ]; then
|
||||
current_matcher_regex=$(echo "$trimmed" | sed 's/^isRegex:[[:space:]]*//')
|
||||
continue
|
||||
fi
|
||||
|
||||
# Duration
|
||||
if [[ "$trimmed" == "duration:"* ]]; then
|
||||
flush_matcher
|
||||
in_matchers=false
|
||||
duration=$(echo "$trimmed" | sed 's/^duration:[[:space:]]*//')
|
||||
continue
|
||||
fi
|
||||
|
||||
# Comment
|
||||
if [[ "$trimmed" == "comment:"* ]]; then
|
||||
comment=$(echo "$trimmed" | sed 's/^comment:[[:space:]]*//' | sed 's/^"//' | sed 's/"$//')
|
||||
continue
|
||||
fi
|
||||
|
||||
done < "$file"
|
||||
|
||||
# Flush last silence
|
||||
flush_matcher
|
||||
if [ "$in_silence" = true ]; then
|
||||
flush_silence
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "Bulk create complete: ${success} succeeded, ${failed} failed"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# LIST COMMAND
|
||||
# ============================================================================
|
||||
|
||||
cmd_list() {
|
||||
local state="active"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--state) state="$2"; shift 2 ;;
|
||||
*) log_error "Unknown option for list: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local silences_json
|
||||
silences_json=$(am_api GET "/api/v2/silences")
|
||||
|
||||
if [ -z "$silences_json" ]; then
|
||||
log_error "Failed to fetch silences"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Filter by state
|
||||
local filtered
|
||||
if [ "$state" = "all" ]; then
|
||||
filtered="$silences_json"
|
||||
else
|
||||
filtered=$(echo "$silences_json" | jq --arg s "$state" '[.[] | select(.status.state == $s)]')
|
||||
fi
|
||||
|
||||
local count
|
||||
count=$(echo "$filtered" | jq 'length')
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
log_info "No ${state} silences found"
|
||||
return
|
||||
fi
|
||||
|
||||
printf "${BLUE}%-10s %-10s %-12s %-30s %-20s %-20s %s${NC}\n" \
|
||||
"ID" "STATE" "AUTHOR" "MATCHERS" "STARTS" "ENDS" "COMMENT"
|
||||
printf '%.0s-' {1..120}
|
||||
echo ""
|
||||
|
||||
echo "$filtered" | jq -r '.[] | [
|
||||
.id[0:8],
|
||||
.status.state,
|
||||
.createdBy,
|
||||
([.matchers[] | "\(.name)=\(.value)"] | join(", ")),
|
||||
.startsAt[0:19],
|
||||
.endsAt[0:19],
|
||||
.comment
|
||||
] | @tsv' | while IFS=$'\t' read -r id st author matchers starts ends comment; do
|
||||
printf "%-10s %-10s %-12s %-30s %-20s %-20s %s\n" \
|
||||
"$id" "$st" "$(truncate "$author" 12)" "$(truncate "$matchers" 30)" \
|
||||
"$starts" "$ends" "$(truncate "$comment" 40)"
|
||||
done
|
||||
|
||||
echo ""
|
||||
log_info "${count} silence(s) found"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# EXTEND COMMAND
|
||||
# ============================================================================
|
||||
|
||||
cmd_extend() {
|
||||
local silence_id="" duration=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--id) silence_id="$2"; shift 2 ;;
|
||||
--duration) duration="$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
*) log_error "Unknown option for extend: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$silence_id" ]; then
|
||||
log_error "--id is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$duration" ]; then
|
||||
log_error "--duration is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local duration_secs
|
||||
duration_secs=$(parse_duration "$duration") || exit 1
|
||||
|
||||
# Find the silence (match by prefix)
|
||||
local silences_json silence
|
||||
silences_json=$(am_api GET "/api/v2/silences")
|
||||
silence=$(echo "$silences_json" | jq --arg id "$silence_id" '[.[] | select(.id | startswith($id)) | select(.status.state == "active")] | first // empty')
|
||||
|
||||
if [ -z "$silence" ] || [ "$silence" = "null" ]; then
|
||||
log_error "Active silence not found matching ID: ${silence_id}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local full_id
|
||||
full_id=$(echo "$silence" | jq -r '.id')
|
||||
|
||||
# Build new silence with extended endsAt
|
||||
local new_ends_at
|
||||
new_ends_at=$(iso_timestamp "$duration_secs")
|
||||
|
||||
local payload
|
||||
payload=$(echo "$silence" | jq --arg endsAt "$new_ends_at" \
|
||||
'del(.id, .status, .updatedAt) | .endsAt = $endsAt')
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_dry_run "Would extend silence ${full_id} to ${new_ends_at}:"
|
||||
echo "$payload" | jq .
|
||||
return
|
||||
fi
|
||||
|
||||
local response
|
||||
response=$(am_api POST "/api/v2/silences" "$payload")
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$response" ]; then
|
||||
local new_id
|
||||
new_id=$(echo "$response" | jq -r '.silenceID // empty')
|
||||
log_info "Silence extended: ${new_id:-ok} (new end: ${new_ends_at})"
|
||||
else
|
||||
log_error "Failed to extend silence"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# EXPIRE COMMAND
|
||||
# ============================================================================
|
||||
|
||||
cmd_expire() {
|
||||
local silence_id="" match_pattern=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--id) silence_id="$2"; shift 2 ;;
|
||||
--match) match_pattern="$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
*) log_error "Unknown option for expire: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$silence_id" ] && [ -z "$match_pattern" ]; then
|
||||
log_error "Either --id or --match is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local silences_json
|
||||
silences_json=$(am_api GET "/api/v2/silences")
|
||||
|
||||
if [ -z "$silences_json" ]; then
|
||||
log_error "Failed to fetch silences"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local ids_to_expire=()
|
||||
|
||||
if [ -n "$silence_id" ]; then
|
||||
# Match by ID prefix
|
||||
local matched
|
||||
matched=$(echo "$silences_json" | jq -r --arg id "$silence_id" \
|
||||
'.[] | select(.id | startswith($id)) | select(.status.state == "active") | .id')
|
||||
while IFS= read -r id; do
|
||||
[ -n "$id" ] && ids_to_expire+=("$id")
|
||||
done <<< "$matched"
|
||||
fi
|
||||
|
||||
if [ -n "$match_pattern" ]; then
|
||||
# Parse pattern: 'comment=~regex'
|
||||
local field pattern
|
||||
if [[ "$match_pattern" == *"=~"* ]]; then
|
||||
field="${match_pattern%%=~*}"
|
||||
pattern="${match_pattern#*=~}"
|
||||
else
|
||||
log_error "Match pattern must use =~ syntax (e.g., 'comment=~maintenance.*')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local matched
|
||||
if [ "$field" = "comment" ]; then
|
||||
matched=$(echo "$silences_json" | jq -r --arg p "$pattern" \
|
||||
'.[] | select(.status.state == "active") | select(.comment | test($p)) | .id')
|
||||
else
|
||||
matched=$(echo "$silences_json" | jq -r --arg f "$field" --arg p "$pattern" \
|
||||
'.[] | select(.status.state == "active") | select(.matchers[] | select(.name == $f) | .value | test($p)) | .id')
|
||||
fi
|
||||
|
||||
while IFS= read -r id; do
|
||||
[ -n "$id" ] && ids_to_expire+=("$id")
|
||||
done <<< "$matched"
|
||||
fi
|
||||
|
||||
if [ ${#ids_to_expire[@]} -eq 0 ]; then
|
||||
log_warn "No matching active silences found"
|
||||
return
|
||||
fi
|
||||
|
||||
local success=0 failed=0
|
||||
|
||||
for id in "${ids_to_expire[@]}"; do
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_dry_run "Would expire silence: ${id}"
|
||||
((success++))
|
||||
continue
|
||||
fi
|
||||
|
||||
if curl -sf --connect-timeout 5 --max-time 10 \
|
||||
-X DELETE "${AM_URL}/api/v2/silence/${id}" >/dev/null 2>&1; then
|
||||
log_info "Expired silence: ${id}"
|
||||
((success++))
|
||||
else
|
||||
log_error "Failed to expire silence: ${id}"
|
||||
((failed++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
log_info "Expire complete: ${success} succeeded, ${failed} failed"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# EXPORT COMMAND
|
||||
# ============================================================================
|
||||
|
||||
cmd_export() {
|
||||
local output=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--output) output="$2"; shift 2 ;;
|
||||
*) log_error "Unknown option for export: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local silences_json
|
||||
silences_json=$(am_api GET "/api/v2/silences")
|
||||
|
||||
if [ -z "$silences_json" ]; then
|
||||
log_error "Failed to fetch silences"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local active
|
||||
active=$(echo "$silences_json" | jq '[.[] | select(.status.state == "active")]')
|
||||
local count
|
||||
count=$(echo "$active" | jq 'length')
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
log_warn "No active silences to export"
|
||||
return
|
||||
fi
|
||||
|
||||
local yaml_output
|
||||
yaml_output=$(echo "$active" | jq -r '
|
||||
"silences:",
|
||||
(.[] |
|
||||
" - matchers:",
|
||||
(.matchers[] |
|
||||
" - name: \(.name)",
|
||||
" value: \(.value)",
|
||||
" isRegex: \(.isRegex)"
|
||||
),
|
||||
" duration: \(
|
||||
((.endsAt | fromdateiso8601) - (.startsAt | fromdateiso8601)) |
|
||||
if . >= 86400 then "\(. / 86400 | floor)d"
|
||||
elif . >= 3600 then "\(. / 3600 | floor)h"
|
||||
elif . >= 60 then "\(. / 60 | floor)m"
|
||||
else "\(.)s"
|
||||
end
|
||||
)",
|
||||
" comment: \"\(.comment)\""
|
||||
)
|
||||
')
|
||||
|
||||
if [ -n "$output" ]; then
|
||||
echo "$yaml_output" > "$output"
|
||||
log_info "Exported ${count} silence(s) to ${output}"
|
||||
else
|
||||
echo "$yaml_output"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# AUDIT COMMAND
|
||||
# ============================================================================
|
||||
|
||||
cmd_audit() {
|
||||
local silences_json
|
||||
silences_json=$(am_api GET "/api/v2/silences")
|
||||
|
||||
if [ -z "$silences_json" ]; then
|
||||
log_error "Failed to fetch silences"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local active
|
||||
active=$(echo "$silences_json" | jq '[.[] | select(.status.state == "active")]')
|
||||
local count
|
||||
count=$(echo "$active" | jq 'length')
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
log_info "No active silences"
|
||||
return
|
||||
fi
|
||||
|
||||
printf "${BLUE}%-10s %-12s %-20s %-20s %-10s %-28s %s${NC}\n" \
|
||||
"ID" "AUTHOR" "CREATED" "EXPIRES" "DURATION" "MATCHERS" "COMMENT"
|
||||
printf '%.0s-' {1..130}
|
||||
echo ""
|
||||
|
||||
echo "$active" | jq -r '.[] | [
|
||||
.id[0:8],
|
||||
.createdBy,
|
||||
.startsAt[0:19],
|
||||
.endsAt[0:19],
|
||||
(
|
||||
((.endsAt | fromdateiso8601) - (.startsAt | fromdateiso8601)) |
|
||||
if . >= 86400 then "\(. / 86400 | floor)d"
|
||||
elif . >= 3600 then "\(. / 3600 | floor)h"
|
||||
elif . >= 60 then "\(. / 60 | floor)m"
|
||||
else "\(.)s"
|
||||
end
|
||||
),
|
||||
([.matchers[] | "\(.name)=\(.value)"] | join(", ")),
|
||||
.comment
|
||||
] | @tsv' | while IFS=$'\t' read -r id author created expires duration matchers comment; do
|
||||
printf "%-10s %-12s %-20s %-20s %-10s %-28s %s\n" \
|
||||
"$id" "$(truncate "$author" 12)" "$created" "$expires" "$duration" \
|
||||
"$(truncate "$matchers" 28)" "$(truncate "$comment" 40)"
|
||||
done
|
||||
|
||||
echo ""
|
||||
log_info "${count} active silence(s)"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# MAIN
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
if [ $# -eq 0 ]; then
|
||||
show_usage
|
||||
fi
|
||||
|
||||
check_requirements || exit 1
|
||||
|
||||
local command="$1"
|
||||
shift
|
||||
|
||||
case "$command" in
|
||||
-h|--help) show_usage ;;
|
||||
create|bulk-create|list|extend|expire|export|audit) ;;
|
||||
*) log_error "Unknown command: $command"; echo ""; show_usage ;;
|
||||
esac
|
||||
|
||||
# All commands except --help require connectivity
|
||||
check_connectivity
|
||||
|
||||
case "$command" in
|
||||
create) cmd_create "$@" ;;
|
||||
bulk-create) cmd_bulk_create "$@" ;;
|
||||
list) cmd_list "$@" ;;
|
||||
extend) cmd_extend "$@" ;;
|
||||
expire) cmd_expire "$@" ;;
|
||||
export) cmd_export "$@" ;;
|
||||
audit) cmd_audit "$@" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user