einige Ergänzungen
This commit is contained in:
Executable
+462
@@ -0,0 +1,462 @@
|
||||
#!/bin/bash
|
||||
|
||||
# =============================================================================
|
||||
# Common Utilities Library for Meldestelle Shell Scripts
|
||||
# =============================================================================
|
||||
# This library provides common functions for logging, error handling, cleanup,
|
||||
# and other utilities used across all shell scripts in the project.
|
||||
#
|
||||
# Usage: source "$(dirname "$0")/utils/common.sh" || source "scripts/utils/common.sh"
|
||||
# =============================================================================
|
||||
|
||||
# Prevent multiple sourcing
|
||||
if [[ "${COMMON_UTILS_LOADED:-}" == "true" ]]; then
|
||||
return 0
|
||||
fi
|
||||
COMMON_UTILS_LOADED=true
|
||||
|
||||
# =============================================================================
|
||||
# Configuration and Constants
|
||||
# =============================================================================
|
||||
|
||||
# Colors for output
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly BLUE='\033[0;34m'
|
||||
readonly PURPLE='\033[0;35m'
|
||||
readonly CYAN='\033[0;36m'
|
||||
readonly WHITE='\033[1;37m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Symbols
|
||||
readonly CHECK_MARK="✓"
|
||||
readonly CROSS_MARK="✗"
|
||||
readonly WARNING_MARK="⚠"
|
||||
readonly INFO_MARK="ℹ"
|
||||
readonly ARROW_MARK="→"
|
||||
|
||||
# Global counters
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
CHECKS=0
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
# =============================================================================
|
||||
# Error Handling and Cleanup
|
||||
# =============================================================================
|
||||
|
||||
# Enhanced error handling
|
||||
set -euo pipefail
|
||||
|
||||
# Error trap function
|
||||
error_trap() {
|
||||
local exit_code=$?
|
||||
local line_number=$1
|
||||
log_error "Script failed at line $line_number with exit code $exit_code"
|
||||
cleanup_on_exit
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
# Set error trap
|
||||
trap 'error_trap $LINENO' ERR
|
||||
|
||||
# Cleanup function (can be overridden by scripts)
|
||||
cleanup_on_exit() {
|
||||
if declare -f cleanup > /dev/null; then
|
||||
log_info "Running cleanup..."
|
||||
cleanup
|
||||
fi
|
||||
}
|
||||
|
||||
# Set exit trap
|
||||
trap cleanup_on_exit EXIT
|
||||
|
||||
# =============================================================================
|
||||
# Logging Functions
|
||||
# =============================================================================
|
||||
|
||||
# Get timestamp
|
||||
get_timestamp() {
|
||||
date '+%Y-%m-%d %H:%M:%S'
|
||||
}
|
||||
|
||||
# Base logging function
|
||||
log_base() {
|
||||
local level=$1
|
||||
local color=$2
|
||||
local symbol=$3
|
||||
local message=$4
|
||||
local timestamp=$(get_timestamp)
|
||||
|
||||
echo -e "${color}[${timestamp}] ${symbol} [${level}]${NC} ${message}" >&2
|
||||
}
|
||||
|
||||
# Info logging
|
||||
log_info() {
|
||||
log_base "INFO" "$BLUE" "$INFO_MARK" "$1"
|
||||
}
|
||||
|
||||
# Success logging
|
||||
log_success() {
|
||||
log_base "SUCCESS" "$GREEN" "$CHECK_MARK" "$1"
|
||||
}
|
||||
|
||||
# Warning logging
|
||||
log_warning() {
|
||||
log_base "WARNING" "$YELLOW" "$WARNING_MARK" "$1"
|
||||
((WARNINGS++))
|
||||
}
|
||||
|
||||
# Error logging
|
||||
log_error() {
|
||||
log_base "ERROR" "$RED" "$CROSS_MARK" "$1"
|
||||
((ERRORS++))
|
||||
}
|
||||
|
||||
# Debug logging (only if DEBUG=true)
|
||||
log_debug() {
|
||||
if [[ "${DEBUG:-false}" == "true" ]]; then
|
||||
log_base "DEBUG" "$PURPLE" "🐛" "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Progress logging
|
||||
log_progress() {
|
||||
log_base "PROGRESS" "$CYAN" "$ARROW_MARK" "$1"
|
||||
}
|
||||
|
||||
# Section header
|
||||
log_section() {
|
||||
local title=$1
|
||||
local line=$(printf '=%.0s' {1..80})
|
||||
echo -e "\n${BLUE}${line}${NC}"
|
||||
echo -e "${BLUE}${title}${NC}"
|
||||
echo -e "${BLUE}${line}${NC}\n"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Status and Validation Functions
|
||||
# =============================================================================
|
||||
|
||||
# Print status with counter increment
|
||||
print_status() {
|
||||
local status=$1
|
||||
local message=$2
|
||||
((CHECKS++))
|
||||
|
||||
case $status in
|
||||
"OK"|"SUCCESS")
|
||||
log_success "$message"
|
||||
;;
|
||||
"WARNING"|"WARN")
|
||||
log_warning "$message"
|
||||
;;
|
||||
"ERROR"|"FAIL")
|
||||
log_error "$message"
|
||||
;;
|
||||
"INFO")
|
||||
log_info "$message"
|
||||
;;
|
||||
*)
|
||||
log_info "$message"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check if file exists with logging
|
||||
check_file() {
|
||||
local file=$1
|
||||
local description=${2:-"File"}
|
||||
|
||||
if [[ -f "$file" ]]; then
|
||||
print_status "OK" "$description exists: $file"
|
||||
return 0
|
||||
else
|
||||
print_status "ERROR" "$description not found: $file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if directory exists with logging
|
||||
check_directory() {
|
||||
local dir=$1
|
||||
local description=${2:-"Directory"}
|
||||
|
||||
if [[ -d "$dir" ]]; then
|
||||
print_status "OK" "$description exists: $dir"
|
||||
return 0
|
||||
else
|
||||
print_status "ERROR" "$description not found: $dir"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if service is running on port
|
||||
check_service_port() {
|
||||
local port=$1
|
||||
local service_name=${2:-"Service"}
|
||||
local timeout=${3:-30}
|
||||
|
||||
log_info "Checking if $service_name is running on port $port..."
|
||||
|
||||
if timeout "$timeout" bash -c "until nc -z localhost $port; do sleep 1; done" 2>/dev/null; then
|
||||
print_status "OK" "$service_name is running on port $port"
|
||||
return 0
|
||||
else
|
||||
print_status "ERROR" "$service_name is not running on port $port (timeout: ${timeout}s)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check HTTP endpoint with retry
|
||||
check_http_endpoint() {
|
||||
local url=$1
|
||||
local service_name=${2:-"Service"}
|
||||
local timeout=${3:-30}
|
||||
local retry_count=${4:-3}
|
||||
|
||||
log_info "Checking HTTP endpoint: $url"
|
||||
|
||||
for ((i=1; i<=retry_count; i++)); do
|
||||
if timeout "$timeout" curl -sf "$url" >/dev/null 2>&1; then
|
||||
print_status "OK" "$service_name endpoint is healthy: $url"
|
||||
return 0
|
||||
else
|
||||
if [[ $i -lt $retry_count ]]; then
|
||||
log_warning "Attempt $i/$retry_count failed, retrying in 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
print_status "ERROR" "$service_name endpoint is not healthy: $url (after $retry_count attempts)"
|
||||
return 1
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Utility Functions
|
||||
# =============================================================================
|
||||
|
||||
# Wait for service with timeout
|
||||
wait_for_service() {
|
||||
local check_command=$1
|
||||
local service_name=$2
|
||||
local timeout=${3:-60}
|
||||
local interval=${4:-5}
|
||||
|
||||
log_info "Waiting for $service_name to be ready (timeout: ${timeout}s)..."
|
||||
|
||||
local elapsed=0
|
||||
while [[ $elapsed -lt $timeout ]]; do
|
||||
if eval "$check_command" >/dev/null 2>&1; then
|
||||
log_success "$service_name is ready"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep "$interval"
|
||||
elapsed=$((elapsed + interval))
|
||||
log_progress "Waiting for $service_name... (${elapsed}s/${timeout}s)"
|
||||
done
|
||||
|
||||
log_error "$service_name failed to become ready within ${timeout}s"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Create directory with logging
|
||||
create_directory() {
|
||||
local dir=$1
|
||||
local description=${2:-"Directory"}
|
||||
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
if mkdir -p "$dir"; then
|
||||
log_success "$description created: $dir"
|
||||
else
|
||||
log_error "Failed to create $description: $dir"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_info "$description already exists: $dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# Backup file with timestamp
|
||||
backup_file() {
|
||||
local file=$1
|
||||
local backup_dir=${2:-"./backups"}
|
||||
|
||||
if [[ -f "$file" ]]; then
|
||||
create_directory "$backup_dir" "Backup directory"
|
||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local backup_file="$backup_dir/$(basename "$file").backup.$timestamp"
|
||||
|
||||
if cp "$file" "$backup_file"; then
|
||||
log_success "File backed up: $file → $backup_file"
|
||||
echo "$backup_file"
|
||||
else
|
||||
log_error "Failed to backup file: $file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_warning "File not found for backup: $file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Run command with timeout and logging
|
||||
run_with_timeout() {
|
||||
local timeout_duration=$1
|
||||
local description=$2
|
||||
shift 2
|
||||
local command=("$@")
|
||||
|
||||
log_info "Running: $description"
|
||||
log_debug "Command: ${command[*]}"
|
||||
|
||||
if timeout "$timeout_duration" "${command[@]}"; then
|
||||
log_success "$description completed successfully"
|
||||
return 0
|
||||
else
|
||||
local exit_code=$?
|
||||
if [[ $exit_code -eq 124 ]]; then
|
||||
log_error "$description timed out after ${timeout_duration}s"
|
||||
else
|
||||
log_error "$description failed with exit code $exit_code"
|
||||
fi
|
||||
return $exit_code
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Summary and Reporting Functions
|
||||
# =============================================================================
|
||||
|
||||
# Print execution summary
|
||||
print_summary() {
|
||||
local script_name=${1:-"Script"}
|
||||
local end_time=$(date +%s)
|
||||
local duration=$((end_time - START_TIME))
|
||||
|
||||
log_section "Execution Summary"
|
||||
|
||||
echo -e "Script: ${WHITE}$script_name${NC}"
|
||||
echo -e "Duration: ${WHITE}${duration}s${NC}"
|
||||
echo -e "Total checks: ${WHITE}$CHECKS${NC}"
|
||||
echo -e "${GREEN}Successful: $((CHECKS - ERRORS - WARNINGS))${NC}"
|
||||
echo -e "${YELLOW}Warnings: $WARNINGS${NC}"
|
||||
echo -e "${RED}Errors: $ERRORS${NC}"
|
||||
echo
|
||||
|
||||
if [[ $ERRORS -eq 0 ]]; then
|
||||
if [[ $WARNINGS -eq 0 ]]; then
|
||||
log_success "All checks passed! $script_name completed successfully."
|
||||
return 0
|
||||
else
|
||||
log_warning "$script_name completed with warnings. Please review the warnings above."
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
log_error "$script_name failed with $ERRORS errors. Please fix the errors above."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Environment and Configuration
|
||||
# =============================================================================
|
||||
|
||||
# Load environment file if it exists
|
||||
load_env_file() {
|
||||
local env_file=${1:-.env}
|
||||
|
||||
if [[ -f "$env_file" ]]; then
|
||||
log_info "Loading environment from: $env_file"
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
source "$env_file"
|
||||
set +a
|
||||
log_success "Environment loaded successfully"
|
||||
else
|
||||
log_warning "Environment file not found: $env_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate required environment variables
|
||||
validate_env_vars() {
|
||||
local vars=("$@")
|
||||
local missing_vars=()
|
||||
|
||||
for var in "${vars[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
missing_vars+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_vars[@]} -gt 0 ]]; then
|
||||
log_error "Missing required environment variables: ${missing_vars[*]}"
|
||||
return 1
|
||||
else
|
||||
log_success "All required environment variables are set"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Docker and Service Management
|
||||
# =============================================================================
|
||||
|
||||
# Check if Docker is running
|
||||
check_docker() {
|
||||
if command_exists docker && docker info >/dev/null 2>&1; then
|
||||
print_status "OK" "Docker is running"
|
||||
return 0
|
||||
else
|
||||
print_status "ERROR" "Docker is not running or not accessible"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if docker-compose is available
|
||||
check_docker_compose() {
|
||||
if command_exists docker-compose; then
|
||||
print_status "OK" "docker-compose is available"
|
||||
return 0
|
||||
elif docker compose version >/dev/null 2>&1; then
|
||||
print_status "OK" "docker compose (plugin) is available"
|
||||
return 0
|
||||
else
|
||||
print_status "ERROR" "Neither docker-compose nor docker compose is available"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Start Docker services with health check wait
|
||||
start_docker_services() {
|
||||
local services=("$@")
|
||||
local compose_file=${COMPOSE_FILE:-docker-compose.yml}
|
||||
|
||||
log_info "Starting Docker services: ${services[*]}"
|
||||
|
||||
if docker-compose -f "$compose_file" up -d "${services[@]}"; then
|
||||
log_success "Docker services started"
|
||||
|
||||
# Wait for services to be healthy
|
||||
for service in "${services[@]}"; do
|
||||
wait_for_service "docker-compose -f $compose_file ps $service | grep -q 'healthy\\|Up'" "$service" 120 10
|
||||
done
|
||||
else
|
||||
log_error "Failed to start Docker services"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Initialization
|
||||
# =============================================================================
|
||||
|
||||
log_debug "Common utilities library loaded successfully"
|
||||
Reference in New Issue
Block a user