#!/bin/bash # Kubernetes deployment script with environment variable substitution # This script processes template files and applies them to Kubernetes # # Registry Authentication Setup: # To pull images from a private registry, set these environment variables: # REGISTRY_USERNAME - Username for the container registry # REGISTRY_PASSWORD - Password/token for the container registry # REGISTRY_HOST - Registry hostname (default: gitea-http.taildb3494.ts.net) # # Example in .env file: # REGISTRY_USERNAME=your-username # REGISTRY_PASSWORD=your-password-or-token # REGISTRY_HOST=gitea-http.taildb3494.ts.net set -euo pipefail # 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 # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" K8S_DIR="$PROJECT_ROOT/k8s" TEMP_DIR="/tmp/meds-k8s-deploy" # Function to print colored output print_info() { echo -e "${BLUE}ℹ️ $1${NC}" } print_success() { echo -e "${GREEN}✅ $1${NC}" } print_warning() { echo -e "${YELLOW}⚠️ $1${NC}" } print_error() { echo -e "${RED}❌ $1${NC}" } # Function to load environment variables load_env() { local env_file="$1" if [[ -f "$env_file" ]]; then print_info "Loading environment from $env_file" # Export variables from .env file set -a source "$env_file" set +a else print_warning "Environment file $env_file not found" fi } # Function to substitute environment variables in template files substitute_templates() { print_info "Processing template files..." # Create temporary directory mkdir -p "$TEMP_DIR" # Process each template file for template_file in "$K8S_DIR"/*.template; do if [[ -f "$template_file" ]]; then local filename=$(basename "$template_file" .template) local output_file="$TEMP_DIR/$filename" print_info "Processing template: $filename" # Substitute environment variables envsubst < "$template_file" > "$output_file" print_success "Generated: $output_file" fi done } # Function to create registry authentication create_registry_auth() { if [[ -n "${REGISTRY_USERNAME:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then local registry_host="${REGISTRY_HOST:-gitea-http.taildb3494.ts.net}" local auth_string=$(echo -n "${REGISTRY_USERNAME}:${REGISTRY_PASSWORD}" | base64 -w 0) local docker_config="{\"auths\":{\"${registry_host}\":{\"auth\":\"${auth_string}\"}}}" export REGISTRY_AUTH_BASE64=$(echo -n "$docker_config" | base64 -w 0) print_info "Registry authentication configured for $registry_host" else print_warning "Registry credentials not provided - skipping registry secret creation" export REGISTRY_AUTH_BASE64="" fi } # Function to validate required environment variables validate_env() { local required_vars=("INGRESS_HOST") local missing_vars=() for var in "${required_vars[@]}"; do if [[ -z "${!var:-}" ]]; then missing_vars+=("$var") fi done if [[ ${#missing_vars[@]} -gt 0 ]]; then print_error "Missing required environment variables:" for var in "${missing_vars[@]}"; do echo " - $var" done echo "" echo "Please set these variables in your .env file or environment." exit 1 fi } # Function to ensure namespace exists ensure_namespace() { if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then print_info "Creating namespace: $NAMESPACE" kubectl create namespace "$NAMESPACE" print_success "Created namespace: $NAMESPACE" else print_info "Using existing namespace: $NAMESPACE" fi } # Function to convert Kubernetes storage units to bytes storage_to_bytes() { local storage="$1" local number=$(echo "$storage" | sed 's/[^0-9]*//g') local unit=$(echo "$storage" | sed 's/[0-9]*//g') case "$unit" in "Ki"|"K") echo $((number * 1024)) ;; "Mi"|"M") echo $((number * 1024 * 1024)) ;; "Gi"|"G") echo $((number * 1024 * 1024 * 1024)) ;; "Ti"|"T") echo $((number * 1024 * 1024 * 1024 * 1024)) ;; "") echo "$number" ;; *) echo "0" ;; esac } # Function to check if PVC storage can be updated can_update_pvc_storage() { local pvc_file="$1" local pvc_name=$(grep "name:" "$pvc_file" | head -1 | awk '{print $2}') local new_storage=$(grep "storage:" "$pvc_file" | awk '{print $2}') # Check if PVC exists if kubectl get pvc "$pvc_name" -n "$NAMESPACE" &> /dev/null; then local current_storage=$(kubectl get pvc "$pvc_name" -n "$NAMESPACE" -o jsonpath='{.status.capacity.storage}') # Convert storage sizes to bytes for comparison local current_bytes=$(storage_to_bytes "$current_storage") local new_bytes=$(storage_to_bytes "$new_storage") if [[ "$new_bytes" -lt "$current_bytes" ]]; then print_warning "Skipping PVC $pvc_name: cannot reduce storage from $current_storage to $new_storage" return 1 fi fi return 0 } # Function to apply Kubernetes manifests apply_manifests() { local manifest_dir="$1" print_info "Applying Kubernetes manifests from $manifest_dir" # Apply static files that don't have template counterparts for manifest_file in "$K8S_DIR"/*.yaml; do if [[ -f "$manifest_file" && ! "$manifest_file" =~ \.template$ ]]; then local basename_file=$(basename "$manifest_file") local template_file="$K8S_DIR/${basename_file}.template" # Only apply if there's no corresponding template file if [[ ! -f "$template_file" ]]; then print_info "Applying static file: $basename_file" kubectl apply -f "$manifest_file" -n "$NAMESPACE" fi fi done # Apply processed template files if [[ -d "$TEMP_DIR" ]]; then for manifest_file in "$TEMP_DIR"/*.yaml; do if [[ -f "$manifest_file" ]]; then local basename_file=$(basename "$manifest_file") # Special handling for PVC files if [[ "$basename_file" == *"pvc.yaml" ]] && grep -q "kind: PersistentVolumeClaim" "$manifest_file"; then if can_update_pvc_storage "$manifest_file"; then print_info "Applying template: $basename_file" kubectl apply -f "$manifest_file" -n "$NAMESPACE" fi # Special handling for registry secret - skip if no auth provided elif [[ "$basename_file" == *"registry-secret.yaml" ]] && [[ -z "${REGISTRY_AUTH_BASE64:-}" ]]; then print_info "Skipping registry secret: no registry credentials provided" else print_info "Applying template: $basename_file" if ! kubectl apply -f "$manifest_file" -n "$NAMESPACE" 2>/dev/null; then # Handle StatefulSet update failures gracefully if [[ "$basename_file" == *"statefulset.yaml" ]] && grep -q "kind: StatefulSet" "$manifest_file"; then print_warning "StatefulSet update failed (likely due to immutable fields) - continuing deployment" else # Re-run the command to show the actual error for non-StatefulSet resources kubectl apply -f "$manifest_file" -n "$NAMESPACE" fi fi fi fi done fi } # Function to cleanup temporary files cleanup() { if [[ -d "$TEMP_DIR" ]]; then print_info "Cleaning up temporary files..." rm -rf "$TEMP_DIR" fi } # Function to show deployment status show_status() { print_info "Deployment Status:" echo "" print_info "Pods:" kubectl get pods -l app=rxminder -n "$NAMESPACE" echo "" print_info "Services:" kubectl get services -l app=rxminder -n "$NAMESPACE" echo "" print_info "Ingress:" kubectl get ingress -l app=rxminder -n "$NAMESPACE" echo "" if [[ -n "${INGRESS_HOST:-}" ]]; then print_success "Application should be available at: http://$INGRESS_HOST" fi } # Function to show usage usage() { echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " -e, --env FILE Environment file to load (default: .env)" echo " -d, --dry-run Show what would be applied without applying" echo " -s, --status Show deployment status only" echo " -c, --cleanup Cleanup temporary files and exit" echo " -h, --help Show this help message" echo "" echo "Examples:" echo " $0 Deploy with default .env file" echo " $0 -e .env.prod Deploy with production environment" echo " $0 --dry-run Preview what would be deployed" echo " $0 --status Check deployment status" } # Main function main() { local env_file=".env" local dry_run=false local status_only=false local cleanup_only=false # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in -e|--env) env_file="$2" shift 2 ;; -d|--dry-run) dry_run=true shift ;; -s|--status) status_only=true shift ;; -c|--cleanup) cleanup_only=true shift ;; -h|--help) usage exit 0 ;; *) print_error "Unknown option: $1" usage exit 1 ;; esac done # Handle cleanup only if [[ "$cleanup_only" == true ]]; then cleanup exit 0 fi # Handle status only if [[ "$status_only" == true ]]; then load_env "$env_file" NAMESPACE="${NAMESPACE:-rxminder}" show_status exit 0 fi # Check if kubectl is available if ! command -v kubectl &> /dev/null; then print_error "kubectl is not installed or not in PATH" exit 1 fi # Check if we can connect to Kubernetes cluster if ! kubectl cluster-info &> /dev/null; then print_error "Cannot connect to Kubernetes cluster" print_info "Make sure your kubectl is configured correctly" exit 1 fi print_info "🚀 Deploying Medication Reminder App to Kubernetes" echo "" # Load environment variables load_env "$env_file" # Set default values for required variables export APP_NAME="${APP_NAME:-rxminder}" export STORAGE_CLASS="${STORAGE_CLASS:-longhorn}" export STORAGE_SIZE="${STORAGE_SIZE:-5Gi}" export DOCKER_IMAGE="${DOCKER_IMAGE:-gitea-http.taildb3494.ts.net/will/rxminder:latest}" # Set default namespace if not provided in environment NAMESPACE="${NAMESPACE:-rxminder}" # Create registry authentication if credentials are provided create_registry_auth # Validate required environment variables validate_env # Ensure namespace exists ensure_namespace # Process templates substitute_templates if [[ "$dry_run" == true ]]; then print_info "Dry run mode - showing generated manifests:" echo "" for manifest_file in "$TEMP_DIR"/*.yaml; do if [[ -f "$manifest_file" ]]; then echo "=== $(basename "$manifest_file") ===" cat "$manifest_file" echo "" fi done else # Apply manifests apply_manifests "$K8S_DIR" print_success "Deployment completed!" echo "" # Show status show_status fi # Cleanup cleanup } # Trap to ensure cleanup happens trap cleanup EXIT # Run main function main "$@"