From f8ffa9e0657482967022253640735d6583f193cf Mon Sep 17 00:00:00 2001 From: alessandro barucci Date: Sat, 12 Sep 2026 19:21:41 +0200 Subject: [PATCH] gitops --- add-on/gitea-test.sh | 100 ++++++ .../clusters/idc/namespace-B/application.yaml | 24 ++ add-on/installaRancher.sh | 31 +- add-on/minio-console.yaml | 136 ++++++++ add-on/sonaquebe.txt | 7 + gitops/README.md | 85 +++++ gitops/bootstrap/00-namespace.yaml | 6 + gitops/bootstrap/01-appproject.yaml | 37 +++ gitops/bootstrap/02-root.yaml | 40 +++ .../clusters/idc/namespace-A/application.yaml | 24 ++ .../idc/namespace-A/manifests/deployment.yaml | 37 +++ .../clusters/idc/namespace-B/application.yaml | 24 ++ .../idc/namespace-B/manifests/deployment.yaml | 32 ++ .../clusters/idc/namespace-C/application.yaml | 13 + .../idc/namespace-C/manifests/deployment.yaml | 32 ++ gitops/extract-manifests.sh | 256 +++++++++++++++ gitops/generate-fleet-helmops-from-helm.sh | 305 ++++++++++++++++++ pipeline/add-cert-manager-hostalias.sh | 75 +++++ pipeline/addlistener.sh | 14 + 19 files changed, 1275 insertions(+), 3 deletions(-) create mode 100644 add-on/gitea-test.sh create mode 100644 add-on/gitops/gitops-repo/clusters/idc/namespace-B/application.yaml create mode 100644 add-on/minio-console.yaml create mode 100644 gitops/README.md create mode 100644 gitops/bootstrap/00-namespace.yaml create mode 100644 gitops/bootstrap/01-appproject.yaml create mode 100644 gitops/bootstrap/02-root.yaml create mode 100644 gitops/clusters/idc/namespace-A/application.yaml create mode 100644 gitops/clusters/idc/namespace-A/manifests/deployment.yaml create mode 100644 gitops/clusters/idc/namespace-B/application.yaml create mode 100644 gitops/clusters/idc/namespace-B/manifests/deployment.yaml create mode 100644 gitops/clusters/idc/namespace-C/application.yaml create mode 100644 gitops/clusters/idc/namespace-C/manifests/deployment.yaml create mode 100644 gitops/extract-manifests.sh create mode 100644 gitops/generate-fleet-helmops-from-helm.sh create mode 100644 pipeline/add-cert-manager-hostalias.sh diff --git a/add-on/gitea-test.sh b/add-on/gitea-test.sh new file mode 100644 index 0000000..0c048fe --- /dev/null +++ b/add-on/gitea-test.sh @@ -0,0 +1,100 @@ + +kubectl cnpg psql pg-devops -n devops + +CREATE DATABASE giteadbtest; +CREATE USER giteatest WITH PASSWORD 'KAYQE1QA7uwUZ8uI'; +GRANT ALL PRIVILEGES ON DATABASE giteadbtest TO giteatest; +ALTER DATABASE giteadbtest OWNER TO giteatest; + +helm repo add gitea https://dl.gitea.io/charts/ +helm repo update + + +kubectl create namespace gitea + +cat <valuestest.yaml - +replicaCount: 1 + +image: + repository: gitea/gitea + tag: 1.25.4-rootless + pullPolicy: IfNotPresent + +strategy: + type: Recreate + +service: + http: + type: ClusterIP + port: 3000 + ssh: + type: ClusterIP + port: 22 + +redis-cluster: + enabled: false + +redis: + enabled: false + +ingress: + enabled: false + +persistence: + enabled: true + storageClass: csi-rbdfs-sc + size: 10Gi + +postgresql: + enabled: false + +postgresql-ha: + enabled: false + +gitea: + admin: + username: gitadmin + password: KAYQE1QA7uwUZ8uI + email: gitadmin@italiadatacenter.com + + config: + database: + DB_TYPE: postgres + HOST: pg-devops-rw.devops.svc:5432 + NAME: giteadbtest + USER: giteatest + PASSWD: KAYQE1QA7uwUZ8uI + SSL_MODE: disable + + server: + ROOT_URL: https://git2.pigreco66.it/ + SSH_DOMAIN: git2.pigreco66.it + SSH_PORT: 22 + + security: + INSTALL_LOCK: true + +EOF + +helm upgrade --install gitea gitea-charts/gitea --namespace gitea -f values.yaml + + +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: gitea + namespace: gitea +spec: + hostnames: + - git.italiadatacenter.com + parentRefs: + - name: main-gateway + namespace: nginx-gateway + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: gitea-http + port: 3000 diff --git a/add-on/gitops/gitops-repo/clusters/idc/namespace-B/application.yaml b/add-on/gitops/gitops-repo/clusters/idc/namespace-B/application.yaml new file mode 100644 index 0000000..d80d564 --- /dev/null +++ b/add-on/gitops/gitops-repo/clusters/idc/namespace-B/application.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: namespace-b + namespace: argocd +spec: + project: gitops-3ns + + source: + repoURL: "https://github.com//gitops-repo.git" + targetRevision: main + path: gitops/clusters/idc/namespace-B/manifests + + destination: + server: "https://kubernetes.default.svc" + namespace: namespace-B + + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/add-on/installaRancher.sh b/add-on/installaRancher.sh index ab93a02..b1d742a 100644 --- a/add-on/installaRancher.sh +++ b/add-on/installaRancher.sh @@ -1,7 +1,32 @@ helm repo add rancher-stable https://releases.rancher.com/server-charts/stable kubectl create namespace cattle-system -helm install rancher rancher-stable/rancher \ +helm install rancher rancher-stable/rancher --namespace cattle-system --set hostname=idc.internal --set bootstrapPassword=.... --set ingress.tls.source=rancher + + +helm upgrade rancher rancher-stable/rancher \ --namespace cattle-system \ - --set hostname=k8s.italiadatacenter.com \ - --set bootstrapPassword=admin + --reuse-values \ + --set hostname=idc.internal \ + --set bootstrapPassword=KAYQE1QA7uwUZ8uI \ + --set ingress.enabled=false \ + --set ingress.tls.source=rancher + +Poc-25_sts + +POC-25_sts + +vi sistemarancher.sh +# 1. recupera il ClusterIP del service "rancher" già creato dal chart +CLUSTERIP=$(kubectl get svc -n cattle-system rancher -o jsonpath='{.spec.clusterIP}') + +# 2. patch del deployment per aggiungere hostAliases +kubectl patch deployment rancher -n cattle-system --type='json' \ + -p="[{\"op\":\"add\",\"path\":\"/spec/template/spec/hostAliases\",\"value\":[{\"ip\":\"$CLUSTERIP\",\"hostnames\":[\"idc.internal\"]}]}]" + +# 3. crea il service NodePort per l'accesso dal browser +kubectl expose deployment rancher -n cattle-system --type=NodePort --port=443 --target-port=443 --name=rancher-nodeport + +# 4. riavvia e segui i log +kubectl rollout restart deployment rancher -n cattle-system +kubectl -n cattle-system logs -l app=rancher -f \ No newline at end of file diff --git a/add-on/minio-console.yaml b/add-on/minio-console.yaml new file mode 100644 index 0000000..8901c7e --- /dev/null +++ b/add-on/minio-console.yaml @@ -0,0 +1,136 @@ +apiVersion: v1 +kind: Secret +metadata: + name: opens3-console-secrets + namespace: minio +type: Opaque +stringData: + # Endpoint S3 del server MinIO a cui la console si collega. + # Cambialo con l'URL reale del tuo MinIO (Service interno o esterno). + CONSOLE_MINIO_SERVER: "http://minio.minio.svc.cluster.local:9000" + + # Passphrase e salt usati da opens3/console per cifrare le sessioni + # (PBKDF2). Generane di robusti, es.: + # openssl rand -base64 32 + CONSOLE_PBKDF_PASSPHRASE: "OHB576kn9SS4JdD7qG4+hyjRpa353HQqxPQ22z2Do0w=" + CONSOLE_PBKDF_SALT: "OBHOWF2INklmrtmyI+qNEb3dbV0yz9ZAxiJJCcC6GYw=" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: opens3-console + namespace: minio + labels: + app.kubernetes.io/name: opens3-console + app.kubernetes.io/instance: opens3-console +spec: + replicas: 1 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: opens3-console + app.kubernetes.io/instance: opens3-console + template: + metadata: + labels: + app.kubernetes.io/name: opens3-console + app.kubernetes.io/instance: opens3-console + spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: console + image: opens3/console:latest + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9090 + protocol: TCP + envFrom: + - secretRef: + name: opens3-console-secrets + # Se il MinIO target usa certificati self-signed, decommenta: + # env: + # - name: CONSOLE_MINIO_TLS_SKIP_VERIFICATION + # value: "on" + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 3 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + memory: 256Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] +--- +apiVersion: v1 +kind: Service +metadata: + name: opens3-console + namespace: minio + labels: + app.kubernetes.io/name: opens3-console + app.kubernetes.io/instance: opens3-console +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: opens3-console + app.kubernetes.io/instance: opens3-console + ports: + - name: http + port: 80 + targetPort: http + protocol: TCP +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httproute-minio-direct +spec: + hostnames: + - idcidp.pigreco66.it + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: main-gateway + namespace: nginx-gateway + rules: + - backendRefs: + - group: "" + kind: Service + name: opens3-console + port: 80 + weight: 1 + filters: + - responseHeaderModifier: + remove: + - X-Frame-Options + - Content-Security-Policy + set: + - name: X-Frame-Options + value: SAMEORIGIN + - name: Content-Security-Policy + value: frame-ancestors 'self' + type: ResponseHeaderModifier + matches: + - path: + type: PathPrefix + value: /minio-console \ No newline at end of file diff --git a/add-on/sonaquebe.txt b/add-on/sonaquebe.txt index bc7e4c1..8b6643a 100644 --- a/add-on/sonaquebe.txt +++ b/add-on/sonaquebe.txt @@ -2,6 +2,13 @@ helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube helm repo update +helm pull sonarqube/sonarqube --version 2026.1.0 + +helm registry login harbor.italiadatacenter.com -u admin +(pwd: KAYQE1QA7uwUZ8uI) + +helm push sonarqube-2026.1.0.tgz oci://harbor.italiadatacenter.com/italiadatacenter + ########### creazione ns e secret db ################ kubectl create namespace sonarqube diff --git a/gitops/README.md b/gitops/README.md new file mode 100644 index 0000000..bde11c4 --- /dev/null +++ b/gitops/README.md @@ -0,0 +1,85 @@ +# gitops-repo - esempio completo (3 namespace, Fleet su RKE2) + +Repo di esempio pronto da usare come base: GitOps limitato a +namespace-A, namespace-B, namespace-C, con Fleet come motore di sync. + +## Struttura + +``` +. +├── bootstrap/ <- file legacy Argo CD (non usati con Fleet) +│ ├── 00-namespace.yaml +│ ├── 01-appproject.yaml +│ └── 02-root.yaml +│ +└── clusters/ + └── idc/ <- nome cluster/repository scope attuale + ├── namespace-A/ + │ ├── application.yaml <- da migrare a Fleet GitRepo + │ └── manifests/ + │ └── deployment.yaml + ├── namespace-B/ + │ ├── application.yaml <- da migrare a Fleet GitRepo + │ └── manifests/ + │ └── deployment.yaml + └── namespace-C/ + ├── application.yaml <- gia convertito a Fleet GitRepo + └── manifests/ + └── deployment.yaml +``` + +## Cosa applichi a mano con Fleet + +- Applichi a mano i manifest Fleet di tipo GitRepo (uno per namespace o uno aggregato). +- I file in clusters/idc/namespace-*/manifests vengono sincronizzati automaticamente da Fleet. +- I file in bootstrap sono legacy Argo CD: con Fleet installato, non sono necessari. + +## Bootstrap Fleet - passo per passo + +### 1. Verifica Fleet + +```bash +kubectl -n cattle-fleet-system get pods +kubectl get crd gitrepos.fleet.cattle.io +``` + +### 2. Applica il GitRepo + +Esempio con namespace-C: + +```bash +kubectl apply -f clusters/idc/namespace-C/application.yaml +``` + +### 3. Verifica stato Fleet + +```bash +kubectl -n fleet-local get gitrepo +kubectl -n cattle-fleet-system get bundles +kubectl -n cattle-fleet-system get bundledeployments +``` + +## Repo privato GitHub + +Se il repo e privato, configura le credenziali per Fleet (Secret nel namespace Fleet usato, tipicamente fleet-local o fleet-default). + +## Prima di usare questo repo + +Sostituisci ovunque compare: +- -> org/utente reale del tuo repo Git +- i deployment di esempio -> i tuoi manifest reali + +## Migrazione consigliata (A e B) + +Attualmente solo namespace-C e gia in formato Fleet. +Per allineare tutto: + +1. Converti clusters/idc/namespace-A/application.yaml in GitRepo Fleet. +2. Converti clusters/idc/namespace-B/application.yaml in GitRepo Fleet. +3. Applica i due manifest e verifica bundle/bundledeployments. + +## Estendere a un quarto namespace in futuro + +1. Crea clusters/idc/namespace-D/manifests con i tuoi YAML. +2. Crea clusters/idc/namespace-D/application.yaml in formato Fleet GitRepo. +3. Applica il manifest e verifica la generazione dei bundle. diff --git a/gitops/bootstrap/00-namespace.yaml b/gitops/bootstrap/00-namespace.yaml new file mode 100644 index 0000000..9bf6b3a --- /dev/null +++ b/gitops/bootstrap/00-namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: argocd + labels: + app.kubernetes.io/name: argocd diff --git a/gitops/bootstrap/01-appproject.yaml b/gitops/bootstrap/01-appproject.yaml new file mode 100644 index 0000000..89160bd --- /dev/null +++ b/gitops/bootstrap/01-appproject.yaml @@ -0,0 +1,37 @@ +# ===================================================================== +# AppProject con perimetro ristretto a namespace-A, namespace-B, +# namespace-C. Qualunque Application che referenzi questo project e +# provi a scrivere in un namespace diverso da questi (+ argocd, per le +# risorse interne) viene rifiutata da Argo CD stesso. +# ===================================================================== +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: gitops-3ns + namespace: argocd +spec: + description: "GitOps limitato a namespace-A, namespace-B, namespace-C" + + sourceRepos: + - "https://github.com//gitops-repo.git" + + destinations: + - namespace: argocd + server: "https://kubernetes.default.svc" + - namespace: namespace-A + server: "https://kubernetes.default.svc" + - namespace: namespace-B + server: "https://kubernetes.default.svc" + - namespace: namespace-C + server: "https://kubernetes.default.svc" + + clusterResourceWhitelist: + - group: "" + kind: Namespace + + namespaceResourceWhitelist: + - group: "*" + kind: "*" + + orphanedResources: + warn: true diff --git a/gitops/bootstrap/02-root.yaml b/gitops/bootstrap/02-root.yaml new file mode 100644 index 0000000..e22efb6 --- /dev/null +++ b/gitops/bootstrap/02-root.yaml @@ -0,0 +1,40 @@ +# ===================================================================== +# Root "app of apps": punta a gitops/clusters/idc/ nel repo. Grazie a +# directory.recurse=true, scopre automaticamente ogni application.yaml +# trovato dentro namespace-A/, namespace-B/, namespace-C/ e li applica +# come proprie Application figlie. +# ===================================================================== +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: root-3ns + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: gitops-3ns + + source: + repoURL: "https://github.com//gitops-repo.git" + targetRevision: main + path: gitops/clusters/idc + directory: + recurse: true + + destination: + server: "https://kubernetes.default.svc" + namespace: argocd + + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + retry: + limit: 5 + backoff: + duration: 10s + factor: 2 + maxDuration: 3m diff --git a/gitops/clusters/idc/namespace-A/application.yaml b/gitops/clusters/idc/namespace-A/application.yaml new file mode 100644 index 0000000..63ad598 --- /dev/null +++ b/gitops/clusters/idc/namespace-A/application.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: namespace-a + namespace: argocd +spec: + project: gitops-3ns + + source: + repoURL: "https://github.com//gitops-repo.git" + targetRevision: main + path: gitops/clusters/idc/namespace-A/manifests + + destination: + server: "https://kubernetes.default.svc" + namespace: namespace-A + + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/gitops/clusters/idc/namespace-A/manifests/deployment.yaml b/gitops/clusters/idc/namespace-A/manifests/deployment.yaml new file mode 100644 index 0000000..0c014d9 --- /dev/null +++ b/gitops/clusters/idc/namespace-A/manifests/deployment.yaml @@ -0,0 +1,37 @@ +# ===================================================================== +# Esempio di manifest reale — sostituiscilo con le tue risorse +# effettive. Ogni file .yaml in questa cartella viene applicato +# automaticamente da Argo CD (Application "namespace-a"). +# ===================================================================== +apiVersion: apps/v1 +kind: Deployment +metadata: + name: esempio-app-a + namespace: namespace-A +spec: + replicas: 1 + selector: + matchLabels: + app: esempio-app-a + template: + metadata: + labels: + app: esempio-app-a + spec: + containers: + - name: esempio-app-a + image: nginx:1.27 + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: esempio-app-a + namespace: namespace-A +spec: + selector: + app: esempio-app-a + ports: + - port: 80 + targetPort: 80 diff --git a/gitops/clusters/idc/namespace-B/application.yaml b/gitops/clusters/idc/namespace-B/application.yaml new file mode 100644 index 0000000..d80d564 --- /dev/null +++ b/gitops/clusters/idc/namespace-B/application.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: namespace-b + namespace: argocd +spec: + project: gitops-3ns + + source: + repoURL: "https://github.com//gitops-repo.git" + targetRevision: main + path: gitops/clusters/idc/namespace-B/manifests + + destination: + server: "https://kubernetes.default.svc" + namespace: namespace-B + + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/gitops/clusters/idc/namespace-B/manifests/deployment.yaml b/gitops/clusters/idc/namespace-B/manifests/deployment.yaml new file mode 100644 index 0000000..43f9d93 --- /dev/null +++ b/gitops/clusters/idc/namespace-B/manifests/deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: esempio-app-b + namespace: namespace-B +spec: + replicas: 1 + selector: + matchLabels: + app: esempio-app-b + template: + metadata: + labels: + app: esempio-app-b + spec: + containers: + - name: esempio-app-b + image: nginx:1.27 + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: esempio-app-b + namespace: namespace-B +spec: + selector: + app: esempio-app-b + ports: + - port: 80 + targetPort: 80 diff --git a/gitops/clusters/idc/namespace-C/application.yaml b/gitops/clusters/idc/namespace-C/application.yaml new file mode 100644 index 0000000..c3c6c1e --- /dev/null +++ b/gitops/clusters/idc/namespace-C/application.yaml @@ -0,0 +1,13 @@ +apiVersion: fleet.cattle.io/v1alpha1 +kind: GitRepo +metadata: + name: namespace-c + namespace: fleet-local +spec: + repo: "https://github.com//gitops-repo.git" + branch: main + paths: + - gitops/clusters/idc/namespace-C/manifests + targets: + - clusterName: local + namespace: namespace-C diff --git a/gitops/clusters/idc/namespace-C/manifests/deployment.yaml b/gitops/clusters/idc/namespace-C/manifests/deployment.yaml new file mode 100644 index 0000000..87abef0 --- /dev/null +++ b/gitops/clusters/idc/namespace-C/manifests/deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: esempio-app-c + namespace: namespace-C +spec: + replicas: 1 + selector: + matchLabels: + app: esempio-app-c + template: + metadata: + labels: + app: esempio-app-c + spec: + containers: + - name: esempio-app-c + image: nginx:1.27 + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: esempio-app-c + namespace: namespace-C +spec: + selector: + app: esempio-app-c + ports: + - port: 80 + targetPort: 80 diff --git a/gitops/extract-manifests.sh b/gitops/extract-manifests.sh new file mode 100644 index 0000000..a76e0d7 --- /dev/null +++ b/gitops/extract-manifests.sh @@ -0,0 +1,256 @@ +#!/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)." diff --git a/gitops/generate-fleet-helmops-from-helm.sh b/gitops/generate-fleet-helmops-from-helm.sh new file mode 100644 index 0000000..70b3e2d --- /dev/null +++ b/gitops/generate-fleet-helmops-from-helm.sh @@ -0,0 +1,305 @@ +#!/usr/bin/env bash + +set -euo pipefail + +OUTPUT_DIR="./generated-helmops" +API_VERSION="fleet.cattle.io/v1alpha1" +RESOURCE_KIND="HelmOp" +VALUES_ARGS=() +WORKSPACE_NAMESPACE="fleet-local" + +usage() { + cat <<'EOF' +Usage: + ./generate-fleet-helmops-from-helm.sh [options] + +Description: + Legge le release presenti con `helm list -A`, recupera metadata e values + per ogni release e genera un file YAML per release in formato HelmOp-style. + +Options: + -o, --output-dir Directory di output (default: ./generated-helmops) + -w, --workspace-namespace + Namespace dove creare la risorsa HelmOp + (default: fleet-local) + --api-version apiVersion del manifest generato + (default: fleet.cattle.io/v1alpha1) + --kind kind del manifest generato (default: HelmOp) + -a, --all-values Usa `helm get values -a -o yaml` + -h, --help Mostra questo help + +Examples: + ./generate-fleet-helmops-from-helm.sh + ./generate-fleet-helmops-from-helm.sh -o ./clusters/idc/imported + ./generate-fleet-helmops-from-helm.sh -w fleet-default + ./generate-fleet-helmops-from-helm.sh --kind HelmChart --api-version helm.cattle.io/v1 +EOF +} + +require_cmd() { + local cmd="$1" + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Errore: comando richiesto non trovato: $cmd" >&2 + exit 1 + fi +} + +trim() { + local value="$1" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + printf '%s' "$value" +} + +safe_file_name() { + printf '%s' "$1" | tr '/\\:' '---' +} + +extract_yaml_scalar() { + local key="$1" + local content="$2" + + printf '%s\n' "$content" \ + | sed -n "s/^[[:space:]]*${key}:[[:space:]]*//p" \ + | head -n 1 \ + | sed 's/^"//; s/"$//; s/^\x27//; s/\x27$//' +} + +extract_first_source() { + local content="$1" + + awk ' + /^sources:/ { in_sources=1; next } + in_sources && /^[^[:space:]-]/ { exit } + in_sources && /^[[:space:]]*-[[:space:]]*/ { + sub(/^[[:space:]]*-[[:space:]]*/, "") + print + exit + } + ' <<< "$content" +} + +extract_chart_name_from_ref() { + local chart_ref="$1" + + if [[ "$chart_ref" =~ ^(.+)-([0-9][A-Za-z0-9.+_-]*)$ ]]; then + printf '%s' "${BASH_REMATCH[1]}" + else + printf '%s' "$chart_ref" + fi +} + +extract_chart_version_from_ref() { + local chart_ref="$1" + + if [[ "$chart_ref" =~ ^(.+)-([0-9][A-Za-z0-9.+_-]*)$ ]]; then + printf '%s' "${BASH_REMATCH[2]}" + fi +} + +find_repo_url_from_local_cache() { + local chart_name="$1" + local chart_version="$2" + local search_json="" + local repo_alias="" + + [[ -z "$chart_name" ]] && return 0 + + search_json="$(helm search repo "$chart_name" --versions -o json 2>/dev/null || true)" + [[ -z "$search_json" || "$search_json" == "[]" ]] && return 0 + + if [[ -n "$chart_version" ]]; then + repo_alias="$(jq -r --arg chart "$chart_name" --arg version "$chart_version" ' + map(select((.name | split("/") | last) == $chart and .version == $version)) + | .[0].name // empty + ' <<< "$search_json")" + fi + + if [[ -z "$repo_alias" ]]; then + repo_alias="$(jq -r --arg chart "$chart_name" ' + map(select((.name | split("/") | last) == $chart)) + | .[0].name // empty + ' <<< "$search_json")" + fi + + [[ -z "$repo_alias" ]] && return 0 + + repo_alias="${repo_alias%%/*}" + + helm repo list -o json 2>/dev/null \ + | jq -r --arg alias "$repo_alias" 'map(select(.name == $alias)) | .[0].url // empty' +} + +indent_file() { + local file_path="$1" + sed 's/^/ /' "$file_path" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + -o|--output-dir) + OUTPUT_DIR="$2" + shift 2 + ;; + -w|--workspace-namespace) + WORKSPACE_NAMESPACE="$2" + shift 2 + ;; + --api-version) + API_VERSION="$2" + shift 2 + ;; + --kind) + RESOURCE_KIND="$2" + shift 2 + ;; + -a|--all-values) + VALUES_ARGS=(-a) + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Opzione sconosciuta: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +require_cmd helm +require_cmd jq + +mkdir -p "$OUTPUT_DIR" + +releases_json="$(helm list -A -o json)" +release_count="$(jq 'length' <<< "$releases_json")" + +if [[ "$release_count" -eq 0 ]]; then + echo "Nessuna release Helm trovata." >&2 + exit 0 +fi + +echo "==> Release trovate: $release_count" +echo "==> Directory output: $OUTPUT_DIR" + +generated_count=0 +warning_count=0 + +while IFS= read -r release; do + name="$(jq -r '.name' <<< "$release")" + namespace="$(jq -r '.namespace' <<< "$release")" + chart_ref="$(jq -r '.chart // ""' <<< "$release")" + + metadata_yaml="$(helm get metadata "$name" -n "$namespace" -o yaml 2>/dev/null || true)" + if [[ -z "$metadata_yaml" ]]; then + echo "! Impossibile leggere metadata per ${namespace}/${name}, salto." >&2 + warning_count=$((warning_count + 1)) + continue + fi + + repo_url="$(trim "$(extract_yaml_scalar repo "$metadata_yaml")")" + if [[ -z "$repo_url" ]]; then + repo_url="$(trim "$(extract_yaml_scalar repository "$metadata_yaml")")" + fi + if [[ -z "$repo_url" ]]; then + repo_url="$(trim "$(extract_yaml_scalar repoURL "$metadata_yaml")")" + fi + if [[ -z "$repo_url" ]]; then + repo_url="$(trim "$(extract_first_source "$metadata_yaml")")" + fi + + chart_name="$(trim "$(extract_yaml_scalar chart "$metadata_yaml")")" + chart_version="$(trim "$(extract_yaml_scalar version "$metadata_yaml")")" + + if [[ -z "$chart_name" ]]; then + chart_name="$(extract_chart_name_from_ref "$chart_ref")" + fi + if [[ -z "$chart_version" ]]; then + chart_version="$(extract_chart_version_from_ref "$chart_ref")" + fi + + if [[ -z "$repo_url" ]]; then + repo_url="$(trim "$(find_repo_url_from_local_cache "$chart_name" "$chart_version")")" + fi + + if [[ -z "$repo_url" ]]; then + repo_url="REPO_URL_NOT_FOUND" + echo "! Repo URL non trovato in metadata o cache locale per ${namespace}/${name}" >&2 + warning_count=$((warning_count + 1)) + fi + + if [[ -z "$chart_name" ]]; then + chart_name="CHART_NAME_NOT_FOUND" + echo "! Chart name non trovato per ${namespace}/${name}" >&2 + warning_count=$((warning_count + 1)) + fi + + values_file="$(mktemp)" + if ! helm get values "$name" -n "$namespace" "${VALUES_ARGS[@]}" -o yaml > "$values_file" 2>/dev/null; then + printf '{}\n' > "$values_file" + echo "! Values non disponibili per ${namespace}/${name}, uso {}" >&2 + warning_count=$((warning_count + 1)) + fi + + if [[ ! -s "$values_file" ]]; then + printf '{}\n' > "$values_file" + fi + + values_compact="$(tr -d '[:space:]' < "$values_file")" + + ns_dir="${OUTPUT_DIR}/${namespace}" + mkdir -p "$ns_dir" + + safe_name="$(safe_file_name "$name")" + out_file="${ns_dir}/${safe_name}-helmop.yaml" + + cat > "$out_file" <> "$out_file" <> "$out_file" <> "$out_file" < +# +# Esempio: +# ./add-cert-manager-hostalias.sh main-gateway-nginx.nginx-gateway.svc.cluster.local +# ./add-cert-manager-hostalias.sh api.internal + +DEPLOYMENT_NAME="cert-manager" +DEPLOYMENT_NS="cert-manager" +TARGET_IP="10.43.37.118" +HOSTNAME_VAL="${1:-}" + +if [[ -z "$HOSTNAME_VAL" ]]; then + echo "Uso: $0 " >&2 + exit 1 +fi + +if ! command -v kubectl >/dev/null 2>&1; then + echo "Errore: kubectl non trovato nel PATH" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "Errore: jq non trovato nel PATH" >&2 + echo "Installa jq e riprova." >&2 + exit 1 +fi + +# Idempotenza: controlla solo il blocco hostAliases associato a TARGET_IP. +EXISTING_HOSTNAMES="$(kubectl get deployment "$DEPLOYMENT_NAME" -n "$DEPLOYMENT_NS" -o jsonpath="{.spec.template.spec.hostAliases[?(@.ip=='${TARGET_IP}')].hostnames[*]}" 2>/dev/null || true)" +if echo " $EXISTING_HOSTNAMES " | grep -Fq " $HOSTNAME_VAL "; then + echo "Hostname '$HOSTNAME_VAL' gia presente per IP ${TARGET_IP} in ${DEPLOYMENT_NS}/${DEPLOYMENT_NAME}. Nessuna modifica." + exit 0 +fi + +DEPLOY_JSON="$(kubectl get deployment "$DEPLOYMENT_NAME" -n "$DEPLOYMENT_NS" -o json)" + +UPDATED_HOST_ALIASES="$({ + echo "$DEPLOY_JSON" | jq -c --arg ip "$TARGET_IP" --arg hostname "$HOSTNAME_VAL" ' + (.spec.template.spec.hostAliases //= []) + | if any(.spec.template.spec.hostAliases[]?; .ip == $ip) then + .spec.template.spec.hostAliases |= map( + if .ip == $ip then + if ((.hostnames // []) | index($hostname)) then + . + else + .hostnames = ((.hostnames // []) + [$hostname]) + end + else + . + end + ) + else + .spec.template.spec.hostAliases += [{"ip": $ip, "hostnames": [$hostname]}] + end + | .spec.template.spec.hostAliases + ' +} )" + +PATCH_PAYLOAD="$(jq -cn --argjson hostAliases "$UPDATED_HOST_ALIASES" '{spec:{template:{spec:{hostAliases:$hostAliases}}}}')" + +kubectl patch deployment "$DEPLOYMENT_NAME" \ + -n "$DEPLOYMENT_NS" \ + --type=merge \ + -p "$PATCH_PAYLOAD" >/dev/null + +echo "Hostname aggiunto con successo." +echo " Deployment: ${DEPLOYMENT_NS}/${DEPLOYMENT_NAME}" +echo " IP : ${TARGET_IP}" +echo " Hostname : ${HOSTNAME_VAL}" diff --git a/pipeline/addlistener.sh b/pipeline/addlistener.sh index a3806f9..389ca07 100644 --- a/pipeline/addlistener.sh +++ b/pipeline/addlistener.sh @@ -35,6 +35,7 @@ fi # Per calcolare il token usa host pulito (senza schema e path) host_for_token="${endpoint#*://}" host_for_token="${host_for_token%%/*}" +host_no_port="${host_for_token%%:*}" token="${host_for_token%%.*}" if [[ -z "$token" ]]; then @@ -42,5 +43,18 @@ if [[ -z "$token" ]]; then exit 1 fi +# Se l'hostname non e nel dominio *.italiadatacenter.com, aggiungilo anche a cert-manager hostAliases. +if [[ "$host_no_port" != *.italiadatacenter.com ]]; then + HOSTALIAS_SCRIPT="/root/work/pipeline/add-cert-manager-hostalias.sh" + if [[ -x "$HOSTALIAS_SCRIPT" ]]; then + "$HOSTALIAS_SCRIPT" "$host_no_port" + elif [[ -f "$HOSTALIAS_SCRIPT" ]]; then + bash "$HOSTALIAS_SCRIPT" "$host_no_port" + else + echo "Errore: script non trovato: $HOSTALIAS_SCRIPT" >&2 + exit 1 + fi +fi + # Output richiesto: https- -secret /root/work/pipeline/add-listener.sh $endpoint https-$token $token-secret