#!/usr/bin/env bash # ===================================================================== # extract-manifests.sh # # Estrae tutte le risorse namespaced dai namespace indicati, le ripulisce # con kubectl-neat (rimuove resourceVersion, uid, status, managedFields, # creationTimestamp, ecc.) e le salva organizzate per namespace/kind, # pronte per essere committate in un repo GitOps. # # Uso: # ./extract-manifests.sh [opzioni] ... # ./extract-manifests.sh [opzioni] --namespaces-file namespaces.txt # # Opzioni: # -o, --output Directory di output (default: ./export) # -s, --include-secrets Include anche i Secret (in CHIARO, base64 non # cifrato — vedi warning qui sotto). Di default # i Secret vengono SALTATI per sicurezza. # -f, --namespaces-file File con un namespace per riga. Righe vuote e # righe che iniziano con # vengono ignorate. # -k, --kinds Lista custom di kind separati da virgola, # al posto della discovery automatica # (es. "deployment,service,httproute") # -h, --help Mostra questo help # # Esempi: # ./extract-manifests.sh monitoring minio idcidp-dev # ./extract-manifests.sh -o ./gitops-repo/imported -s monitoring # ./extract-manifests.sh -k "httproute,gateway" nginx-gateway # ./extract-manifests.sh -f ./namespaces.txt -o ./gitops-repo/imported # # Prerequisiti: # - kubectl configurato e puntato al cluster corretto # - kubectl-neat installato (kubectl krew install neat) # https://github.com/itaysk/kubectl-neat # ===================================================================== set -euo pipefail # --------------------------------------------------------------------- # Default # --------------------------------------------------------------------- OUTPUT_DIR="./export" INCLUDE_SECRETS="false" CUSTOM_KINDS="" NAMESPACES_FILE="" # Kind che non ha senso portare in un repo GitOps: generati/gestiti # automaticamente da controller, non sono mai "desired state" da # dichiarare a mano. EXCLUDED_KINDS="events pods replicasets endpoints endpointslices controllerrevisions" usage() { grep '^#' "$0" | sed -e 's/^#//' -e 's/^ //' exit 0 } load_namespaces_from_file() { local file_path="$1" if [[ ! -f "$file_path" ]]; then echo "Errore: file namespace non trovato: $file_path" >&2 exit 1 fi while IFS= read -r line || [[ -n "$line" ]]; do line="${line%$'\r'}" [[ -z "$line" ]] && continue [[ "$line" =~ ^[[:space:]]*# ]] && continue line="${line#"${line%%[![:space:]]*}"}" line="${line%"${line##*[![:space:]]}"}" [[ -z "$line" ]] && continue NAMESPACES+=("$line") done < "$file_path" } # --------------------------------------------------------------------- # Parsing argomenti # --------------------------------------------------------------------- NAMESPACES=() while [[ $# -gt 0 ]]; do case "$1" in -o|--output) OUTPUT_DIR="$2" shift 2 ;; -s|--include-secrets) INCLUDE_SECRETS="true" shift ;; -f|--namespaces-file) NAMESPACES_FILE="$2" shift 2 ;; -k|--kinds) CUSTOM_KINDS="$2" shift 2 ;; -h|--help) usage ;; -*) echo "Opzione sconosciuta: $1" >&2 exit 1 ;; *) NAMESPACES+=("$1") shift ;; esac done if [[ -n "$NAMESPACES_FILE" ]]; then load_namespaces_from_file "$NAMESPACES_FILE" fi if [[ ${#NAMESPACES[@]} -eq 0 ]]; then echo "Errore: specifica almeno un namespace o usa --namespaces-file." >&2 echo "Uso: $0 [opzioni] ..." >&2 echo " o: $0 [opzioni] --namespaces-file namespaces.txt" >&2 exit 1 fi # --------------------------------------------------------------------- # Prerequisiti # --------------------------------------------------------------------- if ! command -v kubectl >/dev/null 2>&1; then echo "Errore: kubectl non trovato nel PATH." >&2 exit 1 fi if ! kubectl neat --help >/dev/null 2>&1; then echo "Errore: plugin kubectl-neat non trovato." >&2 echo "Installa con: kubectl krew install neat" >&2 echo "(krew: https://krew.sigs.k8s.io/docs/user-guide/setup/install/)" >&2 exit 1 fi mkdir -p "$OUTPUT_DIR" echo "==> Output directory: $OUTPUT_DIR" echo "==> Namespace da processare: ${NAMESPACES[*]}" echo "==> Include Secret: $INCLUDE_SECRETS" echo "" if [[ "$INCLUDE_SECRETS" == "true" ]]; then echo "########################################################" echo "# ATTENZIONE: i Secret verranno esportati in CHIARO #" echo "# (base64, NON cifrato). Non committarli in Git così #" echo "# come sono. Usa Sealed Secrets, SOPS o External Secrets #" echo "# Operator prima di aggiungerli al repo. #" echo "########################################################" echo "" fi # --------------------------------------------------------------------- # Determina i kind namespaced da processare # --------------------------------------------------------------------- get_kinds() { if [[ -n "$CUSTOM_KINDS" ]]; then echo "$CUSTOM_KINDS" | tr ',' '\n' return fi # Discovery automatica: tutti i kind namespaced supportati dal cluster # (copre anche le CRD installate, es. httproute, podmonitor, cluster # CNPG, ecc.), escludendo quelli in EXCLUDED_KINDS. kubectl api-resources --namespaced=true --verbs=list -o name 2>/dev/null \ | cut -d. -f1 \ | sort -u \ | while read -r kind; do skip="false" for excl in $EXCLUDED_KINDS; do [[ "$kind" == "$excl" ]] && skip="true" && break done [[ "$skip" == "false" ]] && echo "$kind" done } KINDS=$(get_kinds) # --------------------------------------------------------------------- # Estrazione # --------------------------------------------------------------------- TOTAL_EXPORTED=0 TOTAL_EMPTY=0 TOTAL_ERRORS=0 for ns in "${NAMESPACES[@]}"; do echo "=== Namespace: $ns ===" if ! kubectl get namespace "$ns" >/dev/null 2>&1; then echo " ! Namespace '$ns' non trovato, salto." >&2 continue fi ns_dir="${OUTPUT_DIR}/${ns}" mkdir -p "$ns_dir" while IFS= read -r kind; do [[ -z "$kind" ]] && continue # Salta i secret a meno che non richiesti esplicitamente if [[ "$kind" == "secrets" || "$kind" == "secret" ]] && [[ "$INCLUDE_SECRETS" != "true" ]]; then continue fi # Conta quante risorse di questo kind esistono nel namespace, per # evitare di scrivere file vuoti/inutili count=$(kubectl get "$kind" -n "$ns" --no-headers 2>/dev/null | wc -l | tr -d ' ') if [[ "$count" -eq 0 ]]; then TOTAL_EMPTY=$((TOTAL_EMPTY + 1)) continue fi out_file="${ns_dir}/${kind}.yaml" if kubectl get "$kind" -n "$ns" -o yaml 2>/dev/null | kubectl neat > "$out_file" 2>/dev/null; then # kubectl neat su una List vuota/malformata può comunque produrre # un file quasi-vuoto: verifichiamo che contenga davvero "kind:" if grep -q "^kind:" "$out_file" 2>/dev/null || grep -q "^items:" "$out_file" 2>/dev/null; then echo " + ${kind} (${count})" TOTAL_EXPORTED=$((TOTAL_EXPORTED + 1)) else rm -f "$out_file" fi else echo " ! Errore esportando ${kind}" >&2 rm -f "$out_file" TOTAL_ERRORS=$((TOTAL_ERRORS + 1)) fi done <<< "$KINDS" echo "" done echo "=== Riepilogo ===" echo "Risorse esportate: $TOTAL_EXPORTED" echo "Kind vuoti/saltati: $TOTAL_EMPTY" echo "Errori: $TOTAL_ERRORS" echo "" echo "Output in: $OUTPUT_DIR" echo "" echo "Prossimi passi consigliati:" echo " 1. Rivedi manualmente ogni file: alcuni campi (es. clusterIP," echo " nodePort, annotazioni iniettate da controller/webhook) vanno" echo " rimossi a mano perché non fanno parte del 'desired state'." echo " 2. Se hai esportato Secret, cifrali (Sealed Secrets / SOPS) prima" echo " di committarli." echo " 3. Riorganizza i file nella struttura del repo GitOps" echo " (infrastructure/ vs apps/, vedi README del repo)."