meldestelle/scripts/config-sync.sh
2025-09-15 11:08:55 +02:00

615 lines
24 KiB
Bash
Executable File

#!/bin/bash
# ===================================================================
# Configuration Synchronization Utility
# Syncs config/central.toml to all dependent configuration files
# Eliminates redundancy across 38+ port definitions and 72+ Spring profiles
# ===================================================================
set -euo pipefail
# Script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CENTRAL_CONFIG="$PROJECT_ROOT/config/central.toml"
# Load common utilities
# shellcheck source=utils/common.sh
source "$SCRIPT_DIR/utils/common.sh" || {
echo "Error: Could not load common utilities"
exit 1
}
# ===================================================================
# TOML Parser Functions
# ===================================================================
# Function to extract value from TOML file
get_config_value() {
local section=$1
local key=$2
local config_file=${3:-$CENTRAL_CONFIG}
# Handle nested sections like [ports] or [spring-profiles.defaults]
if [[ "$section" == *.* ]]; then
# Split nested section
local main_section="${section%%.*}"
local subsection="${section#*.}"
# Extract from nested section
awk -v main="$main_section" -v subsec="$subsection" -v key="$key" '
BEGIN { in_main = 0; in_subsec = 0 }
/^\[/ && !/^\['"$main_section"'/ && !/^\['"$main_section"'\./ { in_main = 0; in_subsec = 0 }
$0 ~ "^\\[" main "\\]$" { in_main = 1; in_subsec = 0; next }
$0 ~ "^\\[" main "\\." subsec "\\]$" { in_main = 1; in_subsec = 1; next }
in_subsec && $0 ~ "^" key " *= *" {
gsub(/^[^=]*= *"?/, ""); gsub(/"$/, ""); print; exit
}
' "$config_file"
else
# Extract from simple section
awk -v section="$section" -v key="$key" '
BEGIN { in_section = 0 }
/^\[/ { in_section = 0 }
$0 ~ "^\\[" section "\\]$" { in_section = 1; next }
in_section && $0 ~ "^" key " *= *" {
gsub(/^[^=]*= *"?/, ""); gsub(/"$/, ""); print; exit
}
' "$config_file"
fi
}
# Function to get all keys from a TOML section
get_section_keys() {
local section=$1
local config_file=${2:-$CENTRAL_CONFIG}
awk -v section="$section" '
BEGIN { in_section = 0 }
/^\[/ { in_section = 0 }
$0 ~ "^\\[" section "\\]$" { in_section = 1; next }
in_section && /^[a-zA-Z0-9_-]+ *= *.*$/ {
match($0, /^[a-zA-Z0-9_-]+/); print substr($0, RSTART, RLENGTH)
}
' "$config_file"
}
# ===================================================================
# Synchronization Functions
# ===================================================================
# Function to sync gradle.properties
sync_gradle_properties() {
log_section "Syncing gradle.properties"
local gradle_file="$PROJECT_ROOT/gradle.properties"
local backup_file="${gradle_file}.bak.$(date +%Y%m%d_%H%M%S)"
# Create backup
cp "$gradle_file" "$backup_file"
log_info "Created backup: $(basename "$backup_file")"
# Extract port values from central config
local gateway_port=$(get_config_value "ports" "api-gateway")
local consul_port=$(get_config_value "ports" "consul")
local ping_port=$(get_config_value "ports" "ping-service")
local members_port=$(get_config_value "ports" "members-service")
local horses_port=$(get_config_value "ports" "horses-service")
local events_port=$(get_config_value "ports" "events-service")
# Update gradle.properties with centralized values
sed -i.tmp \
-e "s/^infrastructure\.gateway\.port=.*/infrastructure.gateway.port=${gateway_port}/" \
-e "s/^infrastructure\.consul\.port=.*/infrastructure.consul.port=${consul_port}/" \
-e "s/^services\.port\.start=.*/services.port.start=${ping_port}/" \
-e "s/^services\.port\.ping=.*/services.port.ping=${ping_port}/" \
-e "s/^services\.port\.members=.*/services.port.members=${members_port}/" \
-e "s/^services\.port\.horses=.*/services.port.horses=${horses_port}/" \
-e "s/^services\.port\.events=.*/services.port.events=${events_port}/" \
"$gradle_file"
rm -f "${gradle_file}.tmp"
log_success "Updated gradle.properties with centralized ports"
}
# Function to sync Docker Compose files
sync_docker_compose_files() {
log_section "Syncing Docker Compose files"
local compose_files=(
"$PROJECT_ROOT/docker-compose.yml"
"$PROJECT_ROOT/docker-compose.services.yml"
"$PROJECT_ROOT/docker-compose.clients.yml"
)
# Extract values from central config
local gateway_port=$(get_config_value "ports" "api-gateway")
local ping_port=$(get_config_value "ports" "ping-service")
local members_port=$(get_config_value "ports" "members-service")
local horses_port=$(get_config_value "ports" "horses-service")
local events_port=$(get_config_value "ports" "events-service")
local masterdata_port=$(get_config_value "ports" "masterdata-service")
local auth_port=$(get_config_value "ports" "auth-server")
local consul_port=$(get_config_value "ports" "consul")
local redis_port=$(get_config_value "ports" "redis")
local postgres_port=$(get_config_value "ports" "postgres")
local prometheus_port=$(get_config_value "ports" "prometheus")
local grafana_port=$(get_config_value "ports" "grafana")
local keycloak_port=$(get_config_value "ports" "keycloak")
local web_app_port=$(get_config_value "ports" "web-app")
# Extract Spring profiles
local infrastructure_profile=$(get_config_value "spring-profiles.defaults" "infrastructure")
local services_profile=$(get_config_value "spring-profiles.defaults" "services")
local clients_profile=$(get_config_value "spring-profiles.defaults" "clients")
for compose_file in "${compose_files[@]}"; do
if [[ -f "$compose_file" ]]; then
local backup_file="${compose_file}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$compose_file" "$backup_file"
log_info "Created backup: $(basename "$backup_file")"
# Update port references
sed -i.tmp \
-e "s/\${GATEWAY_PORT:-[0-9]*}/\${GATEWAY_PORT:-${gateway_port}}/g" \
-e "s/\${PING_SERVICE_PORT:-[0-9]*}/\${PING_SERVICE_PORT:-${ping_port}}/g" \
-e "s/\${MEMBERS_SERVICE_PORT:-[0-9]*}/\${MEMBERS_SERVICE_PORT:-${members_port}}/g" \
-e "s/\${HORSES_SERVICE_PORT:-[0-9]*}/\${HORSES_SERVICE_PORT:-${horses_port}}/g" \
-e "s/\${EVENTS_SERVICE_PORT:-[0-9]*}/\${EVENTS_SERVICE_PORT:-${events_port}}/g" \
-e "s/\${MASTERDATA_SERVICE_PORT:-[0-9]*}/\${MASTERDATA_SERVICE_PORT:-${masterdata_port}}/g" \
-e "s/\${AUTH_SERVICE_PORT:-[0-9]*}/\${AUTH_SERVICE_PORT:-${auth_port}}/g" \
-e "s/\${CONSUL_PORT:-[0-9]*}/\${CONSUL_PORT:-${consul_port}}/g" \
-e "s/\${REDIS_PORT:-[0-9]*}/\${REDIS_PORT:-${redis_port}}/g" \
-e "s/\${PROMETHEUS_PORT:-[0-9]*}/\${PROMETHEUS_PORT:-${prometheus_port}}/g" \
-e "s/\${GRAFANA_PORT:-[0-9]*}/\${GRAFANA_PORT:-${grafana_port}}/g" \
-e "s/:[0-9]*\":${postgres_port}/:${postgres_port}:${postgres_port}/g" \
-e "s/:[0-9]*\":${redis_port}/:${redis_port}:${redis_port}/g" \
-e "s/\${DOCKER_SPRING_PROFILES_DEFAULT:-[^}]*}/\${DOCKER_SPRING_PROFILES_DEFAULT:-${infrastructure_profile}}/g" \
-e "s/\${DOCKER_SPRING_PROFILES_DOCKER:-[^}]*}/\${DOCKER_SPRING_PROFILES_DOCKER:-${services_profile}}/g" \
"$compose_file"
rm -f "${compose_file}.tmp"
log_success "Updated $(basename "$compose_file")"
else
log_warning "File not found: $(basename "$compose_file")"
fi
done
}
# Function to sync environment files
sync_environment_files() {
log_section "Syncing Environment Files"
local env_template="$PROJECT_ROOT/config/.env.template"
if [[ -f "$env_template" ]]; then
local backup_file="${env_template}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$env_template" "$backup_file"
log_info "Created backup: $(basename "$backup_file")"
# Extract all port values
local gateway_port=$(get_config_value "ports" "api-gateway")
local ping_port=$(get_config_value "ports" "ping-service")
local members_port=$(get_config_value "ports" "members-service")
local horses_port=$(get_config_value "ports" "horses-service")
local events_port=$(get_config_value "ports" "events-service")
local masterdata_port=$(get_config_value "ports" "masterdata-service")
local auth_port=$(get_config_value "ports" "auth-server")
local consul_port=$(get_config_value "ports" "consul")
local redis_port=$(get_config_value "ports" "redis")
local postgres_port=$(get_config_value "ports" "postgres")
local prometheus_port=$(get_config_value "ports" "prometheus")
local grafana_port=$(get_config_value "ports" "grafana")
# Update .env.template with centralized values
sed -i.tmp \
-e "s/^GATEWAY_PORT=.*/GATEWAY_PORT=${gateway_port}/" \
-e "s/^PING_SERVICE_PORT=.*/PING_SERVICE_PORT=${ping_port}/" \
-e "s/^MEMBERS_SERVICE_PORT=.*/MEMBERS_SERVICE_PORT=${members_port}/" \
-e "s/^HORSES_SERVICE_PORT=.*/HORSES_SERVICE_PORT=${horses_port}/" \
-e "s/^EVENTS_SERVICE_PORT=.*/EVENTS_SERVICE_PORT=${events_port}/" \
-e "s/^MASTERDATA_SERVICE_PORT=.*/MASTERDATA_SERVICE_PORT=${masterdata_port}/" \
-e "s/^AUTH_SERVICE_PORT=.*/AUTH_SERVICE_PORT=${auth_port}/" \
-e "s/^CONSUL_PORT=.*/CONSUL_PORT=${consul_port}/" \
-e "s/^REDIS_PORT=.*/REDIS_PORT=${redis_port}/" \
-e "s/^DB_PORT=.*/DB_PORT=${postgres_port}/" \
-e "s/^PROMETHEUS_PORT=.*/PROMETHEUS_PORT=${prometheus_port}/" \
-e "s/^GRAFANA_PORT=.*/GRAFANA_PORT=${grafana_port}/" \
"$env_template"
rm -f "${env_template}.tmp"
log_success "Updated .env.template"
else
log_warning ".env.template not found"
fi
}
# Function to sync Docker build arguments
sync_docker_build_args() {
log_section "Syncing Docker Build Arguments"
local build_args_dir="$PROJECT_ROOT/docker/build-args"
# Extract Spring profiles from central config
local infrastructure_profile=$(get_config_value "spring-profiles.defaults" "infrastructure")
local services_profile=$(get_config_value "spring-profiles.defaults" "services")
local clients_profile=$(get_config_value "spring-profiles.defaults" "clients")
# Update services.env
local services_env="$build_args_dir/services.env"
if [[ -f "$services_env" ]]; then
local backup_file="${services_env}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$services_env" "$backup_file"
# Extract port values
local ping_port=$(get_config_value "ports" "ping-service")
local members_port=$(get_config_value "ports" "members-service")
local horses_port=$(get_config_value "ports" "horses-service")
local events_port=$(get_config_value "ports" "events-service")
local masterdata_port=$(get_config_value "ports" "masterdata-service")
sed -i.tmp \
-e "s/^SPRING_PROFILES_ACTIVE=.*/SPRING_PROFILES_ACTIVE=${services_profile}/" \
-e "s/^PING_SERVICE_PORT=.*/PING_SERVICE_PORT=${ping_port}/" \
-e "s/^MEMBERS_SERVICE_PORT=.*/MEMBERS_SERVICE_PORT=${members_port}/" \
-e "s/^HORSES_SERVICE_PORT=.*/HORSES_SERVICE_PORT=${horses_port}/" \
-e "s/^EVENTS_SERVICE_PORT=.*/EVENTS_SERVICE_PORT=${events_port}/" \
-e "s/^MASTERDATA_SERVICE_PORT=.*/MASTERDATA_SERVICE_PORT=${masterdata_port}/" \
"$services_env"
rm -f "${services_env}.tmp"
log_success "Updated services.env"
fi
# Update infrastructure.env
local infrastructure_env="$build_args_dir/infrastructure.env"
if [[ -f "$infrastructure_env" ]]; then
local backup_file="${infrastructure_env}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$infrastructure_env" "$backup_file"
# Extract port values
local gateway_port=$(get_config_value "ports" "api-gateway")
local auth_port=$(get_config_value "ports" "auth-server")
local monitoring_port=$(get_config_value "ports" "monitoring-server")
local consul_port=$(get_config_value "ports" "consul")
sed -i.tmp \
-e "s/^SPRING_PROFILES_ACTIVE=.*/SPRING_PROFILES_ACTIVE=${infrastructure_profile}/" \
-e "s/^GATEWAY_PORT=.*/GATEWAY_PORT=${gateway_port}/" \
-e "s/^AUTH_SERVER_PORT=.*/AUTH_SERVER_PORT=${auth_port}/" \
-e "s/^MONITORING_SERVER_PORT=.*/MONITORING_SERVER_PORT=${monitoring_port}/" \
-e "s/^CONSUL_PORT=.*/CONSUL_PORT=${consul_port}/" \
"$infrastructure_env"
rm -f "${infrastructure_env}.tmp"
log_success "Updated infrastructure.env"
fi
# Update clients.env
local clients_env="$build_args_dir/clients.env"
if [[ -f "$clients_env" ]]; then
local backup_file="${clients_env}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$clients_env" "$backup_file"
# Extract port values
local web_app_port=$(get_config_value "ports" "web-app")
local vnc_port=$(get_config_value "ports" "desktop-app-vnc")
local novnc_port=$(get_config_value "ports" "desktop-app-novnc")
sed -i.tmp \
-e "s/^WEB_APP_PORT=.*/WEB_APP_PORT=${web_app_port}/" \
-e "s/^DESKTOP_APP_VNC_PORT=.*/DESKTOP_APP_VNC_PORT=${vnc_port}/" \
-e "s/^DESKTOP_APP_NOVNC_PORT=.*/DESKTOP_APP_NOVNC_PORT=${novnc_port}/" \
"$clients_env"
rm -f "${clients_env}.tmp"
log_success "Updated clients.env"
fi
}
# Function to sync monitoring configuration
sync_monitoring_config() {
log_section "Syncing Monitoring Configuration"
local prometheus_config="$PROJECT_ROOT/config/monitoring/prometheus.dev.yml"
if [[ -f "$prometheus_config" ]]; then
local backup_file="${prometheus_config}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$prometheus_config" "$backup_file"
log_info "Created backup: $(basename "$backup_file")"
# Extract service ports
local ping_port=$(get_config_value "ports" "ping-service")
local members_port=$(get_config_value "ports" "members-service")
local horses_port=$(get_config_value "ports" "horses-service")
local events_port=$(get_config_value "ports" "events-service")
local masterdata_port=$(get_config_value "ports" "masterdata-service")
local gateway_port=$(get_config_value "ports" "api-gateway")
# Update Prometheus targets with centralized ports
sed -i.tmp \
-e "s/ping-service:[0-9]*/ping-service:${ping_port}/g" \
-e "s/members-service:[0-9]*/members-service:${members_port}/g" \
-e "s/horses-service:[0-9]*/horses-service:${horses_port}/g" \
-e "s/events-service:[0-9]*/events-service:${events_port}/g" \
-e "s/masterdata-service:[0-9]*/masterdata-service:${masterdata_port}/g" \
-e "s/api-gateway:[0-9]*/api-gateway:${gateway_port}/g" \
"$prometheus_config"
rm -f "${prometheus_config}.tmp"
log_success "Updated Prometheus configuration"
else
log_warning "Prometheus config not found: $prometheus_config"
fi
}
# Function to sync test scripts
sync_test_scripts() {
log_section "Syncing Test Scripts"
local test_scripts=(
"$PROJECT_ROOT/scripts/test/integration-test.sh"
"$PROJECT_ROOT/scripts/test/test_gateway.sh"
"$PROJECT_ROOT/scripts/test/test-monitoring.sh"
)
# Extract port values
local ping_port=$(get_config_value "ports" "ping-service")
local gateway_port=$(get_config_value "ports" "api-gateway")
local consul_port=$(get_config_value "ports" "consul")
local prometheus_port=$(get_config_value "ports" "prometheus")
local grafana_port=$(get_config_value "ports" "grafana")
for script_file in "${test_scripts[@]}"; do
if [[ -f "$script_file" ]]; then
local backup_file="${script_file}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$script_file" "$backup_file"
# Update port references in test scripts
sed -i.tmp \
-e "s/:${ping_port}[^0-9]/:${ping_port}/g" \
-e "s/localhost:[0-9]*\/actuator/localhost:${ping_port}\/actuator/g" \
-e "s/ping-service:[0-9]*/ping-service:${ping_port}/g" \
-e "s/api-gateway:[0-9]*/api-gateway:${gateway_port}/g" \
-e "s/consul:[0-9]*/consul:${consul_port}/g" \
-e "s/prometheus:[0-9]*/prometheus:${prometheus_port}/g" \
-e "s/grafana:[0-9]*/grafana:${grafana_port}/g" \
"$script_file"
rm -f "${script_file}.tmp"
log_success "Updated $(basename "$script_file")"
else
log_warning "Test script not found: $(basename "$script_file")"
fi
done
}
# ===================================================================
# Validation Functions
# ===================================================================
# Function to validate central configuration
validate_central_config() {
log_section "Validating Central Configuration"
if [[ ! -f "$CENTRAL_CONFIG" ]]; then
log_error "Central configuration file not found: $CENTRAL_CONFIG"
return 1
fi
log_info "Validating TOML syntax..."
# Basic TOML validation (check for common syntax errors)
local validation_errors=0
# Check for unclosed brackets
if ! awk '/^\[.*[^]]$/ { print "Unclosed bracket on line " NR ": " $0; exit 1 }' "$CENTRAL_CONFIG"; then
((validation_errors++))
fi
# Check for duplicate sections
local duplicate_sections=$(awk '/^\[.*\]$/ {
section = $0
count[section]++
}
END {
for (s in count) {
if (count[s] > 1)
print s
}
}' "$CENTRAL_CONFIG")
if [[ -n "$duplicate_sections" ]]; then
log_warning "Duplicate sections found: $duplicate_sections"
((validation_errors++))
fi
if [[ $validation_errors -eq 0 ]]; then
log_success "Central configuration is valid"
return 0
else
log_error "Central configuration has $validation_errors validation errors"
return 1
fi
}
# Function to show current configuration status
show_config_status() {
log_section "Configuration Status Report"
log_info "Current port assignments from central config:"
local services=("ping-service" "members-service" "horses-service" "events-service" "masterdata-service" "api-gateway" "auth-server")
for service in "${services[@]}"; do
local port=$(get_config_value "ports" "$service")
echo " ${service}: ${port}"
done
log_info "Current Spring profile defaults:"
local infrastructure_profile=$(get_config_value "spring-profiles.defaults" "infrastructure")
local services_profile=$(get_config_value "spring-profiles.defaults" "services")
local clients_profile=$(get_config_value "spring-profiles.defaults" "clients")
echo " Infrastructure: ${infrastructure_profile}"
echo " Services: ${services_profile}"
echo " Clients: ${clients_profile}"
}
# ===================================================================
# Main Functions
# ===================================================================
# Function to perform full synchronization
sync_all() {
log_section "Full Configuration Synchronization"
log_info "Syncing all configuration files from central.toml..."
# Validate central configuration first
validate_central_config || return 1
# Perform all synchronizations
sync_gradle_properties || return 1
sync_docker_compose_files || return 1
sync_environment_files || return 1
sync_docker_build_args || return 1
sync_monitoring_config || return 1
sync_test_scripts || return 1
log_success "All configuration files synchronized successfully!"
show_config_status
}
# Function to show help
show_help() {
cat << EOF
Configuration Synchronization Utility
USAGE:
$0 [COMMAND] [OPTIONS]
COMMANDS:
sync Synchronize all configuration files
validate Validate central configuration file
status Show current configuration status
gradle Sync gradle.properties only
compose Sync Docker Compose files only
env Sync environment files only
docker-args Sync Docker build arguments only
monitoring Sync monitoring configuration only
tests Sync test scripts only
OPTIONS:
-h, --help Show this help message
-v, --verbose Enable verbose output
--dry-run Show what would be changed without making changes
EXAMPLES:
$0 sync # Sync all configuration files
$0 validate # Validate central.toml syntax
$0 status # Show current port and profile assignments
$0 gradle # Sync gradle.properties only
This script reads from config/central.toml and updates all dependent
configuration files to eliminate redundancy across 38+ port definitions
and 72+ Spring profile configurations.
Configuration files that will be synchronized:
- gradle.properties
- docker-compose*.yml files
- config/.env.template
- docker/build-args/*.env files
- config/monitoring/*.yml files
- scripts/test/*.sh files
All original files are backed up before modification.
EOF
}
# Main execution function
main() {
local command="${1:-sync}"
local verbose=false
local dry_run=false
# Parse options
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-v|--verbose)
verbose=true
shift
;;
--dry-run)
dry_run=true
shift
;;
-*)
log_error "Unknown option: $1"
show_help
exit 1
;;
*)
command="$1"
shift
;;
esac
done
# Set verbose mode
if [[ "$verbose" == "true" ]]; then
set -x
fi
# Handle dry run
if [[ "$dry_run" == "true" ]]; then
log_warning "DRY RUN MODE - No files will be modified"
# In a real implementation, you would add dry-run logic here
fi
# Change to project root
cd "$PROJECT_ROOT"
# Execute command
case "$command" in
"sync"|"all")
sync_all
;;
"validate")
validate_central_config
;;
"status")
show_config_status
;;
"gradle")
validate_central_config && sync_gradle_properties
;;
"compose")
validate_central_config && sync_docker_compose_files
;;
"env")
validate_central_config && sync_environment_files
;;
"docker-args")
validate_central_config && sync_docker_build_args
;;
"monitoring")
validate_central_config && sync_monitoring_config
;;
"tests")
validate_central_config && sync_test_scripts
;;
*)
log_error "Unknown command: $command"
show_help
exit 1
;;
esac
log_success "Configuration synchronization completed!"
}
# Run main function with all arguments
main "$@"