#!/bin/bash # deploy-with-env.sh # Deploy Kustomize configurations with environment variable substitution # Usage: ./scripts/deploy-with-env.sh [environment] [action] # Example: ./scripts/deploy-with-env.sh dev apply # Example: ./scripts/deploy-with-env.sh prod diff set -euo pipefail # Default values ENVIRONMENT=${1:-dev} ACTION=${2:-apply} SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Load environment variables from multiple sources load_environment() { local env_files=( "$HOME/.env" "$PROJECT_ROOT/.env" "$PROJECT_ROOT/.env.$ENVIRONMENT" "$PROJECT_ROOT/.env.local" ) print_status "Loading environment variables for: $ENVIRONMENT" for env_file in "${env_files[@]}"; do if [[ -f "$env_file" ]]; then print_status "Loading: $env_file" set -a source "$env_file" set +a fi done # Set defaults if not provided export APP_NAME="${APP_NAME:-rxminder}" export NODE_ENV="${NODE_ENV:-$ENVIRONMENT}" export IMAGE_TAG="${IMAGE_TAG:-latest}" export NAMESPACE="${NAMESPACE:-${APP_NAME}-${ENVIRONMENT}}" # Environment-specific defaults case "$ENVIRONMENT" in "dev"|"development") export NODE_ENV="development" export LOG_LEVEL="${LOG_LEVEL:-debug}" export DEBUG="${DEBUG:-true}" export IMAGE_TAG="${IMAGE_TAG:-dev}" export INGRESS_HOST="${INGRESS_HOST:-${APP_NAME}-dev.local}" ;; "prod"|"production") export NODE_ENV="production" export LOG_LEVEL="${LOG_LEVEL:-warn}" export DEBUG="${DEBUG:-false}" export IMAGE_TAG="${IMAGE_TAG:-v1.0.0}" export INGRESS_HOST="${INGRESS_HOST:-${APP_NAME}.yourdomain.com}" ;; "staging") export NODE_ENV="staging" export LOG_LEVEL="${LOG_LEVEL:-info}" export DEBUG="${DEBUG:-false}" export IMAGE_TAG="${IMAGE_TAG:-staging}" export INGRESS_HOST="${INGRESS_HOST:-staging.${APP_NAME}.yourdomain.com}" ;; esac print_success "Environment loaded: $ENVIRONMENT" print_status "Key variables:" echo " APP_NAME: $APP_NAME" echo " NODE_ENV: $NODE_ENV" echo " IMAGE_TAG: $IMAGE_TAG" echo " NAMESPACE: $NAMESPACE" echo " INGRESS_HOST: $INGRESS_HOST" } # Substitute environment variables in kustomization files substitute_variables() { local overlay_dir="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT" local temp_dir=$(mktemp -d) local kustomization_file="$overlay_dir/kustomization.yaml" print_status "Creating temporary overlay with variable substitution..." # Copy overlay directory to temp location cp -r "$overlay_dir" "$temp_dir/" local temp_overlay="$temp_dir/$(basename "$overlay_dir")" # Substitute variables in kustomization.yaml if [[ -f "$temp_overlay/kustomization.yaml" ]]; then envsubst < "$kustomization_file" > "$temp_overlay/kustomization.yaml.tmp" mv "$temp_overlay/kustomization.yaml.tmp" "$temp_overlay/kustomization.yaml" fi # Substitute variables in any other YAML files in the overlay find "$temp_overlay" -name "*.yaml" -not -name "kustomization.yaml" | while read -r file; do envsubst < "$file" > "$file.tmp" mv "$file.tmp" "$file" done echo "$temp_overlay" } # Generate dynamic ConfigMap with current environment variables generate_dynamic_config() { local temp_dir="$1" local config_file="$temp_dir/dynamic-config.env" print_status "Generating dynamic configuration..." cat > "$config_file" << EOF # Dynamic configuration generated at deploy time # Generated on: $(date) # Environment: $ENVIRONMENT # Application Configuration APP_NAME=$APP_NAME NODE_ENV=$NODE_ENV LOG_LEVEL=$LOG_LEVEL DEBUG=$DEBUG # Image Configuration IMAGE_TAG=$IMAGE_TAG REGISTRY_URL=${REGISTRY_URL:-gitea-http.taildb3494.ts.net} IMAGE_REPOSITORY=${IMAGE_REPOSITORY:-will/rxminder} # Network Configuration INGRESS_HOST=$INGRESS_HOST NAMESPACE=$NAMESPACE # API Configuration REACT_APP_API_URL=${REACT_APP_API_URL:-http://${APP_NAME}-couchdb-service:5984} # Database Configuration DB_HOST=${DB_HOST:-${APP_NAME}-couchdb-service} DB_PORT=${DB_PORT:-5984} COUCHDB_DATABASE_NAME=${COUCHDB_DATABASE_NAME:-meds_app} # Feature Flags ENABLE_MONITORING=${ENABLE_MONITORING:-false} ENABLE_METRICS=${ENABLE_METRICS:-false} ENABLE_TRACING=${ENABLE_TRACING:-false} # Performance Configuration CACHE_TTL=${CACHE_TTL:-1800} REQUEST_TIMEOUT=${REQUEST_TIMEOUT:-30000} MAX_CONNECTIONS=${MAX_CONNECTIONS:-100} # Security Configuration ENABLE_CORS=${ENABLE_CORS:-true} CORS_ORIGIN=${CORS_ORIGIN:-*} # Build Information BUILD_VERSION=${BUILD_VERSION:-unknown} BUILD_COMMIT=${BUILD_COMMIT:-$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")} BUILD_DATE=${BUILD_DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)} EOF # Update kustomization.yaml to include dynamic config local kustomization_file="$temp_dir/kustomization.yaml" # Add configMapGenerator for dynamic config if not present if ! grep -q "dynamic-config.env" "$kustomization_file" 2>/dev/null; then cat >> "$kustomization_file" << EOF # Dynamic configuration added at deploy time configMapGenerator: - name: ${APP_NAME}-dynamic-config envs: - dynamic-config.env behavior: create EOF fi print_success "Dynamic configuration generated" } # Validate deployment prerequisites validate_prerequisites() { print_status "Validating prerequisites..." # Check kubectl if ! command -v kubectl &> /dev/null; then print_error "kubectl is not installed or not in PATH" return 1 fi # Check kubectl connectivity if ! kubectl cluster-info &> /dev/null; then print_error "Cannot connect to Kubernetes cluster" return 1 fi # Check if overlay exists local overlay_dir="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT" if [[ ! -d "$overlay_dir" ]]; then print_error "Overlay directory not found: $overlay_dir" return 1 fi # Check if kustomization.yaml exists if [[ ! -f "$overlay_dir/kustomization.yaml" ]]; then print_error "kustomization.yaml not found in: $overlay_dir" return 1 fi print_success "Prerequisites validated" } # Execute kubectl command with the prepared overlay execute_kubectl() { local temp_overlay="$1" local action="$2" print_status "Executing kubectl $action with prepared overlay..." case "$action" in "apply") kubectl apply -k "$temp_overlay" ;; "delete") kubectl delete -k "$temp_overlay" --ignore-not-found=true ;; "diff") kubectl diff -k "$temp_overlay" || true ;; "dry-run") kubectl apply -k "$temp_overlay" --dry-run=client -o yaml ;; "validate") kubectl kustomize "$temp_overlay" | kubectl apply --dry-run=client --validate=true -f - ;; "build") kubectl kustomize "$temp_overlay" ;; *) print_error "Unknown action: $action" print_status "Supported actions: apply, delete, diff, dry-run, validate, build" return 1 ;; esac } # Show deployment status show_status() { local namespace="${NAMESPACE:-${APP_NAME}-${ENVIRONMENT}}" print_status "Deployment status for $ENVIRONMENT environment:" echo echo "Namespace: $namespace" kubectl get namespace "$namespace" 2>/dev/null || echo "Namespace not found" echo echo "Deployments:" kubectl get deployments -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No deployments found" echo echo "Services:" kubectl get services -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No services found" echo echo "Ingress:" kubectl get ingress -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No ingress found" echo echo "ConfigMaps:" kubectl get configmaps -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No configmaps found" } # Cleanup function cleanup() { if [[ -n "${TEMP_DIR:-}" ]] && [[ -d "$TEMP_DIR" ]]; then print_status "Cleaning up temporary files..." rm -rf "$TEMP_DIR" fi } # Set up cleanup trap trap cleanup EXIT # Show usage information show_usage() { cat << EOF Usage: $0 [environment] [action] [options] ENVIRONMENTS: dev, development Deploy to development environment prod, production Deploy to production environment staging Deploy to staging environment ACTIONS: apply Apply the configuration (default) delete Delete the deployment diff Show differences dry-run Show what would be applied validate Validate configuration build Build and show manifests status Show deployment status OPTIONS: -h, --help Show this help message --no-env Skip loading environment files --verbose Enable verbose output EXAMPLES: $0 dev apply Deploy to development $0 prod diff Show production differences $0 staging delete Delete staging deployment $0 dev status Show development status ENVIRONMENT FILES: The script loads variables from (in order): - ~/.env - ./.env - ./.env.\$ENVIRONMENT - ./.env.local EOF } # Main execution function main() { local skip_env=false local verbose=false # Parse options while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_usage exit 0 ;; --no-env) skip_env=true shift ;; --verbose) verbose=true set -x shift ;; -*) print_error "Unknown option: $1" show_usage exit 1 ;; *) if [[ -z "${ENVIRONMENT_SET:-}" ]]; then ENVIRONMENT="$1" ENVIRONMENT_SET=true elif [[ -z "${ACTION_SET:-}" ]]; then ACTION="$1" ACTION_SET=true else print_error "Too many arguments: $1" show_usage exit 1 fi shift ;; esac done print_status "Kustomize Deployment with Environment Variables" print_status "Environment: $ENVIRONMENT" print_status "Action: $ACTION" # Special case for status action if [[ "$ACTION" == "status" ]]; then if [[ "$skip_env" != "true" ]]; then load_environment fi show_status exit 0 fi # Validate prerequisites validate_prerequisites # Load environment variables if [[ "$skip_env" != "true" ]]; then load_environment fi # Create temporary overlay with substituted variables TEMP_DIR=$(substitute_variables) generate_dynamic_config "$TEMP_DIR" # Execute the kubectl command execute_kubectl "$TEMP_DIR" "$ACTION" if [[ "$ACTION" == "apply" ]]; then print_success "Deployment completed successfully!" show_status elif [[ "$ACTION" == "delete" ]]; then print_success "Resources deleted successfully!" fi } # Run main function with all arguments main "$@"