feat(workflow): add common-functions-base.sh and replace echo with log_message

This commit is contained in:
Murat ÖZDEMİR 2026-06-26 13:59:18 +03:00
parent 28d726d2d8
commit 6fc9ff45aa
3 changed files with 264 additions and 10 deletions

View File

@ -68,13 +68,15 @@ jobs:
- name: Wait for Loki
run: |
source ./common-functions-base.sh
export SPRING_PROFILES_ACTIVE=PROD
for i in $(seq 1 36); do
REPLICAS=$(docker service ls --filter name=iklimco-monitoring_loki --format "{{.Replicas}}" | head -1)
if echo "$REPLICAS" | awk -F'[/ ]' '$1>0 && $1==$2{found=1} END{exit !found}'; then
echo "Loki is ready: $REPLICAS"
log_message "SUCCESS" "Loki is ready: $REPLICAS"
exit 0
fi
echo "Loki not ready yet (${REPLICAS:-missing}), waiting 5s..."
log_message "INFO" "Loki not ready yet (${REPLICAS:-missing}), waiting 5s..."
sleep 5
done
docker service ps iklimco-monitoring_loki || true
@ -82,6 +84,8 @@ jobs:
- name: Configure SWAG Reverse Proxy
run: |
source ./common-functions-base.sh
export SPRING_PROFILES_ACTIVE=PROD
set -a; . ./.env; . ./.env.secrets.swag; set +a
export PORTAINER_SUBDOMAIN="${PORTAINER_SUBDOMAIN:-portainer.iklim.co}"
export RESTRICTED_IPS_BLOCK="$(echo "$RESTRICTED_IPS" | tr ',' '\n' | sed 's|.*| allow &;|')"
@ -92,16 +96,19 @@ jobs:
envsubst "$SWAG_VARS" < "$tpl" | docker run --rm -i \
-v "${SWAG_SITE_CONFS_DIR}:/output" \
alpine sh -c "cat > /output/${fname}"
echo "${fname} written"
log_message "SUCCESS" "${fname} written"
done
SWAG_CTR=$(docker ps -q -f name=iklimco_swag 2>/dev/null | head -1)
if [ -n "$SWAG_CTR" ]; then
docker exec "$SWAG_CTR" nginx -t && docker exec "$SWAG_CTR" nginx -s reload
log_message "SUCCESS" "SWAG nginx reloaded"
fi
- name: Update DNS Records
run: |
source ./common-functions-base.sh
export SPRING_PROFILES_ACTIVE=PROD
set -a; . ./.env; . ./.env.secrets.swag; set +a
FLOATING_IP="${{ vars.PROD_FLOATING_IP }}"
DOMAIN="iklim.co"
@ -113,14 +120,14 @@ jobs:
2>/dev/null | jq -r '.[0].data // empty' 2>/dev/null || true)
if [ "$CURRENT" = "$FLOATING_IP" ]; then
echo "${record}.${DOMAIN} -> ${FLOATING_IP} exists, skipping"
log_message "INFO" "${record}.${DOMAIN} -> ${FLOATING_IP} exists, skipping"
else
curl -sf -X PUT \
-H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \
-H "Content-Type: application/json" \
"https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${record}" \
-d "[{\"data\":\"${FLOATING_IP}\",\"ttl\":600}]"
echo "${record}.${DOMAIN} -> ${FLOATING_IP} added/updated"
log_message "SUCCESS" "${record}.${DOMAIN} -> ${FLOATING_IP} added/updated"
fi
done

View File

@ -61,13 +61,15 @@ jobs:
- name: Wait for Loki
run: |
source ./common-functions-base.sh
export SPRING_PROFILES_ACTIVE=TEST
for i in $(seq 1 36); do
REPLICAS=$(docker service ls --filter name=iklimco-monitoring_loki --format "{{.Replicas}}" | head -1)
if echo "$REPLICAS" | awk -F'[/ ]' '$1>0 && $1==$2{found=1} END{exit !found}'; then
echo "Loki is ready: $REPLICAS"
log_message "SUCCESS" "Loki is ready: $REPLICAS"
exit 0
fi
echo "Loki not ready yet (${REPLICAS:-missing}), waiting 5s..."
log_message "INFO" "Loki not ready yet (${REPLICAS:-missing}), waiting 5s..."
sleep 5
done
docker service ps iklimco-monitoring_loki || true
@ -75,6 +77,8 @@ jobs:
- name: Configure SWAG Reverse Proxy
run: |
source ./common-functions-base.sh
export SPRING_PROFILES_ACTIVE=TEST
set -a; . ./.env; . ./.env.secrets.swag; set +a
export PORTAINER_SUBDOMAIN="${PORTAINER_SUBDOMAIN:-portainer-test.iklim.co}"
export RESTRICTED_IPS_BLOCK="$(echo "$RESTRICTED_IPS" | tr ',' '\n' | sed 's|.*| allow &;|')"
@ -85,16 +89,19 @@ jobs:
envsubst "$SWAG_VARS" < "$tpl" | docker run --rm -i \
-v "${SWAG_SITE_CONFS_DIR}:/output" \
alpine sh -c "cat > /output/${fname}"
echo "${fname} written"
log_message "SUCCESS" "${fname} written"
done
SWAG_CTR=$(docker ps -q -f name=iklimco_swag 2>/dev/null | head -1)
if [ -n "$SWAG_CTR" ]; then
docker exec "$SWAG_CTR" nginx -t && docker exec "$SWAG_CTR" nginx -s reload
log_message "SUCCESS" "SWAG nginx reloaded"
fi
- name: Update DNS Records
run: |
source ./common-functions-base.sh
export SPRING_PROFILES_ACTIVE=TEST
set -a; . ./.env; . ./.env.secrets.swag; set +a
FLOATING_IP="${{ vars.TEST_FLOATING_IP }}"
DOMAIN="iklim.co"
@ -106,14 +113,14 @@ jobs:
2>/dev/null | jq -r '.[0].data // empty' 2>/dev/null || true)
if [ "$CURRENT" = "$FLOATING_IP" ]; then
echo "${record}.${DOMAIN} -> ${FLOATING_IP} exists, skipping"
log_message "INFO" "${record}.${DOMAIN} -> ${FLOATING_IP} exists, skipping"
else
curl -sf -X PUT \
-H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \
-H "Content-Type: application/json" \
"https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${record}" \
-d "[{\"data\":\"${FLOATING_IP}\",\"ttl\":600}]"
echo "${record}.${DOMAIN} -> ${FLOATING_IP} added/updated"
log_message "SUCCESS" "${record}.${DOMAIN} -> ${FLOATING_IP} added/updated"
fi
done

240
common-functions-base.sh Normal file
View File

@ -0,0 +1,240 @@
#!/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.
# Örn: Milisaniye cinsinden JWT süresini saniyeye çevirir.
refresh_calculated_env_vars() {
export JWT_ACCESS_TOKEN_EXPIRATION_SEC=$(( JWT_ACCESS_TOKEN_EXPIRATION / 1000 ))
}
# 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
}