Files
rxminder/scripts/deploy-k8s.sh
William Valentin e47150f80a feat: Add container registry support and Kustomize foundation
- Add registry secret template for private container registry authentication
- Fix frontend deployment to use imagePullSecrets for private registry
- Enhance deploy-k8s.sh with registry authentication handling
- Add PVC storage size validation to prevent storage reduction errors
- Add graceful StatefulSet update error handling
- Fix template variable substitution for DOCKER_IMAGE
- Remove conflicting static PVC file that had unprocessed template variables
- Add Kustomize structure as alternative to shell script templates:
  - Base configuration with common resources
  - Development overlay with dev-specific configurations
  - Support for environment-specific image tags and resource limits

Registry setup requires setting REGISTRY_USERNAME, REGISTRY_PASSWORD, and
optionally REGISTRY_HOST in .env file for private registry authentication.
2025-09-07 20:28:23 -07:00

394 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 "$@"