#!/usr/bin/env bash # ============================================================================= # update.sh — Aggiorna un tenant LoginMaster esistente. # # 1) Rolling update zero-downtime di api-tenant e/o admin-tenant ad un nuovo tag. # 2) Opzionalmente riapplica il ConfigMap dal template (necessario se l'API ha # iniziato a leggere nuove env var) e fa rollout restart per propagarle. # 3) Su errore stampa il comando di rollback (kubectl rollout undo). # # Uso: # ./update.sh # interattivo (chiede tutto) # ./update.sh prod-1.3.8 # tag uguale per api+admin # ./update.sh prod-1.3.8 prod-1.3.7 # tag api + tag admin separati # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TEMPLATES_DIR="$SCRIPT_DIR/templates" REGISTRY="hub.codebaker.it/loginmaster-tenant" DEF_NAMESPACE="loginmaster-tenant" # ----------------------------------------------------------------------------- # Helper # ----------------------------------------------------------------------------- BLUE='\033[0;34m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m' log() { echo -e "\n${BLUE}==>${NC} $*"; } ok() { echo -e "${GREEN}✓${NC} $*"; } warn() { echo -e "${YELLOW}!${NC} $*"; } err() { echo -e "${RED}ERRORE:${NC} $*" >&2; exit 1; } ask() { local prompt="$1" def="${2:-}" input if [[ -n "$def" ]]; then read -r -p "$(echo -e "${CYAN}?${NC} ${prompt} [${def}]: ")" input REPLY="${input:-$def}" else read -r -p "$(echo -e "${CYAN}?${NC} ${prompt}: ")" input REPLY="$input" fi } confirm() { local prompt="$1" def="${2:-N}" input suffix [[ "$def" == "Y" ]] && suffix="[Y/n]" || suffix="[y/N]" read -r -p "$(echo -e "${CYAN}?${NC} ${prompt} ${suffix}: ")" input input="${input:-$def}" [[ "$input" =~ ^[yY]([eE][sS])?$ ]] } kc() { kubectl --context "$CONTEXT" -n "$NAMESPACE" "$@"; } # ----------------------------------------------------------------------------- # Parametri # ----------------------------------------------------------------------------- log "Parametri aggiornamento" CURRENT_CTX=$(kubectl config current-context 2>/dev/null || echo "") echo "Context kubectl disponibili:" kubectl config get-contexts -o name | sed 's/^/ - /' echo ask "Context da usare" "$CURRENT_CTX" CONTEXT="$REPLY" kubectl --context "$CONTEXT" cluster-info >/dev/null 2>&1 \ || err "Impossibile contattare il cluster sul context '$CONTEXT'" ok "Connesso a $CONTEXT" ask "Namespace del tenant" "$DEF_NAMESPACE" NAMESPACE="$REPLY" # Verifica che il namespace esista e abbia i deployment kc get deployment tenant-api >/dev/null 2>&1 \ || err "Deployment tenant-api non trovato nel namespace '$NAMESPACE'" # Mostra stato attuale log "Stato attuale" CURRENT_API=$(kc get deployment tenant-api -o jsonpath='{.spec.template.spec.containers[0].image}') CURRENT_ADMIN=$(kc get deployment tenant-admin -o jsonpath='{.spec.template.spec.containers[0].image}') echo " tenant-api: $CURRENT_API" echo " tenant-admin: $CURRENT_ADMIN" echo # Tag da argomenti posizionali o prompt. Permettiamo tag separati perché # api-tenant e admin-tenant possono essere a versioni diverse. if [[ -n "${1:-}" && -n "${2:-}" ]]; then NEW_TAG_API="$1" NEW_TAG_ADMIN="$2" elif [[ -n "${1:-}" ]]; then NEW_TAG_API="$1" NEW_TAG_ADMIN="$1" else # Default = tag attuali estratti dal cluster (rimangono se l'utente conferma) CUR_API_TAG="${CURRENT_API##*:}" CUR_ADMIN_TAG="${CURRENT_ADMIN##*:}" ask "Nuovo tag api-tenant" "$CUR_API_TAG" NEW_TAG_API="$REPLY" ask "Nuovo tag admin-tenant" "$CUR_ADMIN_TAG" NEW_TAG_ADMIN="$REPLY" fi [[ -z "$NEW_TAG_API" ]] && err "Tag api-tenant obbligatorio" [[ -z "$NEW_TAG_ADMIN" ]] && err "Tag admin-tenant obbligatorio" NEW_API="${REGISTRY}/api-tenant:${NEW_TAG_API}" NEW_ADMIN="${REGISTRY}/admin-tenant:${NEW_TAG_ADMIN}" # Scelta componenti UPDATE_API=true UPDATE_ADMIN=true if ! confirm "Aggiornare tenant-api a ${NEW_API}?" "Y"; then UPDATE_API=false fi if ! confirm "Aggiornare tenant-admin a ${NEW_ADMIN}?" "Y"; then UPDATE_ADMIN=false fi $UPDATE_API || $UPDATE_ADMIN || { echo "Nessun aggiornamento selezionato."; exit 0; } # Riepilogo log "Riepilogo aggiornamento" echo " Context: $CONTEXT" echo " Namespace: $NAMESPACE" $UPDATE_API && echo " tenant-api: $CURRENT_API --> $NEW_API" $UPDATE_ADMIN && echo " tenant-admin: $CURRENT_ADMIN --> $NEW_ADMIN" echo confirm "Procedere?" "N" || { echo "Annullato."; exit 0; } # ----------------------------------------------------------------------------- # Aggiornamento # ----------------------------------------------------------------------------- # Suggerisce il rollback se lo script fallisce a metà strada (rollout fail, kubectl # error, etc.). Viene disarmato solo dopo il rollout completato con successo. print_rollback_hint() { echo warn "Aggiornamento interrotto. Per riportare i Deployment alla revisione precedente:" $UPDATE_API && echo " kubectl --context '$CONTEXT' -n '$NAMESPACE' rollout undo deployment/tenant-api" $UPDATE_ADMIN && echo " kubectl --context '$CONTEXT' -n '$NAMESPACE' rollout undo deployment/tenant-admin" echo " (kubectl rollout undo usa la revision history del Deployment, non richiede il tag precedente)" } trap print_rollback_hint ERR if $UPDATE_API; then log "Aggiorno tenant-api" kc set image deployment/tenant-api tenant-api="$NEW_API" kc rollout status deployment/tenant-api --timeout=300s ok "tenant-api aggiornato a $NEW_TAG_API" fi if $UPDATE_ADMIN; then log "Aggiorno tenant-admin" kc set image deployment/tenant-admin tenant-admin="$NEW_ADMIN" kc rollout status deployment/tenant-admin --timeout=300s ok "tenant-admin aggiornato a $NEW_TAG_ADMIN" fi trap - ERR # ----------------------------------------------------------------------------- # ConfigMap (opzionale — per propagare nuove env var introdotte dal template) # ----------------------------------------------------------------------------- # Le env var iniettate via envFrom configMapRef NON si aggiornano automaticamente # quando il ConfigMap cambia: i pod vanno riavviati. Questo blocco riapplica il # template usando i valori CORRENTI dal cluster (preserva le scelte fatte al # deploy iniziale o tramite kubectl edit) e fa rollout restart. if confirm "Riapplicare anche il ConfigMap dal template (necessario se l'API legge nuove env var)?" "N"; then command -v envsubst >/dev/null || err "envsubst non trovato (brew install gettext && brew link gettext --force)" log "Leggo i valori correnti dal ConfigMap tenant-api-config" cm_get() { kc get configmap tenant-api-config -o jsonpath="{.data.$1}" 2>/dev/null || true; } NAMESPACE="$NAMESPACE" \ LOGINMASTER_API_URL=$(cm_get LOGINMASTER_API_URL) \ ADMIN_ALLOWED_ORIGIN=$(cm_get ADMIN_ALLOWED_ORIGIN) \ TENANT_ADMIN_URL=$(cm_get TENANT_ADMIN_URL) \ SMTP_HOST=$(cm_get SMTP_HOST) \ SMTP_PORT=$(cm_get SMTP_PORT) \ SMTP_SECURE=$(cm_get SMTP_SECURE) \ SMTP_USER=$(cm_get SMTP_USER) \ EMAIL_FROM=$(cm_get EMAIL_FROM) \ envsubst < "$TEMPLATES_DIR/03-configmap.yaml" \ | kc apply -f - ok "ConfigMap riapplicato dal template" log "Restart deployment/tenant-api (necessario per propagare env var)" kc rollout restart deployment/tenant-api kc rollout status deployment/tenant-api --timeout=300s ok "tenant-api riavviato con il nuovo ConfigMap" fi # ----------------------------------------------------------------------------- # Verifica finale # ----------------------------------------------------------------------------- log "Verifica finale" echo kc get pods -l 'app in (tenant-api, tenant-admin)' echo echo "Immagini attuali:" echo " tenant-api: $(kc get deployment tenant-api -o jsonpath='{.spec.template.spec.containers[0].image}')" echo " tenant-admin: $(kc get deployment tenant-admin -o jsonpath='{.spec.template.spec.containers[0].image}')" echo echo "Per fare rollback alla revisione precedente:" $UPDATE_API && echo " kubectl --context '$CONTEXT' -n '$NAMESPACE' rollout undo deployment/tenant-api" $UPDATE_ADMIN && echo " kubectl --context '$CONTEXT' -n '$NAMESPACE' rollout undo deployment/tenant-admin" echo ok "Aggiornamento completato."