#!/bin/bash set -euo pipefail # ========================================== # LOGGER — source the shared library # ========================================== # shellcheck source=lib/logger.sh source "$(dirname "$0")/lib/logger.sh" "deploy" trap 'log_error "Failure at line $LINENO. See $LOG_FILE"' ERR # ========================================== # VARIABLES # ========================================== RESOURCE_GROUP="ExamApp-RG" LOCATION="polandcentral" ACR_NAME="examappregistrycharles" AKS_NAME="ExamApp-AKS" DB_NAME="postgres" NODE_COUNT=2 NODE_VM_SIZE="Standard_B2als_v2" # Fixed DNS label: avoids recreating a new label on every run # (which would break the existing TLS certificate) DNS_LABEL="vigimeteo-prod" # DB credentials — required, never stored in Git if [ -z "${DB_USER:-}" ] || [ -z "${DB_PASSWORD:-}" ]; then log_error "DB_USER and DB_PASSWORD must be exported before running this script." log_error " export DB_USER=\"postgres\"" log_error " export DB_PASSWORD=\"your_password\"" exit 1 fi log_info "========================================" log_info " Vigimeteo – Deployment started" log_info " Full log: $LOG_FILE" log_info "========================================" # ========================================== log_info "STEP 1 — Cloud infrastructure" # ========================================== log_debug "Creating / verifying Resource Group '$RESOURCE_GROUP'..." az group create --name "$RESOURCE_GROUP" --location "$LOCATION" --output none log_info "Resource Group '$RESOURCE_GROUP' ready." # ACR if az acr show --name "$ACR_NAME" --resource-group "$RESOURCE_GROUP" --output none 2>/dev/null; then log_info "ACR '$ACR_NAME' already exists." else log_info "Creating ACR '$ACR_NAME'..." if ! az acr create \ --resource-group "$RESOURCE_GROUP" \ --name "$ACR_NAME" --sku Basic \ --location "$LOCATION" --output none; then ACR_NAME="examapp$(date +%s | tail -c 8)" log_warn "Name unavailable — using fallback name: $ACR_NAME" az acr create \ --resource-group "$RESOURCE_GROUP" \ --name "$ACR_NAME" --sku Basic \ --location "$LOCATION" --output none fi log_info "ACR '$ACR_NAME' created." fi ACR_LOGIN_SERVER=$(az acr show \ --name "$ACR_NAME" --resource-group "$RESOURCE_GROUP" \ --query loginServer --output tsv) log_debug "ACR login server: $ACR_LOGIN_SERVER" # AKS if az aks show \ --resource-group "$RESOURCE_GROUP" --name "$AKS_NAME" \ --output none 2>/dev/null; then log_info "Cluster '$AKS_NAME' already exists." else log_info "Creating AKS cluster '$AKS_NAME' (3–5 min)..." az aks create \ --resource-group "$RESOURCE_GROUP" \ --name "$AKS_NAME" \ --node-count "$NODE_COUNT" \ --node-vm-size "$NODE_VM_SIZE" \ --location "$LOCATION" \ --generate-ssh-keys \ --attach-acr "$ACR_NAME" \ --output none log_info "Cluster '$AKS_NAME' created with $NODE_COUNT nodes ($NODE_VM_SIZE)." fi az aks get-credentials \ --resource-group "$RESOURCE_GROUP" --name "$AKS_NAME" \ --overwrite-existing log_info "kubectl configured." # ========================================== log_info "STEP 2 — Build and push Docker images" # ========================================== az acr login --name "$ACR_NAME" --resource-group "$RESOURCE_GROUP" for service in backend frontend; do dir="" [ "$service" = "backend" ] && dir="./Back-end/" [ "$service" = "frontend" ] && dir="./Front-end/" image="$ACR_LOGIN_SERVER/vigimeteo-$service:latest" log_info "Building $service → $image" docker build -t "$image" "$dir" log_info "Pushing $service..." docker push "$image" log_info "$service pushed successfully." done # ========================================== log_info "STEP 3 — Kubernetes deployment" # ========================================== kubectl apply -f namespace.yaml log_debug "Namespace applied." kubectl create secret generic db-credentials \ --namespace vigimeteo \ --from-literal=host="vigimeteo-db.vigimeteo.svc.cluster.local" \ --from-literal=port="5432" \ --from-literal=dbname="$DB_NAME" \ --from-literal=username="$DB_USER" \ --from-literal=password="$DB_PASSWORD" \ --dry-run=client -o yaml | kubectl apply -f - log_info "Secret 'db-credentials' applied." kubectl create configmap vigimeteo-db-init \ --namespace vigimeteo \ --from-file=init_db.sql=./sql/init_db.sql \ --dry-run=client -o yaml | kubectl apply -f - log_info "ConfigMap 'vigimeteo-db-init' applied." kubectl apply -f statefulset.yaml kubectl apply -f service.yaml sed "s|MON_REGISTRE|$ACR_LOGIN_SERVER|g" deployment.yaml | kubectl apply -f - log_info "StatefulSet, Services and Deployment applied." # ========================================== log_info "STEP 4 — HTTPS exposure (ingress + TLS)" # ========================================== log_info "Applying ingress-nginx..." kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml # Wait for the ingress-nginx controller pod to be Ready before applying ingress.yaml. # Without this wait the admission webhook has no endpoints yet and kubectl returns # an InternalError when validating the Ingress resource. log_info "Waiting for ingress-nginx controller to be Ready (up to 90s)..." kubectl wait --namespace ingress-nginx \ --for=condition=ready pod \ --selector=app.kubernetes.io/component=controller \ --timeout=90s log_info "ingress-nginx controller is Ready." log_info "Applying cert-manager..." kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml log_info "Waiting for Azure public IP (up to 4 min)..." PUBLIC_IP="" ATTEMPTS=0 MAX_ATTEMPTS=24 # 24 × 10s = 4 min while [ -z "$PUBLIC_IP" ] && [ "$ATTEMPTS" -lt "$MAX_ATTEMPTS" ]; do sleep 10 ATTEMPTS=$((ATTEMPTS + 1)) PUBLIC_IP=$(kubectl get svc ingress-nginx-controller \ -n ingress-nginx \ -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || true) log_debug "Attempt $ATTEMPTS/$MAX_ATTEMPTS — IP: ${PUBLIC_IP:-pending}" done if [ -z "$PUBLIC_IP" ]; then log_error "Public IP not obtained after 4 minutes." log_error "Diagnose with: kubectl get svc -n ingress-nginx" exit 1 fi log_info "Public IP obtained: $PUBLIC_IP" NODE_RG=$(az aks show \ --resource-group "$RESOURCE_GROUP" --name "$AKS_NAME" \ --query nodeResourceGroup -o tsv) PUBLIC_IP_NAME=$(az network public-ip list \ --resource-group "$NODE_RG" \ --query "[?ipAddress!=null]|[?contains(ipAddress, '$PUBLIC_IP')].[name]" \ -o tsv) # Fixed DNS label → idempotent, does not break an existing TLS certificate az network public-ip update \ --resource-group "$NODE_RG" \ --name "$PUBLIC_IP_NAME" \ --dns-name "$DNS_LABEL" \ --output none FQDN=$(az network public-ip show \ --resource-group "$NODE_RG" --name "$PUBLIC_IP_NAME" \ --query dnsSettings.fqdn -o tsv) log_info "FQDN: $FQDN" sed "s|MON_DOMAINE|$FQDN|g" ingress.yaml | kubectl apply -f - kubectl apply -f cluster-issuer.yaml # ========================================== log_info "STEP 5 — Post-deployment health checks" # ========================================== log_info "Waiting for all vigimeteo pods to be Ready (up to 3 min)..." if kubectl wait pod \ --all \ --namespace vigimeteo \ --for=condition=Ready \ --timeout=180s; then log_info "All pods are Ready ✅" else log_warn "Some pods are not yet Ready — current state:" kubectl get pods -n vigimeteo | tee -a "$LOG_FILE" log_warn "Deployment was submitted — verify manually." fi log_debug "Detailed pod status:" kubectl get pods -n vigimeteo -o wide 2>&1 | tee -a "$LOG_FILE" log_info "========================================" log_info "DEPLOYMENT COMPLETE" log_info " URL : https://$FQDN" log_info " DB : vigimeteo-db.vigimeteo.svc.cluster.local" log_info " Log : $LOG_FILE" log_info "========================================"