Initial commit — LoginMaster tenant deployment toolkit

Toolkit per deployare/aggiornare un tenant LoginMaster su qualsiasi Kubernetes
(EKS/AKS/DOKS/Scaleway/vSphere/...). Contiene:

- deploy.sh: bootstrap di un nuovo tenant (idempotente, re-run protection,
  storage class auto-rilevata, prompt separati api/admin tag, generazione
  segreti crittografici via openssl rand).
- update.sh: rolling update zero-downtime con tag api/admin separati, rollback
  hint via 'kubectl rollout undo', riapplicazione opzionale del ConfigMap.
- templates/: 8 manifest parametrici (envsubst): namespace, cert-manager TLS
  Mongo, NetworkPolicy intra-namespace, ConfigMap, MongoDB StatefulSet 3 repliche
  con TLS interno + initContainer per keyfile/PEM, tenant-api Deployment 2 repliche
  con CA validation, tenant-admin, ingress nginx + Let's Encrypt.

Sicurezza: TLS interno Mongo (cert-manager CA self-signed 10y), keyFile per
auth replica set, password client mai in argv, NetworkPolicy che isola il
tenant, pod Mongo non-root (uid 999) con initContainer come root per i file
runtime in tmpfs.
This commit is contained in:
Luca
2026-05-06 11:44:04 +02:00
commit 468d4562c7
12 changed files with 1557 additions and 0 deletions
Executable
+208
View File
@@ -0,0 +1,208 @@
#!/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."