#!/bin/bash # ============================================================================== # 🛠️ BASE UTILITY FUNCTIONS (iklim.co) # ============================================================================== # Tüm ortamlar (Dev, Test, Prod) tarafından ortak kullanılan çekirdek fonksiyonlar. # Bu dosya doğrudan çalıştırılmaz, ortam scriptleri tarafından 'source' edilir. # Belirtilen env dosyasını sisteme yükler (export eder). source_env_file() { local path="$1" if [ -f "$path" ]; then set -o allexport source "$path" set +o allexport fi } # Klasördeki tüm .env.secrets.* dosyalarını otomatik bulur ve yükler. # (.example ve .shared dosyalarını atlar, onları ana akış yönetir). source_service_secret_files() { local file for file in .env.secrets.*; do [ -f "$file" ] || continue [[ "$file" == *.example ]] && continue [ "$file" = ".env.secrets.shared" ] && continue source_env_file "$file" done } # Kritik bir env dosyasının varlığını kontrol eder, yoksa scripti durdurur. require_env_file() { local path="$1" local description="$2" if [ ! -f "$path" ]; then log_message "ERROR" "$description not found at $path" exit 1 fi } # Env dosyalarındaki değişken isimlerini tarayıp 'envsubst' için liste oluşturur. # Template dosyalarını doldururken hangi değişkenlerin çözüleceğini belirler. envsubst_vars_from_files() { local file for file in "$ENV_PATH" "$ENV_SECRETS_SHARED_PATH" .env.secrets.*; do [ -f "$file" ] || continue [[ "$file" == *.example ]] && continue grep -E '^[A-Za-z_][A-Za-z_0-9]*=' "$file" | cut -d= -f1 done | sort -u | sed 's/^/\$/' | tr '\n' ' ' } # Belirtilen bir değişkenin değerini hiyerarşik olarak (env -> shared -> secrets) arar. lookup_env_value() { local name="$1" local file local value="" for file in "$ENV_PATH" "$ENV_SECRETS_SHARED_PATH" .env.secrets.*; do [ -f "$file" ] || continue [[ "$file" == *.example ]] && continue if grep -q "^${name}=" "$file"; then value="$(grep "^${name}=" "$file" | tail -n1 | cut -d '=' -f2-)" fi done printf '%s' "$value" } # Matematiksel veya mantıksal işlem gerektiren env değerlerini hesaplar. refresh_calculated_env_vars() { : } # Tüm çevre dosyalarını (ana env, ortak sırlar ve servis sırları) tazeleyerek yükler. refresh_env_vars() { source_env_file "$ENV_PATH" source_env_file "$ENV_SECRETS_SHARED_PATH" source_service_secret_files refresh_calculated_env_vars log_message "INFO" "Environment variables refreshed from all .env and .env.secrets.* files 🔄" } # Bir değişkeni hem shell'e export eder hem de (Dev ortamında) terminale bilgi basar. export_variable() { local name="$1" local value if [ $# -ge 2 ]; then value="$2" else log_message "DEBUG" "Looking for ${name} value in env files" value="$(lookup_env_value "$name")" fi export "${name}=${value}" if [[ "$ENVIRONMENT" == "dev" ]]; then log_message "DEBUG" "Env variable ${name} is set to: ${value:0:5}... 🌎" fi } # --- 🔐 Ortak Vault Yardımcıları --- # Vault kilidini açar (Unseal). Dev, Test ve tek-node kurulumlar için VIP path kullanır. # Prod HA Raft cluster için common-functions-prod.sh bu fonksiyonu override eder. unseal_vault() { local vault_addr=$1 local _curl="curl -s" [[ "${VAULT_SKIP_VERIFY:-false}" == "true" ]] && _curl="curl -sk" local RESPONSE_JSON ERROR_MSG sealed RESPONSE_JSON=$($_curl $vault_addr/v1/sys/health) ERROR_MSG=$(echo "$RESPONSE_JSON" | jq -r '.errors[]?') if [[ -n "$ERROR_MSG" ]]; then log_message "ERROR" "$ERROR_MSG" exit 1 fi sealed=$(echo $RESPONSE_JSON | jq .sealed) if [ "$sealed" = "true" ]; then log_message "INFO" "🔓🗝️ Unsealing Vault ($vault_addr)..." RESPONSE_JSON=$($_curl --request PUT -H "Content-Type: application/json" \ --data "{\"key\": \"$VAULT_UNSEAL_KEY\"}" $vault_addr/v1/sys/unseal) ERROR_MSG=$(echo "$RESPONSE_JSON" | jq -r '.errors[]?') if [[ -n "$ERROR_MSG" ]]; then log_message "ERROR" "$ERROR_MSG" exit 1 fi log_message "SUCCESS" "Vault unsealed successfully" else log_message "INFO" "Vault is already unsealed" fi } # --- 📝 Ortak Log Yardımcıları --- # Log seviyesini numerik değere çevirir _get_log_level_num() { case "${1^^}" in TRACE) echo 1 ;; DEBUG) echo 2 ;; INFO) echo 3 ;; SUCCESS) echo 4 ;; WARN) echo 5 ;; ERROR) echo 6 ;; FATAL) echo 7 ;; NONE) echo 99 ;; *) echo 3 ;; # Default to INFO esac } # Log seviyesine uygun emojiyi döndürür _get_log_level_emoji() { case "${1^^}" in TRACE) echo "🔎" ;; DEBUG) echo "🪲" ;; INFO) echo "ℹ️" ;; SUCCESS) echo "✅" ;; WARN) echo "⚠️" ;; ERROR) echo "❌" ;; FATAL) echo "☠️" ;; *) echo "🤔" ;; esac } # Timestamp'li log fonksiyonu. GLOBAL_LOG_LEVEL'a göre filtreler. # Kullanım: log_message "INFO" "Bu bir log mesajıdır" log_message() { local level="${1^^}" local message="$2" local current_level_num=$(_get_log_level_num "$level") local global_level_str="${GLOBAL_LOG_LEVEL:-INFO}" local global_level_num=$(_get_log_level_num "$global_level_str") local emoji=$(_get_log_level_emoji "$level") local env_name="${SPRING_PROFILES_ACTIVE:-UNKNOWN}" env_name="${env_name^^}" if [ "$current_level_num" -ge "$global_level_num" ]; then #local timestamp=$(TZ="Europe/Istanbul" date +"%Y-%m-%dT%H:%M:%S%:z") local timestamp=$(TZ="Europe/Istanbul" date +"%H:%M:%S") if [ "$current_level_num" -ge 5 ]; then # ERROR ve FATAL hata akışına (stderr) basılır echo "[$timestamp] [$env_name] $emoji [$level] $message" >&2 else echo "[$timestamp] [$env_name] $emoji [$level] $message" fi fi } # Kayıtlı tüm log dosyalarının son satırlarını basar ve dosyaları temizler. log_tail() { for logfile in "${LOG_FILES[@]}"; do tail -n 5 "$logfile" rm -f "$logfile" done } # Bir log dosyası oluşana kadar bekler ve başından (veya bir pattern'den) kesit basar. log_head() { local logfile=$1 local pattern=$2 local lines=5 while [ ! -f "$logfile" ] || [ "$(wc -l < "$logfile")" -lt $lines ]; do sleep 0.5 done if [ -z "$pattern" ]; then head -n $lines "$logfile" else local start_line start_line=$(grep -nm1 "$pattern" "$logfile" | cut -d: -f1) if [ -z "$start_line" ]; then head -n $lines "$logfile" else sed -n "${start_line},$((start_line + lines - 1))p" "$logfile" fi fi } # --- 🐳 Ortak Swarm Yardımcıları --- # Docker Swarm üzerindeki bir servisi rolling-restart (güncelleme) yöntemiyle tazeler. swarm_service_update() { local stack="$1" local service="$2" local image="$3" local full_name="${stack}_${service}" if docker service inspect "$full_name" >/dev/null 2>&1; then log_message "INFO" "🔄 Updating $full_name → $image" docker service update \ --image "$image" \ --with-registry-auth \ --update-order start-first \ --update-failure-action rollback \ "$full_name" log_message "SUCCESS" "$full_name updated" else log_message "ERROR" "Service $full_name does not exist. Manual stack deploy required." return 1 fi }