Compare commits

..
4 Commits
37 changed files with 3136 additions and 6 deletions
+90
View File
@@ -0,0 +1,90 @@
kubectl get cluster.postgresql.cnpg.io -n <namespace>
kubectl get backup -n <namespace>
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: <pgclustername>-restored
spec:
instances: <instancenumber>
storage:
size: <dbstorage>
storageClass: csi-rbdfs-sc
walStorage:
storageClass: csi-rbdfs-sc
size: <dbstorage>
bootstrap:
recovery:
source: <pgclustername>-origin
backup:
name: sporteams-backup-20260718020000 # nome dell'oggetto Backup da kubectl get backup -n <namespace>
recoveryTarget: # opzionale in assenza applica tutto il journal (wal) a partire dal backup
targetTime: "2026-07-18 15:30:00.000000+00" # timestamp in cui si ferma rebuild dal WAL
externalClusters:
- name: <pgclustername>-origin
barmanObjectStore:
endpointURL: http://minio.minio.svc.cluster.local:9000
destinationPath: s3://cnpgbackup2/<pgclustername>
s3Credentials:
accessKeyId:
name: minio-backup-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: minio-backup-creds
key: ACCESS_SECRET_KEY
wal:
compression: gzip
maxParallel: 2
data:
compression: gzip
immediateCheckpoint: false
jobs: 2
# Dopo il restore, il cluster ripristinato può avere un backup indipendente:
backup:
barmanObjectStore:
endpointURL: http://minio.minio.svc.cluster.local:9000
destinationPath: s3://cnpgbackup2/<pgclustername>-restored
s3Credentials:
accessKeyId:
name: minio-backup-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: minio-backup-creds
key: ACCESS_SECRET_KEY
wal:
compression: gzip
maxParallel: 2
data:
compression: gzip
immediateCheckpoint: false
jobs: 2
retentionPolicy: "30d"
postgresql:
parameters:
max_connections: "300"
shared_buffers: "1GB"
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "2Gi"
### Utility varie ##########
kubectl get cluster.postgresql.cnpg.io -n stsharedservices-dev
kubectl -n minio exec -it minio-0 -- sh
mc alias set local http://localhost:9000 minioadmin KAYQE1QA7uwUZ8uI
mc ls local cnpgbackup2
@@ -0,0 +1,173 @@
# =====================================================================
# values.yaml per kube-prometheus-stack su RKE2 (installazione diretta,
# senza Rancher Manager)
#
# Repo: https://prometheus-community.github.io/helm-charts
# Chart: kube-prometheus-stack
#
# Install:
# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# helm repo update
# helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
# -n monitoring --create-namespace \
# -f 01-kube-prometheus-stack-values.yaml
# =====================================================================
# ---------------------------------------------------------------------
# Prometheus
# ---------------------------------------------------------------------
prometheus:
prometheusSpec:
retention: 15d
retentionSize: "10GB"
# Adatta alla storageClass disponibile nel tuo cluster RKE2
# (es. longhorn, local-path-provisioner, ceph-rbd, ecc.)
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: longhorn # <-- CAMBIA con la tua storageClass
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
resources:
requests:
cpu: 250m
memory: 1Gi
limits:
memory: 2Gi
# Permette a Prometheus di scoprire ServiceMonitor/PodMonitor
# creati in QUALSIASI namespace (importante per uno stack "generico"
# dove le verticali applicative, es. CNPG, vivono in namespace propri)
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
ruleSelectorNilUsesHelmValues: false
# Su RKE2 il control-plane espone metriche ma va abilitato esplicitamente
# lo scraping (vedi sezioni kubeEtcd/kubeControllerManager/kubeScheduler
# più sotto). Se il tuo cluster è "hardened" e non espone questi endpoint
# sui nodi worker, valuta di disabilitare le relative sezioni.
# ---------------------------------------------------------------------
# Control plane RKE2 — di default questi componenti su RKE2 girano come
# pod statici raggiungibili sul nodo server; verificare le porte reali
# con `kubectl get pods -n kube-system` e `netstat` sul nodo se lo
# scraping non trova i target.
# ---------------------------------------------------------------------
kubeEtcd:
enabled: true
service:
port: 2381
targetPort: 2381
kubeControllerManager:
enabled: true
service:
port: 10257
targetPort: 10257
# RKE2 espone il controller-manager in HTTPS con cert self-signed:
# potrebbe servire scrapeConfig con tlsConfig.insecureSkipVerify: true
# (vedi kube-prometheus-stack docs, sezione "kubeControllerManager").
kubeScheduler:
enabled: true
service:
port: 10259
targetPort: 10259
# Traefik è deployato di default su RKE2 ed è considerato componente
# interno. Se lo usi come ingress controller, aggiungi un ServiceMonitor
# dedicato puntato al suo endpoint /metrics (di solito porta 9100).
kubeProxy:
enabled: true
# ---------------------------------------------------------------------
# Alertmanager
# ---------------------------------------------------------------------
alertmanager:
alertmanagerSpec:
retention: 120h
storage:
volumeClaimTemplate:
spec:
storageClassName: longhorn # <-- CAMBIA con la tua storageClass
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 256Mi
# Config di base; per la config completa vedi il file
# 04-alertmanager-config.yaml (AlertmanagerConfig CRD, più gestibile
# in modo dichiarativo/GitOps rispetto a alertmanager.config qui).
# ---------------------------------------------------------------------
# Grafana (incluso nel chart)
# ---------------------------------------------------------------------
grafana:
enabled: true
defaultDashboardsTimezone: Europe/Rome
persistence:
enabled: true
storageClassName: longhorn # <-- CAMBIA con la tua storageClass
size: 5Gi
# Cambia in produzione: usa un Secret invece di plaintext
adminPassword: "CHANGE_ME"
# Loki come datasource aggiuntivo (vedi file 03-loki-alloy)
additionalDataSources:
- name: Loki
type: loki
access: proxy
url: http://loki.monitoring.svc.cluster.local:3100
isDefault: false
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
# Import automatico dashboard CNPG (ID 20417) via sidecar dashboards
# già incluso nel chart: basta creare un ConfigMap con label
# grafana_dashboard=1 (vedi file 02-cnpg-monitoring.yaml in fondo,
# sezione dashboard, oppure importa manualmente da Grafana UI).
# ---------------------------------------------------------------------
# node-exporter
# ---------------------------------------------------------------------
nodeExporter:
enabled: true
prometheus-node-exporter:
resources:
requests:
cpu: 50m
memory: 30Mi
limits:
memory: 50Mi
# ---------------------------------------------------------------------
# kube-state-metrics
# ---------------------------------------------------------------------
kubeStateMetrics:
enabled: true
kube-state-metrics:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
@@ -0,0 +1,96 @@
# =====================================================================
# Monitoring per CloudNativePG (CNPG)
#
# Applica con: kubectl apply -f 02-cnpg-monitoring.yaml
#
# Prerequisiti:
# - CNPG operator già installato (namespace tipico: cnpg-system)
# - kube-prometheus-stack già installato nel namespace "monitoring"
# con podMonitorSelectorNilUsesHelmValues: false (vedi file 01),
# così Prometheus scopre PodMonitor in TUTTI i namespace.
# =====================================================================
# ---------------------------------------------------------------------
# 1) PodMonitor per l'operatore CNPG stesso
# (metriche interne: reconcile loop, controller-runtime, porta 8080)
# ---------------------------------------------------------------------
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: cnpg-operator
namespace: cnpg-system
labels:
release: kube-prometheus-stack
spec:
selector:
matchLabels:
app.kubernetes.io/name: cloudnative-pg
podMetricsEndpoints:
- port: metrics
interval: 30s
---
# ---------------------------------------------------------------------
# 2) PodMonitor generico per TUTTI i cluster Postgres gestiti da CNPG
#
# Nota: se preferisci il metodo "automatico" (enablePodMonitor: true
# nella risorsa Cluster), questo PodMonitor manuale NON serve per
# quel cluster specifico — ma il manuale è consigliato dalla doc
# ufficiale CNPG perché dà controllo esplicito sul lifecycle,
# indipendente dal singolo Cluster CR.
#
# Il selettore usa la label standard "cnpg.io/cluster" che CNPG
# applica automaticamente a tutti i pod di ogni cluster gestito,
# quindi UN SOLO PodMonitor copre TUTTI i cluster Postgres presenti
# nel namespace target (adatta matchExpressions se i tuoi cluster
# vivono in namespace diversi: serve un PodMonitor per namespace,
# oppure passa a un selector cross-namespace via namespaceSelector).
# ---------------------------------------------------------------------
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: cnpg-clusters
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
namespaceSelector:
any: true # scropa i pod CNPG in QUALSIASI namespace
selector:
matchExpressions:
- key: cnpg.io/cluster
operator: Exists
podMetricsEndpoints:
- port: metrics # exporter per-istanza, porta 9187
interval: 30s
scrapeTimeout: 10s
path: /metrics
---
# ---------------------------------------------------------------------
# 3) Import automatico dashboard CNPG ufficiale (grafana.com ID 20417)
# tramite sidecar dashboards di Grafana (già incluso nel chart
# kube-prometheus-stack). Basta un ConfigMap con la label
# "grafana_dashboard: '1'" nel namespace di Grafana.
#
# In alternativa più semplice: Grafana UI -> Dashboards -> Import
# -> inserisci ID 20417 -> seleziona datasource Prometheus.
# ---------------------------------------------------------------------
apiVersion: v1
kind: ConfigMap
metadata:
name: cnpg-grafana-dashboard
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
cnpg-dashboard.json: |
{
"annotations": {"list": []},
"title": "CloudNativePG (import via ID 20417)",
"__inputs": [],
"__requires": [],
"schemaVersion": 39,
"panels": [],
"_comment": "Placeholder: sostituisci con l'export JSON completo scaricato da https://grafana.com/grafana/dashboards/20417 per avere la dashboard reale, oppure importa manualmente da Grafana UI usando l'ID."
}
+205
View File
@@ -0,0 +1,205 @@
# =====================================================================
# PrometheusRule: alert infrastrutturali + CNPG
# Applica con: kubectl apply -f 03-alert-rules.yaml
# =====================================================================
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: infra-alerts
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
groups:
- name: node-health
rules:
- alert: NodeDown
expr: up{job="node-exporter"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Nodo {{ $labels.instance }} irraggiungibile"
description: "node-exporter non risponde da 5 minuti su {{ $labels.instance }}."
- alert: NodeHighCPU
expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 10m
labels:
severity: warning
annotations:
summary: "CPU alta su {{ $labels.instance }}"
description: "Uso CPU sopra 85% da 10 minuti."
- alert: NodeHighMemory
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
for: 10m
labels:
severity: warning
annotations:
summary: "Memoria alta su {{ $labels.instance }}"
description: "Uso memoria sopra 90% da 10 minuti."
- alert: NodeDiskSpaceLow
expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 < 15
for: 10m
labels:
severity: warning
annotations:
summary: "Spazio disco basso su {{ $labels.instance }} ({{ $labels.mountpoint }})"
description: "Meno del 15% di spazio libero da 10 minuti."
- alert: NodeDiskSpaceCritical
expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 < 5
for: 5m
labels:
severity: critical
annotations:
summary: "Spazio disco CRITICO su {{ $labels.instance }} ({{ $labels.mountpoint }})"
description: "Meno del 5% di spazio libero da 5 minuti."
- name: kubernetes-health
rules:
- alert: KubePodCrashLooping
expr: increase(kube_pod_container_status_restarts_total[15m]) > 3
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} in crash loop"
description: "Più di 3 restart negli ultimi 15 minuti."
- alert: KubePodNotReady
expr: sum by (namespace, pod) (kube_pod_status_phase{phase=~"Pending|Unknown"}) > 0
for: 15m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} non Ready da 15 minuti"
- alert: KubeDeploymentReplicasMismatch
expr: kube_deployment_spec_replicas != kube_deployment_status_replicas_available
for: 15m
labels:
severity: warning
annotations:
summary: "Deployment {{ $labels.namespace }}/{{ $labels.deployment }} con repliche non allineate"
- alert: KubePersistentVolumeFillingUp
expr: (kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytes) * 100 < 10
for: 10m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} quasi pieno"
description: "Meno del 10% di spazio libero sul volume."
- alert: EtcdInsufficientMembers
expr: sum(up{job="kube-etcd"} == 1) < ((count(up{job="kube-etcd"}) + 1) / 2)
for: 5m
labels:
severity: critical
annotations:
summary: "Quorum etcd a rischio"
description: "Meno della maggioranza dei membri etcd è raggiungibile."
- name: tls-certificates
rules:
# Richiede blackbox_exporter con probe HTTPS configurato sui tuoi
# endpoint esterni; vedi nota nel file README.
- alert: TLSCertExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 15
for: 1h
labels:
severity: warning
annotations:
summary: "Certificato TLS per {{ $labels.instance }} in scadenza"
description: "Meno di 15 giorni alla scadenza del certificato."
- alert: TLSCertExpired
expr: probe_ssl_earliest_cert_expiry - time() < 0
for: 5m
labels:
severity: critical
annotations:
summary: "Certificato TLS per {{ $labels.instance }} SCADUTO"
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cnpg-alerts
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
groups:
- name: cnpg-health
rules:
- alert: CNPGClusterNotHealthy
expr: cnpg_collector_up == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Cluster CNPG {{ $labels.namespace }}/{{ $labels.pod }} non raggiungibile"
description: "L'exporter CNPG non risponde da 5 minuti."
- alert: CNPGInstanceInUnexpectedRecovery
expr: cnpg_pg_replication_in_recovery == 1 and on(pod) cnpg_pg_replication_is_wal_receiver_up == 0
for: 5m
labels:
severity: warning
annotations:
summary: "Istanza {{ $labels.pod }} in recovery inatteso"
description: "Verifica se il ruolo primary/standby è quello atteso."
- alert: CNPGReplicationLagHigh
expr: cnpg_pg_replication_lag > 300
for: 5m
labels:
severity: warning
annotations:
summary: "Replication lag alto su {{ $labels.pod }}"
description: "Lag di replica superiore a 300 secondi."
- alert: CNPGWALArchivingFailing
expr: increase(cnpg_pg_stat_archiver_failed_count[15m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Archiviazione WAL fallita su {{ $labels.pod }}"
description: "Almeno un fallimento di archiviazione WAL negli ultimi 15 minuti — rischio per i backup e il point-in-time recovery."
- alert: CNPGBackupFailed
expr: cnpg_collector_last_failed_backup_timestamp > cnpg_collector_last_available_backup_timestamp
for: 5m
labels:
severity: critical
annotations:
summary: "Ultimo backup fallito per {{ $labels.namespace }}/{{ $labels.pod }}"
- alert: CNPGNoRecentBackup
expr: (time() - cnpg_collector_last_available_backup_timestamp) > 86400
for: 10m
labels:
severity: warning
annotations:
summary: "Nessun backup riuscito nelle ultime 24h per {{ $labels.namespace }}/{{ $labels.pod }}"
- alert: CNPGConnectionsNearLimit
expr: (cnpg_backends_total / cnpg_pg_settings_setting{name="max_connections"}) * 100 > 85
for: 10m
labels:
severity: warning
annotations:
summary: "Connessioni Postgres vicine al limite su {{ $labels.pod }}"
description: "Oltre l'85% di max_connections in uso."
# Nota: i nomi esatti delle metriche cnpg_* possono variare leggermente
# tra versioni dell'operatore CNPG. Verifica sempre su
# http://localhost:9090/graph con `{__name__=~"cnpg_.+"}` dopo il
# port-forward a Prometheus, e allinea le espressioni se necessario:
# kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090
@@ -0,0 +1,105 @@
# =====================================================================
# Loki + Grafana Alloy — log aggregation
#
# Loki:
# helm repo add grafana https://grafana.github.io/helm-charts
# helm repo update
# helm install loki grafana/loki -n monitoring -f loki-values.yaml
#
# Alloy (raccolta log, DaemonSet su ogni nodo RKE2):
# helm install alloy grafana/alloy -n monitoring -f alloy-values.yaml
#
# Questo file contiene ENTRAMBI i set di values, separati da "---";
# salvali in due file distinti se preferisci due comandi helm separati.
# =====================================================================
# ---------------------------------------------------------------------
# loki-values.yaml (modalità single-binary, adatta a partire in piccolo;
# per HA/produzione a lungo termine valutare la modalità "simple scalable"
# con backend object storage S3-compatibile, es. MinIO)
# ---------------------------------------------------------------------
deploymentMode: SingleBinary
loki:
auth_enabled: false
commonConfig:
replication_factor: 1
storage:
type: filesystem
singleBinary:
replicas: 1
persistence:
enabled: true
storageClass: longhorn # <-- CAMBIA con la tua storageClass
size: 50Gi
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
memory: 1Gi
# Disabilita i componenti "scalable" non necessari in modalità SingleBinary
read:
replicas: 0
write:
replicas: 0
backend:
replicas: 0
gateway:
enabled: false
test:
enabled: false
---
# ---------------------------------------------------------------------
# alloy-values.yaml (DaemonSet che raccoglie log da tutti i nodi/pod
# e li invia a Loki)
# ---------------------------------------------------------------------
alloy:
configMap:
content: |
discovery.kubernetes "pods" {
role = "pod"
}
discovery.relabel "pods" {
targets = discovery.kubernetes.pods.targets
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
}
loki.source.kubernetes "pods" {
targets = discovery.relabel.pods.output
forward_to = [loki.write.default.receiver]
}
loki.write "default" {
endpoint {
url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push"
}
}
controller:
type: daemonset
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
@@ -0,0 +1,62 @@
# =====================================================================
# AlertmanagerConfig — routing alert per severità
# Applica con: kubectl apply -f 05-alertmanager-config.yaml
#
# Richiede i Secret con le credenziali (webhook Slack, integration key
# PagerDuty) creati separatamente, es.:
#
# kubectl create secret generic alertmanager-slack \
# -n monitoring --from-literal=url='https://hooks.slack.com/services/XXX/YYY/ZZZ'
#
# kubectl create secret generic alertmanager-pagerduty \
# -n monitoring --from-literal=serviceKey='YOUR_PAGERDUTY_INTEGRATION_KEY'
# =====================================================================
apiVersion: monitoring.coreos.com/v1alpha1
kind: AlertmanagerConfig
metadata:
name: routing-config
namespace: monitoring
labels:
# deve matchare alertmanagerConfigSelector del chart
# (di default kube-prometheus-stack seleziona per release label)
release: kube-prometheus-stack
spec:
route:
groupBy: ["alertname", "namespace"]
groupWait: 30s
groupInterval: 5m
repeatInterval: 4h
receiver: "slack-default"
routes:
- matchers:
- name: severity
value: critical
matchType: "="
receiver: "pagerduty-critical"
continue: true # invia ANCHE a slack-default per visibilità
- matchers:
- name: severity
value: warning
matchType: "="
receiver: "slack-default"
receivers:
- name: "slack-default"
slackConfigs:
- apiURL:
name: alertmanager-slack
key: url
channel: "#alerts-k8s"
sendResolved: true
title: '{{ "{{" }} .CommonAnnotations.summary {{ "}}" }}'
text: '{{ "{{" }} .CommonAnnotations.description {{ "}}" }}'
- name: "pagerduty-critical"
pagerdutyConfigs:
- serviceKey:
name: alertmanager-pagerduty
key: serviceKey
severity: "critical"
description: '{{ "{{" }} .CommonAnnotations.summary {{ "}}" }}'
sendResolved: true
+121
View File
@@ -0,0 +1,121 @@
# Stack di monitoring/alerting per RKE2 — CNPG come verticale iniziale
Stack generico, installato senza passare da Rancher Manager: kube-prometheus-stack
(Prometheus Operator + Prometheus + Alertmanager + Grafana + node-exporter +
kube-state-metrics) + Loki/Alloy per i log + monitoring dedicato per CloudNativePG.
## Ordine di installazione
1. **Namespace + kube-prometheus-stack**
```bash
kubectl create namespace monitoring
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
-n monitoring \
-f 01-kube-prometheus-stack-values.yaml
```
kube-prometheus-stack has been installed. Check its status by running:
kubectl --namespace monitoring get pods -l "release=kube-prometheus-stack"
Get Grafana 'admin' user password by running:
kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo
Access Grafana local instance:
export POD_NAME=$(kubectl --namespace monitoring get pod -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=kube-prometheus-stack" -oname)
kubectl --namespace monitoring port-forward $POD_NAME 3000
Get your grafana admin user password by running:
kubectl get secret --namespace monitoring -l app.kubernetes.io/component=admin-secret -o jsonpath="{.items[0].data.admin-password}" | base64 --decode ; echo
Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create & configure Alertmanager and Prometheus instances using the Operator.
kubectl run -it --rm debug --image=curlimages/curl -n monitoring --restart=Never -- curl -v http://kube-prometheus-stack-grafana.monitoring.svc.cluster.local:80/
2. **Monitoring CNPG** (dopo che l'operatore CNPG è già installato)
```bash
kubectl apply -f 02-cnpg-monitoring.yaml
```
3. **Alert rules** (infra + CNPG)
```bash
kubectl apply -f 03-alert-rules.yaml
```
4. **Loki + Alloy** (log aggregation) — dividi il file in due se preferisci
comandi helm separati (`loki-values.yaml` / `alloy-values.yaml`)
```bash
helm install loki grafana/loki -n monitoring -f 04-loki-alloy-values.yaml
helm install alloy grafana/alloy -n monitoring -f 04-loki-alloy-values.yaml
```
5. **Alertmanager routing** — crea prima i Secret con le credenziali reali,
poi applica la config
```bash
kubectl create secret generic alertmanager-slack \
-n monitoring --from-literal=url='<SLACK_WEBHOOK_URL>'
kubectl create secret generic alertmanager-pagerduty \
-n monitoring --from-literal=serviceKey='<PAGERDUTY_KEY>'
kubectl apply -f 05-alertmanager-config.yaml
```
## Cose da personalizzare prima di applicare in un cluster reale
- **storageClassName** in tutti i file (`longhorn` è solo un placeholder —
sostituisci con la storageClass effettivamente disponibile sul tuo RKE2,
verificabile con `kubectl get storageclass`).
- **adminPassword** di Grafana nel file 01 — spostalo in un Secret,
non lasciarlo in chiaro nei values in produzione.
- **Nomi metriche CNPG** — verifica che corrispondano alla versione esatta
dell'operatore CNPG installata (vedi nota in fondo al file 03).
- **Dashboard CNPG** — il ConfigMap nel file 02 è un placeholder; scarica
il JSON reale da https://grafana.com/grafana/dashboards/20417 e
sostituiscilo, oppure importa manualmente da Grafana UI (Dashboards →
Import → ID 20417).
- **Control-plane RKE2** (kubeEtcd/kubeControllerManager/kubeScheduler nel
file 01) — porte ed endpoint possono variare in base a come RKE2 è
configurato (hardening CIS, profili custom). Verifica con
`kubectl get pods -n kube-system` quali pod statici girano e su quali
porte, e correggi se necessario.
- **AlertmanagerConfig selector** — deve combaciare con
`alertmanagerConfigSelector` impostato dal chart (di default seleziona
per label `release: <helm-release-name>`); se hai chiamato la release
diversamente da `kube-prometheus-stack`, aggiorna la label.
## Verifiche post-installazione
```bash
# Prometheus targets (verifica che CNPG, node-exporter, ecc. siano UP)
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090
# -> http://localhost:9090/targets
# Grafana
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
# -> http://localhost:3000 (utente: admin, password: quella impostata)
# Alertmanager
kubectl port-forward -n monitoring svc/kube-prometheus-stack-alertmanager 9093
# -> http://localhost:9093
```
## Estensioni future (non incluse in questa prima fase)
- **blackbox_exporter** — probe HTTP/TCP/TLS per endpoint applicativi
esterni e monitoraggio scadenza certificati (le regole `TLSCert*` nel
file 03 presuppongono questo exporter già installato).
- **Tempo + OpenTelemetry Collector** — tracing distribuito quando ci
saranno più verticali applicative da correlare.
- **Thanos/Mimir** — retention lunga e vista multi-cluster, se in futuro
servirà storicizzare oltre i 15gg locali o aggregare più cluster RKE2.
+38
View File
@@ -0,0 +1,38 @@
apiVersion: v1
kind: Service
metadata:
name: kube-prometheus-stack-grafana
namespace: monitoring
labels:
app.kubernetes.io/instance: kube-prometheus-stack
app.kubernetes.io/name: grafana
spec:
type: ClusterIP
selector:
app.kubernetes.io/instance: kube-prometheus-stack
app.kubernetes.io/name: grafana
ports:
- name: http
protocol: TCP
port: 80
targetPort: grafana
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: grafana
namespace: monitoring
spec:
hostnames:
- grafana.internal
parentRefs:
- name: main-gateway
namespace: nginx-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: kube-prometheus-stack-grafana
port: 80
View File
+438
View File
@@ -0,0 +1,438 @@
#!/usr/bin/env bash
#
# export-k8s-manifests.sh
#
# Ricostruisce i manifest YAML dichiarativi delle risorse installate
# su un cluster Kubernetes, per una lista di namespace data in input.
#
# Rimuove i campi "runtime" (status, managedFields, resourceVersion, uid,
# creationTimestamp, ownerReferences generate da controller, ecc.) in modo
# da produrre YAML riapplicabili con `kubectl apply -f`.
#
# Uso:
# ./export-k8s-manifests.sh -n ns1,ns2,ns3 [-o output_dir] [-c kubeconfig] [-k context]
# ./export-k8s-manifests.sh -f namespaces.txt [-o output_dir]
#
# Dipendenze:
# - kubectl (obbligatorio)
# - yq (v4+, https://github.com/mikefarah/yq) — consigliato per pulizia campi
# Se assente, viene usato un fallback con python3.
#
set -euo pipefail
# ------------------------------------------------------------------
# Default
# ------------------------------------------------------------------
OUTPUT_DIR="./k8s-export-$(date +%Y%m%d-%H%M%S)"
NAMESPACES=()
NAMESPACE_FILE=""
KUBECONFIG_OPT=()
CONTEXT_OPT=()
INCLUDE_SECRETS=false
# Tipi di risorsa namespaced da esportare.
# Personalizza questa lista secondo le tue esigenze.
RESOURCE_TYPES=(
configmap
secret
service
serviceaccount
deployment
statefulset
daemonset
replicaset
job
cronjob
ingress
networkpolicy
poddisruptionbudget
horizontalpodautoscaler
persistentvolumeclaim
role
rolebinding
# --- API Gateway (namespaced) -------------------------------------
# Gateway API standard (gateway.networking.k8s.io)
gateway
httproute
grpcroute
tcproute
tlsroute
referencegrant
# Istio
virtualservice
destinationrule
# Traefik
ingressroute
middleware
# Kong
kongingress
kongplugin
kongconsumer
# APISIX
apisixroute
apisixpluginconfig
# NGINX Gateway Fabric — CRD di estensione (gateway.nginx.org)
# Le risorse Gateway API standard (gateway, httproute, gatewayclass, ecc.)
# sono già coperte sopra; queste sono le estensioni specifiche NGF per
# configurazioni non esprimibili nello standard Gateway API.
nginxproxy
clientsettingspolicy
observabilitypolicy
upstreamsettingspolicy
snippetsfilter
# CRD comuni — decommenta/aggiungi secondo gli operator installati
cluster.postgresql.cnpg.io # CloudNativePG Cluster
scheduledbackup.postgresql.cnpg.io
certificate.cert-manager.io
)
# Tipi di risorsa CLUSTER-SCOPED da esportare.
# Vengono filtrate per pertinenza ai namespace indicati dove possibile
# (es. ClusterRoleBinding con subject in uno dei namespace, PV con claim
# in uno dei namespace). Le risorse "globali" (StorageClass, GatewayClass,
# IngressClass, PriorityClass) vengono esportate sempre, essendo poche
# e prive di legame diretto con un namespace.
CLUSTER_RESOURCE_TYPES_FILTERED=(
clusterrolebinding
persistentvolume
)
CLUSTER_RESOURCE_TYPES_GLOBAL=(
storageclass
priorityclass
# API Gateway (cluster-scoped)
gatewayclass
ingressclass
# Da valutare se esportare sempre (potenzialmente numerose/rumorose):
# clusterrole
# customresourcedefinition
)
# ------------------------------------------------------------------
# Parsing argomenti
# ------------------------------------------------------------------
usage() {
cat <<EOF
Uso: $0 -n ns1,ns2,ns3 [opzioni]
$0 -f namespaces.txt [opzioni]
Opzioni:
-n <ns1,ns2,...> Lista namespace separati da virgola
-f <file> File con un namespace per riga (alternativo a -n)
-o <dir> Directory di output (default: ${OUTPUT_DIR})
-c <kubeconfig> Path al kubeconfig da usare
-k <context> Nome del context kubectl da usare
-s Includi anche i Secret (default: esclusi per sicurezza)
-h Mostra questo help
EOF
exit 1
}
while getopts "n:f:o:c:k:sh" opt; do
case "$opt" in
n) IFS=',' read -r -a NAMESPACES <<< "$OPTARG" ;;
f) NAMESPACE_FILE="$OPTARG" ;;
o) OUTPUT_DIR="$OPTARG" ;;
c) KUBECONFIG_OPT=(--kubeconfig "$OPTARG") ;;
k) CONTEXT_OPT=(--context "$OPTARG") ;;
s) INCLUDE_SECRETS=true ;;
h) usage ;;
*) usage ;;
esac
done
if [[ -n "$NAMESPACE_FILE" ]]; then
while IFS= read -r line; do
[[ -z "$line" || "$line" =~ ^# ]] && continue
NAMESPACES+=("$line")
done < "$NAMESPACE_FILE"
fi
if [[ ${#NAMESPACES[@]} -eq 0 ]]; then
echo "Errore: nessun namespace specificato (usa -n o -f)" >&2
usage
fi
if [[ "$INCLUDE_SECRETS" == false ]]; then
RESOURCE_TYPES=("${RESOURCE_TYPES[@]/secret}")
fi
KCTL=(kubectl "${KUBECONFIG_OPT[@]}" "${CONTEXT_OPT[@]}")
# ------------------------------------------------------------------
# Verifica dipendenze
# ------------------------------------------------------------------
command -v kubectl >/dev/null 2>&1 || { echo "Errore: kubectl non trovato" >&2; exit 1; }
USE_YQ=false
if command -v yq >/dev/null 2>&1; then
USE_YQ=true
elif command -v python3 >/dev/null 2>&1; then
USE_YQ=false
else
echo "Errore: serve 'yq' oppure 'python3' per la pulizia dei manifest" >&2
exit 1
fi
echo "Verifica connessione al cluster..."
"${KCTL[@]}" cluster-info >/dev/null 2>&1 || { echo "Errore: impossibile contattare il cluster" >&2; exit 1; }
CLUSTER_NAME=$("${KCTL[@]}" config current-context 2>/dev/null || echo "unknown-cluster")
echo "Cluster context: ${CLUSTER_NAME}"
echo "Output dir: ${OUTPUT_DIR}"
echo "Namespace da esportare: ${NAMESPACES[*]}"
echo "Secret inclusi: ${INCLUDE_SECRETS}"
echo
mkdir -p "$OUTPUT_DIR"
# Gli script Python vengono scritti su file temporanei perché in bash
# `python3 - <<'HEREDOC'` sovrascrive lo stdin del processo con l'heredoc,
# rendendo impossibile leggere i dati dalla pipeline (il fd 0 è già consumato
# prima che il codice Python arrivi a sys.stdin.read()).
_PY_CLEAN=$(mktemp)
_PY_FILTER=$(mktemp)
trap 'rm -f "$_PY_CLEAN" "$_PY_FILTER"' EXIT
if [[ "$USE_YQ" == false ]]; then
cat > "$_PY_CLEAN" << 'PYCLEAN'
import sys, yaml
doc = yaml.safe_load(sys.stdin)
if doc is None:
sys.exit(0)
doc.pop("status", None)
md = doc.get("metadata", {})
for f in ("uid", "resourceVersion", "generation", "creationTimestamp",
"selfLink", "managedFields", "ownerReferences"):
md.pop(f, None)
ann = md.get("annotations")
if ann:
ann.pop("kubectl.kubernetes.io/last-applied-configuration", None)
ann.pop("deployment.kubernetes.io/revision", None)
if not ann:
md.pop("annotations", None)
spec = doc.get("spec", {})
spec.pop("clusterIP", None)
spec.pop("clusterIPs", None)
tmpl = spec.get("template", {})
if isinstance(tmpl, dict):
tmpl.get("metadata", {}).pop("creationTimestamp", None)
print(yaml.dump(doc, sort_keys=False, default_flow_style=False), end="")
PYCLEAN
cat > "$_PY_FILTER" << 'PYFILTER'
import json, os, sys
mode = sys.argv[1]
data = json.load(sys.stdin)
ns_set = set(os.environ.get("NS_CSV", "").split())
for item in data.get("items", []):
if mode == "subjects":
subs = item.get("subjects") or []
match = any(s.get("namespace") in ns_set for s in subs)
else:
claim = (item.get("spec") or {}).get("claimRef") or {}
match = claim.get("namespace") in ns_set
if match:
print(item["metadata"]["name"])
PYFILTER
fi
# ------------------------------------------------------------------
# Funzione di pulizia dei campi runtime
# ------------------------------------------------------------------
clean_manifest_yq() {
# Rimuove campi generati dal cluster, mantenendo lo spec dichiarativo
yq eval '
del(.status) |
del(.metadata.uid) |
del(.metadata.resourceVersion) |
del(.metadata.generation) |
del(.metadata.creationTimestamp) |
del(.metadata.selfLink) |
del(.metadata.managedFields) |
del(.metadata.annotations."kubectl.kubernetes.io/last-applied-configuration") |
del(.metadata.annotations."deployment.kubernetes.io/revision") |
del(.metadata.ownerReferences) |
del(.spec.clusterIP) |
del(.spec.clusterIPs) |
del(.spec.template.metadata.creationTimestamp)
' -
}
clean_manifest_python() {
# Usa il file temporaneo creato all'avvio; l'heredoc inline non funziona
# in pipeline perché sovrascrive lo stdin del processo.
python3 "$_PY_CLEAN"
}
clean_manifest() {
if [[ "$USE_YQ" == true ]]; then
clean_manifest_yq
else
clean_manifest_python
fi
}
# ------------------------------------------------------------------
# Export per namespace / kind / risorsa
# ------------------------------------------------------------------
SUMMARY_FILE="${OUTPUT_DIR}/EXPORT_SUMMARY.md"
{
echo "# Export manifest Kubernetes"
echo
echo "- Cluster context: \`${CLUSTER_NAME}\`"
echo "- Data export: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
echo "- Namespace: ${NAMESPACES[*]}"
echo
echo "| Namespace | Kind | Nome | File |"
echo "|---|---|---|---|"
} > "$SUMMARY_FILE"
for ns in "${NAMESPACES[@]}"; do
echo "== Namespace: ${ns} =="
if ! "${KCTL[@]}" get namespace "$ns" >/dev/null 2>&1; then
echo " ATTENZIONE: namespace '${ns}' non trovato, salto." >&2
continue
fi
NS_DIR="${OUTPUT_DIR}/${ns}"
mkdir -p "$NS_DIR"
for kind in "${RESOURCE_TYPES[@]}"; do
[[ -z "$kind" ]] && continue
# Il controllo api-resources è omesso: kubectl get <kind> 2>/dev/null || true
# gestisce già i resource type non esistenti restituendo NAMES vuoto.
NAMES=$("${KCTL[@]}" -n "$ns" get "$kind" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
[[ -z "$NAMES" ]] && continue
KIND_DIR="${NS_DIR}/${kind}"
mkdir -p "$KIND_DIR"
while IFS= read -r name; do
[[ -z "$name" ]] && continue
# Salta le risorse generate automaticamente (es. default token secret,
# ReplicaSet gestiti da Deployment, Pod gestiti da controller superiori)
if [[ "$kind" == "secret" && "$name" =~ ^default-token- ]]; then
continue
fi
OUT_FILE="${KIND_DIR}/${name}.yaml"
echo " -> ${kind}/${name}"
if "${KCTL[@]}" -n "$ns" get "$kind" "$name" -o yaml 2>/dev/null | clean_manifest > "$OUT_FILE"; then
echo "| ${ns} | ${kind} | ${name} | \`${ns}/${kind}/${name}.yaml\` |" >> "$SUMMARY_FILE"
else
echo " ATTENZIONE: export fallito per ${kind}/${name}" >&2
rm -f "$OUT_FILE"
fi
done <<< "$NAMES"
done
done
# ------------------------------------------------------------------
# Export risorse cluster-scoped
# ------------------------------------------------------------------
echo
echo "== Risorse cluster-scoped =="
CLUSTER_DIR="${OUTPUT_DIR}/_cluster-scoped"
# Cache unica delle risorse disponibili (evita N chiamate a api-resources)
_AVAIL_NAMES=$("${KCTL[@]}" api-resources --no-headers 2>/dev/null | awk '{print tolower($1)}' || true)
_AVAIL_KINDS=$("${KCTL[@]}" api-resources --no-headers 2>/dev/null | awk '{print tolower($NF)}' || true)
resource_type_exists() {
local kind="$1"
# Controlla sia la colonna NAME (plurale) sia la colonna KIND (singolare) del
# risultato di api-resources per gestire plurali irregolari (es. ingress→ingresses).
echo "$_AVAIL_NAMES" | grep -qx "${kind}s" \
|| echo "$_AVAIL_KINDS" | grep -qx "$kind"
}
export_cluster_resource() {
local kind="$1"
local name="$2"
local kind_dir="${CLUSTER_DIR}/${kind}"
mkdir -p "$kind_dir"
local out_file="${kind_dir}/${name}.yaml"
echo " -> ${kind}/${name}"
if "${KCTL[@]}" get "$kind" "$name" -o yaml 2>/dev/null | clean_manifest > "$out_file"; then
echo "| (cluster) | ${kind} | ${name} | \`_cluster-scoped/${kind}/${name}.yaml\` |" >> "$SUMMARY_FILE"
else
echo " ATTENZIONE: export fallito per ${kind}/${name}" >&2
rm -f "$out_file"
fi
}
# --- Risorse globali: esportate sempre, senza filtro namespace ---
for kind in "${CLUSTER_RESOURCE_TYPES_GLOBAL[@]}"; do
[[ -z "$kind" ]] && continue
resource_type_exists "$kind" || continue
NAMES=$("${KCTL[@]}" get "$kind" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
[[ -z "$NAMES" ]] && continue
while IFS= read -r name; do
[[ -z "$name" ]] && continue
export_cluster_resource "$kind" "$name"
done <<< "$NAMES"
done
# Filtra gli item di un array JSON kubectl (-o json) tenendo solo quelli
# per cui `jq_path` (letto tramite kubectl jsonpath-like con yq/python) ha
# un valore presente nella lista NAMESPACES. Stampa i nomi corrispondenti.
# $1 = json completo (stdin) $2 = "subjects" | "claimref"
filter_by_namespace_field() {
local mode="$1"
if [[ "$USE_YQ" == true ]]; then
if [[ "$mode" == "subjects" ]]; then
yq eval '.items[] | select((.subjects // []) | any_c(.namespace == env(NS_MATCH))) | .metadata.name' -
else
yq eval '.items[] | select(.spec.claimRef.namespace == env(NS_MATCH)) | .metadata.name' -
fi
else
python3 "$_PY_FILTER" "$mode"
fi
}
# --- ClusterRoleBinding: solo quelli con subject in uno dei namespace indicati ---
if resource_type_exists clusterrolebinding; then
MATCHED_CRB=""
for ns in "${NAMESPACES[@]}"; do
RESULT=$("${KCTL[@]}" get clusterrolebinding -o json 2>/dev/null \
| NS_CSV="${NAMESPACES[*]}" NS_MATCH="$ns" filter_by_namespace_field subjects || true)
MATCHED_CRB="${MATCHED_CRB}
${RESULT}"
done
MATCHED_CRB=$(echo "$MATCHED_CRB" | sort -u | sed '/^$/d')
while IFS= read -r name; do
[[ -z "$name" ]] && continue
export_cluster_resource clusterrolebinding "$name"
done <<< "$MATCHED_CRB"
fi
# --- PersistentVolume: solo quelli con claimRef in uno dei namespace indicati ---
if resource_type_exists persistentvolume; then
MATCHED_PV=""
for ns in "${NAMESPACES[@]}"; do
RESULT=$("${KCTL[@]}" get persistentvolume -o json 2>/dev/null \
| NS_CSV="${NAMESPACES[*]}" NS_MATCH="$ns" filter_by_namespace_field claimref || true)
MATCHED_PV="${MATCHED_PV}
${RESULT}"
done
MATCHED_PV=$(echo "$MATCHED_PV" | sort -u | sed '/^$/d')
while IFS= read -r name; do
[[ -z "$name" ]] && continue
export_cluster_resource persistentvolume "$name"
done <<< "$MATCHED_PV"
fi
echo
echo "Export completato in: ${OUTPUT_DIR}"
echo "Riepilogo: ${SUMMARY_FILE}"
+12 -6
View File
@@ -4,25 +4,31 @@ set -euo pipefail
# Uso: ./addlistener.sh [env] [properties_file] # Uso: ./addlistener.sh [env] [properties_file]
# env e' ignorato: endpoint viene sempre letto dalla chiave "endpoint". # env e' ignorato: endpoint viene sempre letto dalla chiave "endpoint".
ENVIRONMENT="${1:-dev}" ENVIRONMENT="${1:-dev}"
PROPERTIES_FILE="${2:-env/${ENVIRONMENT}/values.env}"
# Hardening per evitare errori con set -u in esecuzioni/sourcing anomali
: "${PROPERTIES_FILE:=env/${ENVIRONMENT}/values.env}"
ENV_DIR="env/${ENVIRONMENT}" ENV_DIR="env/${ENVIRONMENT}"
if [[ -n "$ENVIRONMENT" ]]; then if [[ -n "$ENVIRONMENT" ]]; then
echo "Ambiente richiesto: $ENVIRONMENT (chiave usata: endpoint)" echo "Ambiente richiesto: $ENVIRONMENT (chiave usata: endpoint)"
fi fi
if [[ ! -d "$ENV_DIR" ]]; then if [[ -f "${PROPERTIES_FILE:-}" ]]; then
echo "Warning: directory non trovata: $ENV_DIR" >&2 # Estrae endpoint ignorando commenti e spazi, supportando anche endpoint = valore
exit 0 endpoint_raw="$({ grep -E '^[[:space:]]*endpoint[[:space:]]*=' "${PROPERTIES_FILE}" | tail -n1 || true; } | sed -E 's/^[[:space:]]*endpoint[[:space:]]*=[[:space:]]*//')"
fi elif [[ -d "$ENV_DIR" ]]; then
# Estrae endpoint da qualsiasi file .env nella directory dell'ambiente # Estrae endpoint da qualsiasi file .env nella directory dell'ambiente
endpoint_raw="$({ grep -r '^[[:space:]]*endpoint[[:space:]]*=' "$ENV_DIR" 2>/dev/null | head -n1 || true; } | sed -E 's/^[^:]*:[[:space:]]*endpoint[[:space:]]*=[[:space:]]*//')" endpoint_raw="$({ grep -r '^[[:space:]]*endpoint[[:space:]]*=' "$ENV_DIR" 2>/dev/null | head -n1 || true; } | sed -E 's/^[^:]*:[[:space:]]*endpoint[[:space:]]*=[[:space:]]*//')"
else
echo "Warning: file non trovato: ${PROPERTIES_FILE:-<non impostato>} e directory non trovata: $ENV_DIR" >&2
exit 0
fi
# Rimuove eventuali virgolette e spazi ai bordi # Rimuove eventuali virgolette e spazi ai bordi
endpoint="$(echo "$endpoint_raw" | sed -E 's/^[[:space:]"\x27]+//; s/[[:space:]"\x27]+$//')" endpoint="$(echo "$endpoint_raw" | sed -E 's/^[[:space:]"\x27]+//; s/[[:space:]"\x27]+$//')"
if [[ -z "$endpoint" ]]; then if [[ -z "$endpoint" ]]; then
echo "La chiave endpoint non e valorizzata in $PROPERTIES_FILE" >&2 echo "La chiave endpoint non e valorizzata in ${PROPERTIES_FILE:-<non impostato>}" >&2
exit 0 exit 0
fi fi
+218
View File
@@ -0,0 +1,218 @@
#!/usr/bin/env bash
set -euo pipefail
ACTION=""
NAMESPACE=""
DRY_RUN=false
usage() {
cat <<EOF
Uso:
$0 hibernate [--namespace NAMESPACE] [--dry-run]
$0 resume [--namespace NAMESPACE] [--dry-run]
Esempi:
# Mostra cosa verrebbe ibernato in tutti i namespace
$0 hibernate --dry-run
# Iberna tutti i cluster CNPG
$0 hibernate
# Iberna solo i cluster di un namespace
$0 hibernate --namespace produzione
# Mostra cosa verrebbe riattivato
$0 resume --dry-run
# Riattiva tutti i cluster CNPG
$0 resume
EOF
}
#
# Parsing argomenti
#
if [[ $# -lt 1 ]]; then
usage
exit 1
fi
ACTION="$1"
shift
case "$ACTION" in
hibernate|resume)
;;
*)
echo "Errore: azione non valida: $ACTION"
usage
exit 1
;;
esac
while [[ $# -gt 0 ]]; do
case "$1" in
--namespace|-n)
NAMESPACE="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
--help|-h)
usage
exit 0
;;
*)
echo "Errore: parametro sconosciuto: $1"
usage
exit 1
;;
esac
done
#
# Controlli prerequisiti
#
command -v kubectl >/dev/null 2>&1 || {
echo "Errore: kubectl non trovato"
exit 1
}
kubectl cnpg version >/dev/null 2>&1 || {
echo "Errore: plugin kubectl-cnpg non disponibile o non funzionante"
exit 1
}
#
# Recupero cluster CNPG
#
# label applicata dallo script per tracciare le ibernazioni proprie
SCRIPT_LABEL="cnpg-maintenance-hibernated"
JSONPATH='{range .items[*]}{.metadata.namespace}{";"}{.metadata.name}{";"}{.status.readyInstances}{";"}{.metadata.annotations.cnpg\.io/hibernation}{";"}{.metadata.labels.cnpg-maintenance-hibernated}{"\n"}{end}'
if [[ -n "$NAMESPACE" ]]; then
CLUSTERS=$(kubectl get cluster.postgresql.cnpg.io \
-n "$NAMESPACE" \
-o jsonpath="$JSONPATH")
else
CLUSTERS=$(kubectl get cluster.postgresql.cnpg.io \
-A \
-o jsonpath="$JSONPATH")
fi
if [[ -z "$CLUSTERS" ]]; then
echo "Nessun cluster CNPG trovato."
exit 0
fi
echo
echo "========================================"
echo " CNPG BULK OPERATION"
echo "========================================"
echo "Azione: $ACTION"
if [[ -n "$NAMESPACE" ]]; then
echo "Namespace: $NAMESPACE"
else
echo "Namespace: TUTTI"
fi
echo "Dry-run: $DRY_RUN"
echo "========================================"
echo
#
# Elaborazione cluster
#
while IFS=";" read -r NS CLUSTER READY_INSTANCES HIBERNATION_ANNOTATION SCRIPT_HIBERNATED; do
[[ -z "$CLUSTER" ]] && continue
if [[ "$ACTION" == "hibernate" ]]; then
# skip clusters con no ready instances (già ibernati o non attivi)
if [[ -z "$READY_INSTANCES" || "$READY_INSTANCES" -lt 1 ]]; then
echo "Cluster: $NS/$CLUSTER — SALTATO (readyInstances=${READY_INSTANCES:-0}, già ibernato o non attivo)"
echo
continue
fi
else
# resume: solo cluster ibernati da questo script (label presente) e con annotazione CNPG attiva
if [[ "$SCRIPT_HIBERNATED" != "true" || "$HIBERNATION_ANNOTATION" != "on" ]]; then
echo "Cluster: $NS/$CLUSTER — SALTATO (non ibernato da questo script)"
echo
continue
fi
fi
echo "Cluster: $NS/$CLUSTER (readyInstances=${READY_INSTANCES:-0}, hibernation=${HIBERNATION_ANNOTATION:-none}, script-label=${SCRIPT_HIBERNATED:-none})"
if [[ "$ACTION" == "hibernate" ]]; then
CMD=(
kubectl
cnpg
hibernate
on
"$CLUSTER"
-n
"$NS"
)
else
CMD=(
kubectl
cnpg
hibernate
off
"$CLUSTER"
-n
"$NS"
)
fi
if [[ "$DRY_RUN" == true ]]; then
echo "[DRY-RUN] ${CMD[*]}"
if [[ "$ACTION" == "hibernate" ]]; then
echo "[DRY-RUN] kubectl label cluster.postgresql.cnpg.io $CLUSTER -n $NS ${SCRIPT_LABEL}=true"
else
echo "[DRY-RUN] kubectl label cluster.postgresql.cnpg.io $CLUSTER -n $NS ${SCRIPT_LABEL}-"
fi
else
echo "Esecuzione: ${CMD[*]}"
if "${CMD[@]}"; then
echo "OK: $NS/$CLUSTER"
if [[ "$ACTION" == "hibernate" ]]; then
kubectl label cluster.postgresql.cnpg.io "$CLUSTER" -n "$NS" "${SCRIPT_LABEL}=true" --overwrite
else
kubectl label cluster.postgresql.cnpg.io "$CLUSTER" -n "$NS" "${SCRIPT_LABEL}-"
fi
else
echo "ERRORE: $NS/$CLUSTER"
fi
fi
echo
done <<< "$CLUSTERS"
echo "========================================"
echo "Operazione completata"
echo "========================================"
+287
View File
@@ -0,0 +1,287 @@
# Clone and Transform Script
Script bash per clonare un repository GitLab usando API e token privato, con sostituzione automatica di stringhe nei file.
## Prerequisiti
- `bash` (4.0+)
- `git`
- `curl` (opzionale, per verificare l'accesso)
- Token privato GitLab con almeno scope `api` o `read_repository`
## Preparazione
### 1. Creare il file `transform.txt`
Crea un file `transform.txt` nella stessa directory dello script con il formato:
```txt
<old_string>=<new_string>
<old_string>=<new_string>
...
```
**Esempio di transform.txt:**
```txt
# Configurazioni di sviluppo verso produzione
DATABASE_HOST=localhost=DATABASE_HOST=prod.example.com
DATABASE_PORT=5432=DATABASE_PORT=5432
API_ENDPOINT=http://localhost:8080=API_ENDPOINT=https://api.prod.com
ENVIRONMENT=development=ENVIRONMENT=production
LOG_LEVEL=debug=LOG_LEVEL=info
SECRET_KEY=dev-secret=SECRET_KEY=prod-secret-key-xyz
```
### 2. Ottenere un token GitLab
1. Accedi a GitLab
2. Vai a **Profile → Access Tokens**
3. Crea un nuovo token con scopes:
- `api` - accesso completo
- oppure `read_repository` - solo lettura
4. Copia il token (non sarà più visibile dopo)
### 3. Rendere eseguibile lo script
```bash
chmod +x clone-and-transform.sh
```
## Utilizzo
### Sintassi base
```bash
./clone-and-transform.sh -u <URL> -t <TOKEN> -r <REPO-PATH> -d <DEST-DIR>
```
### Parametri
| Parametro | Breve | Descrizione | Esempio |
|-----------|-------|-------------|---------|
| `--url` | `-u` | URL base di GitLab | `https://gitlab.com` |
| `--token` | `-t` | Token di accesso privato | `glpat-xxxxx` |
| `--repo` | `-r` | Percorso del repository | `mygroup/myproject` |
| `--dest` | `-d` | Directory di destinazione | `./cloned-repo` |
| `--transform` | `-f` | Path del file transform.txt | `./transform.txt` (default) |
| `--help` | `-h` | Mostra l'aiuto | - |
### Esempi di utilizzo
**Esempio 1: Clone con trasformazioni usando file default**
```bash
./clone-and-transform.sh \
-u https://gitlab.com \
-t glpat-xxxxxxxxxxxx \
-r mygroup/myproject \
-d ./my-cloned-repo
```
**Esempio 2: Clone con file transform.txt personalizzato**
```bash
./clone-and-transform.sh \
-u https://gitlab.internal.com \
-t glpat-xxxxxxxxxxxx \
-r company/backend \
-d ./backend-clone \
-f ./custom-transform.txt
```
**Esempio 3: Clone da GitLab Enterprise**
```bash
./clone-and-transform.sh \
-u https://gitlab.mycompany.com \
-t glpat-xxxxxxxxxxxx \
-r products/idcidp \
-d ./idcidp-prod
```
## Come funziona lo script
1. **Validazione**: Verifica i parametri e l'esistenza di `transform.txt`
2. **Clone**: Clona il repository usando autenticazione OAuth2
3. **Scoperta directory**: Identifica directory `kubernetes`, `containers`, `conf`
4. **Trasformazione**: Per ogni file trovato applica le sostituzioni definite in `transform.txt`
5. **Report**: Mostra il number di file modificati e suggerisce i prossimi passi
### Directory elaborate
Lo script elabora automaticamente questi percorsi:
- `kubernetes/`
- `containers/`
- `conf/`
Se una directory non esiste, viene saltata con un avvertimento.
### File binari
I file binari (immagini, archivi, ecc.) vengono automaticamente saltati per evitare corruzioni.
## Output esempio
```
[INFO] Starting GitLab repository clone and transformation...
[INFO] GitLab URL: https://gitlab.com
[INFO] Repository: mygroup/myproject
[INFO] Destination: ./my-cloned-repo
[INFO] Transform file: ./transform.txt
[INFO] Cloning repository...
[INFO] Repository cloned successfully
[INFO] Found directory: kubernetes
[INFO] Found directory: containers
[INFO] Applying transformations from ./transform.txt...
[INFO] Processing directory: kubernetes
[INFO] Modified: ./my-cloned-repo/kubernetes/deployment.yaml
[INFO] Modified: ./my-cloned-repo/kubernetes/service.yaml
[INFO] Processing directory: containers
[INFO] ========== TRANSFORMATION SUMMARY ==========
Total files processed: 12
Files modified: 5
[INFO] ==========================================
[INFO] Transformation completed successfully!
Next steps:
1. Review changes: cd my-cloned-repo && git diff
2. Commit changes: git add . && git commit -m 'Apply transformations'
3. Push changes: git push
```
## Verifica delle modifiche
Dopo l'esecuzione, è consigliato verificare le modifiche:
```bash
cd ./my-cloned-repo
git diff
```
Per vedere solo i file modificati:
```bash
git status
```
## Commit e Push
Se le modifiche sono corrette:
```bash
git add .
git commit -m "Apply environment transformations"
git push
```
## Troubleshooting
### Errore: "Failed to clone repository"
**Causa**: Token non valido o senza permessi
**Soluzione**:
1. Verifica il token nel profilo GitLab
2. Assicurati che il token abbia scope `api`
3. Verifica che il repository sia accessibile con il token
### Errore: "Transform file not found"
**Causa**: Il file `transform.txt` non esiste
**Soluzione**:
1. Crea il file `transform.txt` nella directory dove esegui lo script
2. Usa l'opzione `-f` per specificare un percorso personalizzato
### Nessun file modificato
**Causa**: Le stringhe in `transform.txt` non corrispondono ai file
**Soluzione**:
1. Verifica il contenuto dei file nella directory clonata
2. Assicurati che le stringhe in `transform.txt` siano corrette
3. Usa pattern più generici se necessario
### Errore di escaping in stringhe complesse
Se le stringhe contengono caratteri speciali (barre, ampersand, etc.):
**Lo script gestisce automaticamente l'escaping**, ma se riscontri problemi:
1. Usa sequenze di escape nel file transform.txt
2. Ad esempio, per una barra inversa: `path\\old=path\\new`
## Sicurezza
### Best practices
1. **Non** commettere il token nel repository
2. Usa variabili d'ambiente per il token:
```bash
./clone-and-transform.sh \
-u https://gitlab.com \
-t $GITLAB_TOKEN \
-r mygroup/myproject \
-d ./clone
```
3. Usa file `.gitignore` per `transform.txt` se contiene dati sensibili:
```bash
echo "transform.txt" >> .gitignore
```
4. Revoca il token dopo l'uso se è ad uso singolo
## Opzioni avanzate
### Usare con script di automazione
```bash
#!/bin/bash
export GITLAB_TOKEN="glpat-xxxxx"
export GITLAB_URL="https://gitlab.internal.com"
./clone-and-transform.sh \
-u $GITLAB_URL \
-t $GITLAB_TOKEN \
-r company/backend \
-d ./backend-clone
```
### Variazione per ambienti multipli
Crea diversi file di trasformazione:
- `transform-dev.txt`
- `transform-qa.txt`
- `transform-prod.txt`
```bash
# Per development
./clone-and-transform.sh \
-u https://gitlab.com \
-t $GITLAB_TOKEN \
-r mygroup/myproject \
-d ./clone-dev \
-f transform-dev.txt
# Per production
./clone-and-transform.sh \
-u https://gitlab.com \
-t $GITLAB_TOKEN \
-r mygroup/myproject \
-d ./clone-prod \
-f transform-prod.txt
```
## Changelog
### v1.0 (Initial Release)
- Clone repository con token privato
- Applicazione di trasformazioni su directory specifiche
- Supporto per file UTF-8
- Skip automatico file binari
- Output colorato e dettagliato
## Licenza
MIT
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
set -Eeuo pipefail
# Configuration
SOURCE_ENV="${1:-dbdev}"
DBNAME="${2:-geco}"
POD_NAME="${3:-postgresql-1-postgresql-0}"
CONTAINER_NAME="${4:-postgresql-server}"
BACKUP_REMOTE_DIR="${5:-/shared/backup}"
BACKUP_LOCAL_DIR="${6:-.}"
COMPRESSION="${7:-true}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Logging function
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $*"
}
error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR:${NC} $*" >&2
exit 1
}
warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARN:${NC} $*"
}
# Determine backup file extension
if [ "${COMPRESSION}" = "true" ]; then
BACKUP_FILE="${DBNAME}-${SOURCE_ENV}-$(date +%Y%m%d-%H%M%S).backup.gz"
DUMP_FORMAT="c"
else
BACKUP_FILE="${DBNAME}-${SOURCE_ENV}-$(date +%Y%m%d-%H%M%S).backup"
DUMP_FORMAT="c"
fi
log "Inizio backup database PostgreSQL"
log "Namespace: ${SOURCE_ENV}"
log "Pod: ${POD_NAME}"
log "Database: ${DBNAME}"
log "File di backup: ${BACKUP_FILE}"
# Check if namespace exists
if ! kubectl get namespace "${SOURCE_ENV}" &>/dev/null; then
error "Namespace '${SOURCE_ENV}' non trovato"
fi
# Check if pod exists
if ! kubectl get pod "${POD_NAME}" -n "${SOURCE_ENV}" &>/dev/null; then
error "Pod '${POD_NAME}' non trovato nel namespace '${SOURCE_ENV}'"
fi
# Check if pod is running
POD_STATUS=$(kubectl get pod "${POD_NAME}" -n "${SOURCE_ENV}" -o jsonpath='{.status.phase}')
if [ "${POD_STATUS}" != "Running" ]; then
error "Pod '${POD_NAME}' non è in stato Running (stato attuale: ${POD_STATUS})"
fi
log "Creazione backup sul pod..."
# Create backup on remote pod
if [ "${COMPRESSION}" = "true" ]; then
kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${SOURCE_ENV}" \
-- pg_dump -U postgres -d "${DBNAME}" -F "${DUMP_FORMAT}" -b -v | gzip > "${BACKUP_LOCAL_DIR}/${BACKUP_FILE}" || error "Backup fallito"
else
kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${SOURCE_ENV}" \
-- pg_dump -U postgres -d "${DBNAME}" -F "${DUMP_FORMAT}" -b -v > "${BACKUP_LOCAL_DIR}/${BACKUP_FILE}" || error "Backup fallito"
fi
log "Backup completato"
# Verify file exists and has content
if [ ! -f "${BACKUP_LOCAL_DIR}/${BACKUP_FILE}" ]; then
error "File di backup non trovato: ${BACKUP_LOCAL_DIR}/${BACKUP_FILE}"
fi
FILE_SIZE=$(du -h "${BACKUP_LOCAL_DIR}/${BACKUP_FILE}" | cut -f1)
if [ -z "${FILE_SIZE}" ] || [ "${FILE_SIZE}" = "0" ]; then
error "File di backup vuoto o non valido"
fi
log "✅ Backup completato con successo"
log "Percorso: ${BACKUP_LOCAL_DIR}/${BACKUP_FILE}"
log "Dimensione: ${FILE_SIZE}"
log "Inizio trasferimento file di backup su server remoto..."
if sshpass -p "POC-25_sts" scp "${BACKUP_LOCAL_DIR}/${BACKUP_FILE}" barucci@10.20.1.101:/home/barucci/froms/; then
log "✅ File trasferito con successo su 10.20.1.101"
log "Destinazione: barucci@10.20.1.101:/home/barucci/froms/${BACKUP_FILE}"
else
warn "Trasferimento file fallito, ma backup locale è disponibile in ${BACKUP_LOCAL_DIR}/${BACKUP_FILE}"
fi
+773
View File
@@ -0,0 +1,773 @@
#!/bin/bash
################################################################################
# Script: clone-and-transform.sh
# Description: Clone GitLab repository using private token and apply
# string substitutions from transform.env
# Usage: ./clone-and-transform.sh <gitlab-url> <token> <repo-path> <dest-dir>
################################################################################
set -e # Exit on error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
# Function to display usage
usage() {
cat << EOF
Usage: $0 [OPTIONS]
OPTIONS:
-u, --url <URL> GitLab API base URL (e.g., https://gitlab.com)
-t, --token <TOKEN> Private access token
-r, --repo <REPO-PATH> Repository path (e.g., group/project)
-d, --dest <DEST-DIR> Destination directory for clone / transformation source
-f, --transform <FILE> Path to transform.env file (default: ./transform.env)
--gitea-repo <REPO-PATH> Repository path to clone from Gitea (default: same as --repo)
-transformation Skip GitLab clone; run Gitea clone + transform + commit/push
-h, --help Show this help message
Example:
$0 -u https://gitlab.com -t glpat-xxxxx -r mygroup/myproject -d ./cloned-repo
$0 -transformation -d ./existing-repo --gitea-repo mygroup/myproject -f ./transform.env
EOF
exit 1
}
# Default values
GITLAB_URL=""
PRIVATE_TOKEN=""
REPO_PATH=""
DEST_DIR=""
TRANSFORM_FILE="./transform.env"
GITEA_URL="https://git.italiadatacenter.com"
GITEA_TOKEN="65fc5f07365bd3f89c5b9ddca3d2d2e7b3777a88"
GITEA_REPO_PATH=""
GITEA_DEST_DIR="target-repo"
TRANSFORMATION_ONLY=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-u|--url)
GITLAB_URL="$2"
shift 2
;;
-t|--token)
PRIVATE_TOKEN="$2"
shift 2
;;
-r|--repo)
REPO_PATH="$2"
shift 2
;;
-d|--dest)
DEST_DIR="$2"
shift 2
;;
-f|--transform)
TRANSFORM_FILE="$2"
shift 2
;;
--gitea-repo)
GITEA_REPO_PATH="$2"
shift 2
;;
-transformation)
TRANSFORMATION_ONLY=true
shift
;;
-h|--help)
usage
;;
*)
print_error "Unknown option: $1"
usage
;;
esac
done
# Validate required parameters
if [[ "$TRANSFORMATION_ONLY" == true ]]; then
if [[ -z "$DEST_DIR" ]]; then
print_error "Missing required parameter: --dest"
usage
fi
else
if [[ -z "$GITLAB_URL" ]] || [[ -z "$PRIVATE_TOKEN" ]] || [[ -z "$REPO_PATH" ]] || [[ -z "$DEST_DIR" ]]; then
print_error "Missing required parameters"
usage
fi
fi
# Check if transform.env exists
if [[ ! -f "$TRANSFORM_FILE" ]]; then
print_error "Transform file not found: $TRANSFORM_FILE"
exit 1
fi
# Validate transform.env format
if ! grep -Eq '^[[:space:]]*[^#[:space:]].*===.*$' "$TRANSFORM_FILE"; then
print_error "Transform file format invalid. Expected: <old_string>===<new_string>"
exit 1
fi
if [[ "$TRANSFORMATION_ONLY" == true ]]; then
print_info "Mode: transformation only (GitLab clone skipped)"
else
print_info "Starting GitLab repository clone and transformation..."
print_info "GitLab URL: $GITLAB_URL"
print_info "Repository: $REPO_PATH"
fi
print_info "Destination: $DEST_DIR"
print_info "Transform file: $TRANSFORM_FILE"
print_info "Gitea instance: $GITEA_URL"
print_info "Gitea destination: $GITEA_DEST_DIR"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HTTPROUTE_TEMPLATE_FILE="$SCRIPT_DIR/httproute.yaml"
if [[ "$TRANSFORMATION_ONLY" == false ]]; then
# Build repository URLs
BASE_URL="${GITLAB_URL%/}"
if [[ ! "$BASE_URL" =~ ^https?:// ]]; then
print_error "GitLab URL must start with http:// or https://"
exit 1
fi
TARGET_REPO_URL="${BASE_URL}/${REPO_PATH}.git"
AUTH_REPO_URL="$(printf '%s\n' "$TARGET_REPO_URL" | sed -E "s#^(https?://)#\\1oauth2:${PRIVATE_TOKEN}@#")"
# Always use a fresh clone to avoid stale/outdated repository state
if [[ -e "$DEST_DIR" ]]; then
if [[ -z "$DEST_DIR" || "$DEST_DIR" == "/" || "$DEST_DIR" == "." ]]; then
print_error "Refusing to remove unsafe destination path: $DEST_DIR"
exit 1
fi
print_info "Destination already exists, removing it before clone: $DEST_DIR"
rm -rf -- "$DEST_DIR"
fi
print_info "Cloning repository..."
print_info "Running: git clone $(printf '%s' "$AUTH_REPO_URL" | sed -E 's#(https?://)[^@]+@#\1***@#') $DEST_DIR"
GIT_TERMINAL_PROMPT=0 git clone --quiet "$AUTH_REPO_URL" "$DEST_DIR" 2>/dev/null || {
print_error "Failed to clone repository"
print_info "Ensure your token has 'api' or 'read_repository' scope"
exit 1
}
print_info "Repository cloned successfully"
fi # end TRANSFORMATION_ONLY skip
# Clone additional repository from Gitea into target-repo
if [[ -z "$GITEA_REPO_PATH" ]]; then
GITEA_REPO_PATH="$REPO_PATH"
fi
GITEA_BASE_URL="${GITEA_URL%/}"
if [[ ! "$GITEA_BASE_URL" =~ ^https?:// ]]; then
print_error "Gitea URL must start with http:// or https://"
exit 1
fi
GITEA_TARGET_REPO_URL="${GITEA_BASE_URL}/${GITEA_REPO_PATH}.git"
GITEA_AUTH_REPO_URL="$(printf '%s\n' "$GITEA_TARGET_REPO_URL" | sed -E "s#^(https?://)#\\1${GITEA_TOKEN}@#")"
if [[ -e "$GITEA_DEST_DIR" ]]; then
if [[ -z "$GITEA_DEST_DIR" || "$GITEA_DEST_DIR" == "/" || "$GITEA_DEST_DIR" == "." ]]; then
print_error "Refusing to remove unsafe destination path: $GITEA_DEST_DIR"
exit 1
fi
print_info "Gitea destination already exists, removing it before clone: $GITEA_DEST_DIR"
rm -rf -- "$GITEA_DEST_DIR"
fi
print_info "Cloning additional repository from Gitea..."
print_info "Running: git clone $(printf '%s' "$GITEA_AUTH_REPO_URL" | sed -E 's#(https?://)[^@]+@#\1***@#') $GITEA_DEST_DIR"
GIT_TERMINAL_PROMPT=0 git clone --quiet "$GITEA_AUTH_REPO_URL" "$GITEA_DEST_DIR" 2>/dev/null || {
print_error "Failed to clone additional Gitea repository"
print_info "Check Gitea token permissions and repository path"
exit 1
}
print_info "Additional Gitea repository cloned successfully"
# In transformation-only mode, seed DEST_DIR from the freshly cloned Gitea repo
if [[ "$TRANSFORMATION_ONLY" == true ]]; then
if [[ -e "$DEST_DIR" ]]; then
if [[ -z "$DEST_DIR" || "$DEST_DIR" == "/" || "$DEST_DIR" == "." ]]; then
print_error "Refusing to remove unsafe destination path: $DEST_DIR"
exit 1
fi
print_info "Removing existing DEST_DIR before seeding from Gitea: $DEST_DIR"
rm -rf -- "$DEST_DIR"
fi
print_info "Copying Gitea clone ($GITEA_DEST_DIR) -> $DEST_DIR ..."
cp -R "$GITEA_DEST_DIR" "$DEST_DIR"
print_info "Copy completed"
fi
# Check if target directories exist
DIRS_TO_PROCESS=("kubernetes" "container" "conf" "envs")
FOUND_DIRS=()
for dir in "${DIRS_TO_PROCESS[@]}"; do
if [[ -d "$DEST_DIR/$dir" ]]; then
FOUND_DIRS+=("$dir")
print_info "Found directory: $dir"
fi
done
# Also search for envs directories nested in subdirectories
while IFS= read -r envs_abs; do
envs_rel="${envs_abs#${DEST_DIR}/}"
# Skip top-level envs (already handled above)
if [[ "$envs_rel" != "envs" ]]; then
FOUND_DIRS+=("$envs_rel")
print_info "Found nested envs directory: $envs_rel"
fi
done < <(find "$DEST_DIR" -mindepth 2 -type d -name "envs" 2>/dev/null)
if [[ ${#FOUND_DIRS[@]} -eq 0 ]]; then
print_warning "No target directories (kubernetes/container/conf/envs) found in cloned repository"
fi
# Function to apply transformations to a file
apply_transformations() {
local file="$1"
local temp_file
local changes=0
temp_file="$(mktemp "${file}.tmp.XXXXXX")"
# Create a copy
cp "$file" "$temp_file"
# Apply each transformation from transform.env (format: old_string===new_string)
while IFS= read -r line || [[ -n "$line" ]]; do
# Skip empty lines and comments
[[ -z "$line" ]] && continue
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" != *"==="* ]] && continue
local old_string new_string
old_string="${line%%===*}"
new_string="${line#*===}"
# Trim whitespace
old_string=$(echo "$old_string" | xargs)
new_string=$(echo "$new_string" | xargs)
# Check if substitution would occur
if grep -Fq -- "$old_string" "$temp_file" 2>/dev/null; then
# Use sed with proper escaping for replacement
local old_escaped new_escaped
old_escaped=$(printf '%s\n' "$old_string" | sed 's/[&|\\]/\\&/g')
new_escaped=$(printf '%s\n' "$new_string" | sed 's/[&|\\]/\\&/g')
sed -i "s|${old_escaped}|${new_escaped}|g" "$temp_file"
changes=$((changes + 1))
fi
done < "$TRANSFORM_FILE"
# Compare and update if changes were made
if ! diff -q "$file" "$temp_file" > /dev/null 2>&1; then
mv "$temp_file" "$file"
return 0
else
rm "$temp_file"
return 1
fi
}
# Function to apply automatic placeholder transformation: %string% -> <string>
apply_placeholder_transformations() {
local file="$1"
local temp_file
temp_file="$(mktemp "${file}.tmp.XXXXXX")"
cp "$file" "$temp_file"
# Convert placeholders enclosed in %...% to <...>
sed -i -E 's/%([^%]+)%/<\1>/g' "$temp_file"
if ! diff -q "$file" "$temp_file" > /dev/null 2>&1; then
mv "$temp_file" "$file"
return 0
else
rm "$temp_file"
return 1
fi
}
# Apply transformations to files in target directories
print_info "Applying transformations from $TRANSFORM_FILE..."
total_files=0
modified_files=0
for dir in "${FOUND_DIRS[@]}"; do
target_path="$DEST_DIR/$dir"
print_info "Processing directory: $dir"
# Find all files in the directory and its subdirectories
while IFS= read -r file; do
total_files=$((total_files + 1))
# Skip binary files
if file "$file" | grep -q "binary"; then
print_warning "Skipping binary file: $file"
continue
fi
if apply_transformations "$file"; then
print_info " Modified: $file"
modified_files=$((modified_files + 1))
fi
done < <(find "$target_path" -type f 2>/dev/null)
done
# Apply automatic %string% -> <string> transformation to all files in target directory
print_info "Applying automatic placeholder transformation (%...% -> <...>) in $DEST_DIR..."
placeholder_total_files=0
placeholder_modified_files=0
while IFS= read -r file; do
placeholder_total_files=$((placeholder_total_files + 1))
# Skip binary files
if file "$file" | grep -q "binary"; then
print_warning "Skipping binary file: $file"
continue
fi
if apply_placeholder_transformations "$file"; then
print_info " Placeholder updated: $file"
placeholder_modified_files=$((placeholder_modified_files + 1))
fi
done < <(find "$DEST_DIR" -type f 2>/dev/null)
# Post-transformation directory operations on DEST_DIR
if [[ "$TRANSFORMATION_ONLY" == false ]]; then
print_info "Applying post-transformation directory operations on $DEST_DIR..."
# 1) Rename container -> containers
if [[ -d "$DEST_DIR/container" ]]; then
mv -- "$DEST_DIR/container" "$DEST_DIR/containers"
print_info "Renamed: container -> containers"
else
print_warning "Directory not found, skipping rename: $DEST_DIR/container"
fi
# 2) Rename conf -> env (fail if env already exists)
if [[ -d "$DEST_DIR/conf" ]]; then
if [[ -e "$DEST_DIR/env" ]]; then
print_error "Cannot rename conf -> env: destination $DEST_DIR/env already exists"
exit 1
fi
mv -- "$DEST_DIR/conf" "$DEST_DIR/env"
print_info "Renamed: conf -> env"
else
print_warning "Directory not found, skipping rename: $DEST_DIR/conf"
fi
# 3) Remove .git directory
if [[ -d "$DEST_DIR/.git" ]]; then
rm -rf -- "$DEST_DIR/.git"
print_info "Removed: $DEST_DIR/.git"
else
print_warning "Directory not found, skipping removal: $DEST_DIR/.git"
fi
# 4) Remove .gitlab-ci.yml
if [[ -f "$DEST_DIR/.gitlab-ci.yml" ]]; then
rm -f -- "$DEST_DIR/.gitlab-ci.yml"
print_info "Removed: $DEST_DIR/.gitlab-ci.yml"
else
print_warning "File not found, skipping removal: $DEST_DIR/.gitlab-ci.yml"
fi
# Summary
echo ""
print_info "========== TRANSFORMATION SUMMARY =========="
echo "Total files processed: $total_files"
echo "Files modified: $modified_files"
echo "Placeholder files scanned: $placeholder_total_files"
echo "Placeholder files modified: $placeholder_modified_files"
print_info "=========================================="
if [[ $modified_files -gt 0 ]]; then
print_info "Transformation completed successfully!"
else
print_warning "No files were modified. Check transform.env contents and patterns."
fi
# Rename configuration.txt -> values.env under env directory
if [[ -d "$DEST_DIR/env" ]]; then
print_info "Rinomino configuration.txt -> values.env in $DEST_DIR/env ..."
while IFS= read -r f; do
target_path="$(dirname "$f")/values.env"
mv -- "$f" "$target_path"
print_info " Renamed: $f -> $target_path"
done < <(find "$DEST_DIR/env" -type f -name "configuration.txt")
else
print_warning "Directory $DEST_DIR/env non trovata, skip rename configuration.txt"
fi
# Move env/configuration.env to DEST_DIR root as properties.env
if [[ -f "$DEST_DIR/env/configuration.env" ]]; then
if [[ -f "$DEST_DIR/properties.env" ]]; then
rm -f -- "$DEST_DIR/properties.env"
print_info "Removed existing file: $DEST_DIR/properties.env"
fi
mv -- "$DEST_DIR/env/configuration.env" "$DEST_DIR/properties.env"
print_info "Moved: $DEST_DIR/env/configuration.env -> $DEST_DIR/properties.env"
else
print_warning "File non trovato, skip move: $DEST_DIR/env/configuration.env"
fi
# If an Ingress resource exists in kubernetes YAMLs, generate httproute.yaml from template
# and remove Ingress resource definitions from manifests.
K8S_DIR="$DEST_DIR/kubernetes"
if [[ -d "$K8S_DIR" ]]; then
print_info "Checking Kubernetes manifests for Ingress resources in $K8S_DIR ..."
ingress_found=false
service_name=""
ingress_service_name=""
ingress_port=""
while IFS= read -r -d '' k8s_file; do
if [[ -z "$service_name" ]]; then
service_name="$(awk '
/^[[:space:]]*kind:[[:space:]]*Service([[:space:]]|$)/ { in_service=1; next }
in_service && /^[[:space:]]*kind:[[:space:]]*/ { in_service=0 }
in_service && /^[[:space:]]*name:[[:space:]]*/ {
value=$0
sub(/^[[:space:]]*name:[[:space:]]*/, "", value)
gsub(/[[:space:]]+$/, "", value)
print value
exit
}
' "$k8s_file")"
fi
if grep -Eq '^[[:space:]]*kind:[[:space:]]*Ingress([[:space:]]|$)' "$k8s_file"; then
ingress_found=true
if [[ -z "$ingress_port" ]]; then
ingress_port="$(awk '
/^[[:space:]]*kind:[[:space:]]*Ingress([[:space:]]|$)/ { in_ingress=1; next }
in_ingress && /^[[:space:]]*kind:[[:space:]]*/ { in_ingress=0 }
in_ingress && /^[[:space:]]*number:[[:space:]]*[0-9]+/ {
value=$0
sub(/.*number:[[:space:]]*/, "", value)
gsub(/[^0-9].*$/, "", value)
print value
exit
}
' "$k8s_file")"
fi
if [[ -z "$ingress_service_name" ]]; then
ingress_service_name="$(awk '
/^[[:space:]]*kind:[[:space:]]*Ingress([[:space:]]|$)/ { in_ingress=1; next }
in_ingress && /^[[:space:]]*kind:[[:space:]]*/ { in_ingress=0 }
in_ingress && /^[[:space:]]*name:[[:space:]]*/ {
value=$0
sub(/^[[:space:]]*name:[[:space:]]*/, "", value)
gsub(/[[:space:]]+$/, "", value)
print value
exit
}
' "$k8s_file")"
fi
fi
done < <(find "$K8S_DIR" -type f \( -name "*.yaml" -o -name "*.yml" \) -print0)
if [[ "$ingress_found" == true ]]; then
if [[ ! -f "$HTTPROUTE_TEMPLATE_FILE" ]]; then
print_error "HTTPRoute template not found: $HTTPROUTE_TEMPLATE_FILE"
exit 1
fi
if [[ -z "$service_name" ]]; then
service_name="$ingress_service_name"
fi
if [[ -z "$service_name" ]]; then
print_error "Cannot create HTTPRoute: service name not found in Kubernetes YAML"
exit 1
fi
if [[ -z "$ingress_port" ]]; then
print_error "Cannot create HTTPRoute: ingress port number not found in Kubernetes YAML"
exit 1
fi
httproute_output="$K8S_DIR/httproute.yaml"
sed -e "s|<svc-name>|$service_name|g" -e "s|<port>|$ingress_port|g" "$HTTPROUTE_TEMPLATE_FILE" > "$httproute_output"
print_info "Created HTTPRoute file: $httproute_output"
while IFS= read -r -d '' k8s_file; do
temp_k8s_file="$(mktemp "${k8s_file}.tmp.XXXXXX")"
awk '
function flush_doc() {
if (!doc_started) {
return
}
if (!doc_is_ingress) {
if (output_count > 0) {
printf "---\n"
}
printf "%s", doc
output_count++
}
doc = ""
doc_started = 0
doc_is_ingress = 0
}
/^[[:space:]]*---[[:space:]]*$/ {
flush_doc()
next
}
{
doc_started = 1
doc = doc $0 "\n"
if ($0 ~ /^[[:space:]]*kind:[[:space:]]*Ingress([[:space:]]|$)/) {
doc_is_ingress = 1
}
}
END {
flush_doc()
}
' "$k8s_file" > "$temp_k8s_file"
if [[ ! -s "$temp_k8s_file" ]]; then
rm -f -- "$k8s_file"
rm -f -- "$temp_k8s_file"
print_info "Removed ingress-only manifest file: $k8s_file"
else
mv -- "$temp_k8s_file" "$k8s_file"
fi
done < <(find "$K8S_DIR" -type f \( -name "*.yaml" -o -name "*.yml" \) -print0)
print_info "Ingress resource definitions removed from Kubernetes manifests"
else
print_info "No Ingress resource found in Kubernetes manifests"
fi
# If a PersistentVolume resource exists in kubernetes YAMLs,
# remove PersistentVolume resource definitions from manifests.
pv_found=false
while IFS= read -r -d '' k8s_file; do
if grep -Eq '^[[:space:]]*kind:[[:space:]]*PersistentVolume([[:space:]]|$)' "$k8s_file"; then
pv_found=true
break
fi
done < <(find "$K8S_DIR" -type f \( -name "*.yaml" -o -name "*.yml" \) -print0)
if [[ "$pv_found" == true ]]; then
print_info "PersistentVolume resources found, removing from Kubernetes manifests ..."
while IFS= read -r -d '' k8s_file; do
temp_k8s_file="$(mktemp "${k8s_file}.tmp.XXXXXX")"
awk '
function flush_doc() {
if (!doc_started) {
return
}
if (!doc_is_pv) {
if (output_count > 0) {
printf "---\n"
}
printf "%s", doc
output_count++
}
doc = ""
doc_started = 0
doc_is_pv = 0
}
/^[[:space:]]*---[[:space:]]*$/ {
flush_doc()
next
}
{
doc_started = 1
doc = doc $0 "\n"
if ($0 ~ /^[[:space:]]*kind:[[:space:]]*PersistentVolume([[:space:]]|$)/) {
doc_is_pv = 1
}
}
END {
flush_doc()
}
' "$k8s_file" > "$temp_k8s_file"
if [[ ! -s "$temp_k8s_file" ]]; then
rm -f -- "$k8s_file"
rm -f -- "$temp_k8s_file"
print_info "Removed persistent-volume-only manifest file: $k8s_file"
else
mv -- "$temp_k8s_file" "$k8s_file"
fi
done < <(find "$K8S_DIR" -type f \( -name "*.yaml" -o -name "*.yml" \) -print0)
print_info "PersistentVolume resource definitions removed from Kubernetes manifests"
else
print_info "No PersistentVolume resource found in Kubernetes manifests"
fi
# Normalize line endings to LF for kubernetes manifests.
while IFS= read -r -d '' k8s_file; do
sed -i 's/\r$//' "$k8s_file"
done < <(find "$K8S_DIR" -type f \( -name "*.yaml" -o -name "*.yml" \) -print0)
print_info "Normalized line endings to LF for Kubernetes YAML files"
else
print_warning "Directory not found, skipping Kubernetes ingress conversion: $K8S_DIR"
fi
# Conditional directory restructuring: check if src/ and containers/ exist,
# and if containers/ has exactly 1 subdirectory
print_info "Checking conditions for directory restructuring..."
SRC_PATH="$DEST_DIR/src"
CONTAINERS_PATH="$DEST_DIR/containers"
RESTRUCTURE=false
CONTAINERS_SUBDIR=""
if [[ -d "$SRC_PATH" ]]; then
print_info " ✓ Found: $SRC_PATH"
if [[ -d "$CONTAINERS_PATH" ]]; then
print_info " ✓ Found: $CONTAINERS_PATH"
# Count subdirectories in containers/
subdir_count=0
subdirs=()
while IFS= read -r subdir; do
subdirs+=("$subdir")
subdir_count=$((subdir_count + 1))
done < <(find "$CONTAINERS_PATH" -mindepth 1 -maxdepth 1 -type d)
if [[ $subdir_count -eq 1 ]]; then
CONTAINERS_SUBDIR="${subdirs[0]##*/}" # Extract basename
print_info " ✓ Found exactly 1 subdirectory in containers: $CONTAINERS_SUBDIR"
RESTRUCTURE=true
elif [[ $subdir_count -eq 0 ]]; then
print_warning " ✗ No subdirectories found in $CONTAINERS_PATH"
else
print_warning " ✗ Found $subdir_count subdirectories in $CONTAINERS_PATH (expected 1)"
fi
else
print_warning " ✗ Directory not found: $CONTAINERS_PATH"
fi
else
print_warning " ✗ Directory not found: $SRC_PATH"
fi
if [[ "$RESTRUCTURE" == true ]]; then
print_info "========== STARTING CONDITIONAL DIRECTORY RESTRUCTURING =========="
print_info "Target subdirectory name: $CONTAINERS_SUBDIR"
# 1) Rename src -> _src
if [[ -d "$SRC_PATH" ]]; then
mv -- "$SRC_PATH" "$DEST_DIR/_src"
print_info "Step 1/6: Renamed src -> _src"
fi
# 2) Create src/<containers_subdir>/
mkdir -p "$DEST_DIR/src/$CONTAINERS_SUBDIR"
print_info "Step 2/6: Created directory src/$CONTAINERS_SUBDIR"
# 3) Move _src -> src/<containers_subdir>/
mv -- "$DEST_DIR/_src" "$DEST_DIR/src/$CONTAINERS_SUBDIR/_src"
print_info "Step 3/6: Moved _src -> src/$CONTAINERS_SUBDIR/_src"
# 4) Rename _src -> src (within src/<containers_subdir>/)
mv -- "$DEST_DIR/src/$CONTAINERS_SUBDIR/_src" "$DEST_DIR/src/$CONTAINERS_SUBDIR/src"
print_info "Step 4/6: Renamed _src -> src/$CONTAINERS_SUBDIR/src"
# 5) Move *.json, *.js, .prettierrc from root to src/<containers_subdir>/
print_info "Step 5/6: Moving files from root to src/$CONTAINERS_SUBDIR/..."
# Move *.json files
while IFS= read -r json_file; do
if [[ -f "$json_file" ]]; then
mv -- "$json_file" "$DEST_DIR/src/$CONTAINERS_SUBDIR/"
print_info " Moved: $(basename "$json_file") -> src/$CONTAINERS_SUBDIR/"
fi
done < <(find "$DEST_DIR" -maxdepth 1 -type f -name "*.json")
# Move *.js files
while IFS= read -r js_file; do
if [[ -f "$js_file" ]]; then
mv -- "$js_file" "$DEST_DIR/src/$CONTAINERS_SUBDIR/"
print_info " Moved: $(basename "$js_file") -> src/$CONTAINERS_SUBDIR/"
fi
done < <(find "$DEST_DIR" -maxdepth 1 -type f -name "*.js")
# Move .prettierrc if exists
if [[ -f "$DEST_DIR/.prettierrc" ]]; then
mv -- "$DEST_DIR/.prettierrc" "$DEST_DIR/src/$CONTAINERS_SUBDIR/"
print_info " Moved: .prettierrc -> src/$CONTAINERS_SUBDIR/"
fi
# 6) Move envs/ directory if exists
if [[ -d "$DEST_DIR/envs" ]]; then
mv -- "$DEST_DIR/envs" "$DEST_DIR/src/$CONTAINERS_SUBDIR/"
print_info "Step 6/6: Moved envs directory -> src/$CONTAINERS_SUBDIR/envs"
else
print_warning "Step 6/6: envs directory not found at root, skipping move"
fi
print_info "========== DIRECTORY RESTRUCTURING COMPLETED SUCCESSFULLY =========="
else
print_info "Directory restructuring conditions not met, skipping directory reorganization"
fi
fi # end TRANSFORMATION_ONLY skip for directory operations
# Copy transformed content from source repo to target repo
print_info "Copying transformed content: $DEST_DIR -> $GITEA_DEST_DIR ..."
cp -R "./${DEST_DIR}/." "./${GITEA_DEST_DIR}/"
print_info "Copy completed"
# Commit and push changes on target Gitea repository with skip CI/CD marker
print_info "Preparing commit and push in $GITEA_DEST_DIR ..."
git -C "$GITEA_DEST_DIR" add -A
if git -C "$GITEA_DEST_DIR" diff --cached --quiet; then
print_warning "No changes detected in $GITEA_DEST_DIR, skipping commit and push"
else
COMMIT_MESSAGE="chore: sync transformed content [skip ci]"
git -C "$GITEA_DEST_DIR" commit -m "$COMMIT_MESSAGE"
git -C "$GITEA_DEST_DIR" push
print_info "Commit and push completed"
fi
exit 0
+18
View File
@@ -0,0 +1,18 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httproute2
spec:
hostnames:
- <endpoint>
parentRefs:
- name: main-gateway
namespace: nginx-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: <svc-name
port: 80
+2
View File
@@ -0,0 +1,2 @@
delete from campionati_ftp;
insert into campionati_ftp (knrtcx,ktipax,ksgtcl,ksgtcx,kdetcx,id,prov,reg,kris7x,israg,c5,refertodigitale) select knrtcx,ktipax,ksgtcl,ksgtcx,kdetcx,id,prov,reg,kris7x,israg,c5,refertodigitale from dblink('dbname=pgare','select * from siglecampionati') AS campionati(knrtcx character(16),ktipax text,ksgtcl text,ksgtcx text,kdetcx text,id integer,prov character(32),reg character(36),kris7x text,israg boolean,c5 boolean,refertodigitale boolean);
+2
View File
@@ -0,0 +1,2 @@
delete from companies_ftp;
insert into companies_ftp(scoso,scomo,scomi,scomc,scomf,ssigl,sdeno,sind2,scaps,slocr,spvre,scapc,sloco,steds,stepr,stesr,scapo,sfaxs,stelx,spiva,snatu,ssito,smail,spresc,spresn,f0dei,f0ini,f0loi,f9coi,f0prv,f0cap,scosop,stid) select * from dblink('dbname=pgare','select scoso,scomo,scomi,scomc,scomf,ssigl,sdeno,sind2,scaps,slocr,spvre,scapc,sloco,steds,stepr,stesr,scapo,sfaxs,stelx,spiva,snatu,ssito,smail,spresc,spresn,f0dei,f0ini,f0loi,f9coi,f0prv,f0cap,scosop,stid from anagrafe') AS anagrafe(scoso integer,scomo text,scomi text,scomc text,scomf text,ssigl text,sdeno text,sind2 text,scaps text,slocr text,spvre text,scapc text,sloco text,steds text,stepr text,stesr text,scapo text,sfaxs text,stelx text,spiva text,snatu text,ssito text,smail text,spresc text,spresn text,f0dei text,f0ini text,f0loi text,f9coi text,f0prv text,f0cap text,scosop integer,stid integer);
+6
View File
@@ -0,0 +1,6 @@
#
# m h dom mon dow command
0 5 * * * /home/gitlab-runner/dockeclean.sh
0 * * * * /project/inbound_figc/journal_old2new.sh > /project/inbound_figc/journal_old2new.out
30 4 * * * /project/inbound_figc/mainload.sh > /project/inbound_figc/loadmain.out
0 3 * * * /project/inbound_figc/backup.sh > /project/inbound_figc/backup.out
+1
View File
@@ -0,0 +1 @@
SELECT "aaagare"();
+11
View File
@@ -0,0 +1,11 @@
userid="sportftp"
password="\$porteams2019"
ftp -n 212.77.67.140 << EOFTP >ftpget.$$
user $userid $password
prompt
cd files
mget *.csv
quit
EOFTP
+14
View File
@@ -0,0 +1,14 @@
delete from journal_ftp;
insert into journal_ftp(id,chi,quando,idp,ida,ora,data,idc) select * from dblink('dbname=pgare','select id,chi,quando,idp,ida,ora,data,idc from vjournal') AS journal(id integer,chi numeric,quando timestamp,idp integer,ida integer,ora text,data date,idc varchar);
insert into journal_figc(id_utente_modificatore,matricola_figc_sq_modificatrice,created_at,id_partita,id_azione,orario,data,matricola_impianto_figc,azione_annullata,id_old)
select
null as id_utente_modificatore,
null as matricola_figc_sq_modificatrice,
j.quando as created_at,
p.id as id_partita,
j.ida as id_azione,
j.ora::time as orario,
j.data as data,
idc as matricola_impianto_figc,
false as azione_annullata,j.id
from journal_ftp j,partite_figc p where j.idp=p.id_partita_ftp and j.id not in(select id_old from journal_figc) order by j.id;
+12
View File
@@ -0,0 +1,12 @@
insert into journal_figc(id_utente_modificatore,matricola_figc_sq_modificatrice,created_at,id_partita,id_azione,orario,data,matricola_impianto_figc,azione_annullata,id_old)
select
null as id_utente_modificatore,
null as matricola_figc_sq_modificatrice,
j.quando as created_at,
p.id as id_partita,
j.ida as id_azione,
j.ora::time as orario,
j.data as data,
idc as matricola_impianto_figc,
false as azione_annullata,j.id
from journal_ftp j,partite_figc p where j.idp=p.id_partita_ftp and j.id not in(select id_old from journal_figc) order by j.id;
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
. ./.profile
cd /project/inbound_figc
/usr/local/bin/kubectl cp journal.sql db/postgresql-1-postgresql-0:/etc/journal.sql -c postgresql-server
/usr/local/bin/kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n db -- psql -U postgres -d gare -f /etc/journal.sql
+1
View File
@@ -0,0 +1 @@
insert into partite(idpf,ids) select distinct * from pgpartite2insert;
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
sed -i "s/\"/ /g" $1
if [[ $2 == "cal" ]]
then
echo "strutr;r0cmp;r0gir;r0gio;r0anr;r0dai;r0ma1;r0ma2;r0no1;r0pu1;r0pu2;void1;void2;void3;void4;void5;void6;void7;r0imp;r0ori;void8"|cat - $1 > /tmp/out && mv /tmp/out $1"_int"
fi
if [[ $2 == "imp" ]]
then
echo "i0str;i0imp;i0dei;i0ini;i0loi;i0prv;void"|cat - $1 > /tmp/out && mv /tmp/out $1"_int"
fi
if [[ $2 == "ana" ]]
then
echo "SCOSO;SCOMO;SCOMI;SCOMC;SCOMF;SSIGL;SDENO;DESCA;X21;X23;X24;SPC01;SIND1;SIND2;SCAPS;SLOCR;SPVRE;SINC1;SINC2;SCAPC;SLOCO;SPVCO;STEDS;STEPR;STESR;SCAPO;SFAXS;STELX;SPIVA;SNATU;SSITO;SMAIL;SPRESC;SPRESN;F0DEI;F0INI;F0LOI;F9COI;F0PRV;F0CAP;C0DEI;C0INI;C0LOI;C9COI;C0PRV;C0CAP;D0DEI;D0INI;D0LOI;D9COI;D0PRV;D0CAP;SAT01;SAT02;SAT03;SAT04;SAT05;SAT06;SAT07;SAT08;SAT09;SAT10;void"|cat - $1 > /tmp/out && mv /tmp/out $1"_int"
fi
if [[ $2 == "cla" ]]
then
echo "REG;KSGTCL;GIRONE;IDSOC;PUNTI;GF;GS;GIOCATE;VINTE;PAREGGIATE;perse"|cat - $1 > /tmp/out && mv /tmp/out $1"_int"
fi
iconv -f iso-8859-1 -t UTF-8 $1"_int" -o $1"_int_utf8"
sed '1s/^\xEF\xBB\xBF//' < $1"_int_utf8" >$1"_int_utf8_nobom.csv"
sed -i '1s/^\xEF\xBB\xBF//' $1"_int_utf8"
View File
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
cd /project/inbound_figc
kubectl cp cserc17f.csv_int_utf8_nobom.csv dbdev/postgresql-1-postgresql-0:/etc/cserc17f_2load_utf8_nobom.csv
kubectl cp cseri17f.csv_int_utf8_nobom.csv dbdev/postgresql-1-postgresql-0:/etc/cseri17f_2load_utf8_nobom.csv
kubectl cp csexc17f.csv_int_utf8_nobom.csv dbdev/postgresql-1-postgresql-0:/etc/csexc17f_2load_utf8_nobom.csv
kubectl cp csezc17f.csv_int_utf8_nobom.csv dbdev/postgresql-1-postgresql-0:/etc/csezc17f_2load_utf8_nobom.csv
#k cp csezc17f.csv_int_utf8_nobom.csv dbdev/postgresql-1-postgresql-0:/etc/csezc17f_2load_utf8_nobom.csv
kubectl cp gareload.sql dbdev/postgresql-1-postgresql-0:/etc/gareload.sql
kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n dbdev -- psql -U postgres -d gare -f /etc/gareload.sql
#kubectl cp loadclub.sql db/postgresql-1-postgresql-0:/etc/loadclub.sql
#kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n db -- psql -U postgres -d segdigi -f /etc/loadclub.sql
+2
View File
@@ -0,0 +1,2 @@
delete from partite_ftp;
insert into partite_ftp(id,strutr,r0cmp,r0gir,r0gio,r0anr,r0dai,r0ma1,r0ma2,r0pu1,r0pu2,r0cov,r0rdi,rfl0,r0imp,r0ori,color,data,valid) select * from dblink('dbname=pgare','select id,strutr,r0cmp,r0gir,r0gio,r0anr,r0dai,r0ma1,r0ma2,r0pu1,r0pu2,r0cov,r0rdi,r0fl0,r0imp,r0ori,color,data,valid from calendari') AS calendari(id integer, strutr integer,r0cmp character(8),r0gir character(8),r0gio character(8),r0anr character(3),r0dai character(6),r0ma1 integer,r0ma2 integer,r0pu1 character(3),r0pu2 character(3),r0cov character(3),r0rdi character(3),r0fl0 character(3),r0imp character(8),r0ori character(4),color character(10),data date,valid boolean);
+4
View File
@@ -0,0 +1,4 @@
SELECT "aaapgare"();
SELECT "aaapgare_imp"();
SELECT "aaapgare_ana"();
SELECT "aaacla"();
+162
View File
@@ -0,0 +1,162 @@
#!/bin/bash
set -Eeuo pipefail
# Configuration
TARGET_ENV="${1:-stsharedservices-dev}"
DBNAME="${2:-geco}"
POD_NAME="${3:-stpgclusterdev-1}"
CONTAINER_NAME="${4:-postgres}"
BACKUP_FILE="${5:-.}"
DROP_IF_EXISTS="${6:-true}"
DB_USER="${7:-${PGUSER:-postgres}}"
DB_PASSWORD="${8:-${PGPASSWORD:-}}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Logging functions
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $*"
}
error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR:${NC} $*" >&2
exit 1
}
warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARN:${NC} $*"
}
info() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] INFO:${NC} $*"
}
log "Inizio restore database PostgreSQL"
log "Namespace target: ${TARGET_ENV}"
log "Pod target: ${POD_NAME}"
log "Database: ${DBNAME}"
log "DB user: ${DB_USER}"
log "File backup: ${BACKUP_FILE}"
# Validate backup file exists
if [ ! -f "${BACKUP_FILE}" ]; then
error "File di backup non trovato: ${BACKUP_FILE}"
fi
FILE_SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
log "Dimensione file backup: ${FILE_SIZE}"
# Determine if backup is compressed
if [[ "${BACKUP_FILE}" == *.gz ]]; then
IS_COMPRESSED=true
info "Backup compresso (gzip) rilevato"
else
IS_COMPRESSED=false
info "Backup non compresso rilevato"
fi
# Check if namespace exists
if ! kubectl get namespace "${TARGET_ENV}" &>/dev/null; then
error "Namespace '${TARGET_ENV}' non trovato"
fi
# Check if pod exists
if ! kubectl get pod "${POD_NAME}" -n "${TARGET_ENV}" &>/dev/null; then
error "Pod '${POD_NAME}' non trovato nel namespace '${TARGET_ENV}'"
fi
# Check if pod is running
POD_STATUS=$(kubectl get pod "${POD_NAME}" -n "${TARGET_ENV}" -o jsonpath='{.status.phase}')
if [ "${POD_STATUS}" != "Running" ]; then
error "Pod '${POD_NAME}' non è in stato Running (stato attuale: ${POD_STATUS})"
fi
# Check database connectivity
info "Verifica connessione al database..."
if ! kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" pg_isready -U "${DB_USER}" -d "${DBNAME}" &>/dev/null; then
error "Impossibile connettersi a PostgreSQL nel pod '${POD_NAME}'"
fi
log "Connessione al database verificata"
# Drop database if requested
if [ "${DROP_IF_EXISTS}" = "true" ]; then
warn "Elimino il database '${DBNAME}' se esiste..."
kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" psql -U "${DB_USER}" -c "DROP DATABASE IF EXISTS \"${DBNAME}\" WITH (FORCE);" 2>/dev/null || warn "Database non esiste o elimina non è riuscita"
info "Creo nuovo database '${DBNAME}'..."
kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" psql -U "${DB_USER}" -c "CREATE DATABASE \"${DBNAME}\";" || error "Impossibile creare il database '${DBNAME}'"
fi
log "Inizio restore del database..."
# Execute restore
if [ "${IS_COMPRESSED}" = "true" ]; then
info "Decompressione e restore in corso..."
gunzip -c "${BACKUP_FILE}" | kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" pg_restore -U "${DB_USER}" -d "${DBNAME}" --no-owner -v 2>&1 | grep -E "(NOTICE|ERROR|completed)" || true
else
info "Restore da file plain SQL in corso..."
cat "${BACKUP_FILE}" | kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" psql -U "${DB_USER}" -d "${DBNAME}" -v ON_ERROR_STOP=1 2>&1 | grep -E "(NOTICE|ERROR|INSERT|CREATE)" | tail -20 || true
fi
log "Restore completato"
info "Allineo owner delle tabelle al proprietario del database target..."
TARGET_DB_OWNER=$(kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" psql -U "${DB_USER}" -d postgres -t -A -c "SELECT pg_get_userbyid(datdba) FROM pg_database WHERE datname='${DBNAME}';")
if [ -z "${TARGET_DB_OWNER}" ]; then
error "Impossibile determinare l'owner del database '${DBNAME}'"
fi
kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" psql -U "${DB_USER}" -d "${DBNAME}" -v ON_ERROR_STOP=1 -c "DO \$\$ DECLARE r RECORD; BEGIN FOR r IN SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'p') AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT LIKE 'pg_toast%' LOOP EXECUTE format('ALTER TABLE %I.%I OWNER TO %I', r.nspname, r.relname, '${TARGET_DB_OWNER}'); END LOOP; END \$\$;" >/dev/null
log "Owner tabelle allineato a '${TARGET_DB_OWNER}'"
# Verify restore success
info "Verifica integrità database..."
TABLE_COUNT=$(kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" psql -U "${DB_USER}" -d "${DBNAME}" -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public';" 2>/dev/null || echo "0")
if [ "${TABLE_COUNT}" -gt 0 ]; then
log "✅ Restore completato con successo"
log "Database '${DBNAME}' contiene ${TABLE_COUNT} tabelle"
else
warn "⚠️ Database '${DBNAME}' non contiene tabelle pubbliche (potrebbe essere empty)"
fi
# Get database size
DB_SIZE=$(kubectl exec -i "${POD_NAME}" \
-c "${CONTAINER_NAME}" \
-n "${TARGET_ENV}" \
-- env "PGPASSWORD=${DB_PASSWORD}" psql -U "${DB_USER}" -t -c "SELECT pg_size_pretty(pg_database_size('${DBNAME}'));" 2>/dev/null || echo "N/A")
log "Dimensione database: ${DB_SIZE}"
log "✅ Restore completato con successo"
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
set -euo pipefail
# Launcher script for clone-and-transform.sh
# Fill all values below before running.
GITLAB_URL="https://gitlab.com"
GITLAB_TOKEN="glpat-E67BjdXoKvSEgHG31XZW0GM6MQpvOjEKdTozNzF5OQ8.01.171191k74"
GITLAB_REPO_PATH=$1
DEST_DIR="./work-repo"
TRANSFORM_FILE=$3
GITEA_REPO_PATH=$2
TRANSFORMATION_ONLY="${4:-false}"
#
#GITLAB_URL="https://gitlab.com"
#GITLAB_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx"
#GITLAB_REPO_PATH="group/project"
#DEST_DIR="./work-repo"
#TRANSFORM_FILE="./transform.env"
#GITEA_REPO_PATH="group/project"
# Set to "true" to skip GitLab clone and run only: Gitea clone + transform + commit/push
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MAIN_SCRIPT="${SCRIPT_DIR}/clone-and-transform.sh"
if [[ ! -f "$MAIN_SCRIPT" ]]; then
echo "[ERROR] Main script not found: $MAIN_SCRIPT"
exit 1
fi
chmod +x "$MAIN_SCRIPT"
if [[ "$TRANSFORMATION_ONLY" == "true" ]]; then
"$MAIN_SCRIPT" \
-transformation \
--dest "$DEST_DIR" \
--transform "$TRANSFORM_FILE" \
--gitea-repo "$GITEA_REPO_PATH"
else
"$MAIN_SCRIPT" \
--url "$GITLAB_URL" \
--token "$GITLAB_TOKEN" \
--repo "$GITLAB_REPO_PATH" \
--dest "$DEST_DIR" \
--transform "$TRANSFORM_FILE" \
--gitea-repo "$GITEA_REPO_PATH"
fi
+49
View File
@@ -0,0 +1,49 @@
---1) creazione nuova org su gitea Sporteams
---2) crezione progetto stsharedservices
---3) deploy postregsdb su stsharedservices
---4) deploy rabbitmq su stsharedservices
---5) export db Geco
---6) import db Geco
---7) creazione progetti gecofe e gecobe
8) clonazione da analoghi st
9) deploy
kubectl cnpg hibernate on pg-athleteos -n athleteos-dev
stpgclusterdev-rw.stsharedservices-dev.svc.cluster.local
kubectl cnpg delete cluster stpgclusterdev
devVkn9tcHSxK7E
Backup
#!/bin/sh
source_env=dbdev
#target_env=dbqa
dbname=geco
backupfile=${dbname}.backup
#cd /shared/dbdata/sql
kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n ${source_env} -- pg_dump -U postgres -d ${dbname} -F c -b -v -f /shared/backup/$backupfile
kubectl cp ${source_env}/postgresql-1-postgresql-0:/shared/backup/${backupfile} ${backupfile}
#mv $backupfile restore_${backupfile}
kubectl cp ${backupfile} ${target_env}/postgresql-1-postgresql-0:/tmp/restore_${backupfile}
kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n ${target_env} -- psql -U postgres -c "drop database ${dbname}"
kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n ${target_env} -- psql -U postgres -c "create database ${dbname}"
kubectl exec -it postgresql-1-postgresql-0 -c postgresql-server -n ${target_env} -- pg_restore -U postgres -d ${dbname} /tmp/restore_${backupfile}
scp geco-20260418-171917.backup.gz barucci@10.20.1.101:/root/work/fromst
iZmBf42AVzDtD8ZcA9pFvtIGX6cbg9VvzjE0Jd7LkZnplvpceCAYjLqQRr7Z7SsA
rabbitmqcluster.dbdev.svc.cluster.local=stmqclusterdev.stsharedservices-dev.svc.cluster.local
View File
+33
View File
@@ -0,0 +1,33 @@
# Example transformations:
# DATABASE_HOST=localhost=DATABASE_HOST=10.0.0.5
# API_PORT=8080=API_PORT=3000
# ENVIRONMENT=dev=ENVIRONMENT=prod
# OLD_SECRET_KEY=abc123=NEW_SECRET_KEY=xyz789
#Postgres uri
postgresql-1-postgresql-svc.dbdev.svc.cluster.local=postgresql-rw.stsharedservices-dev.svc.cluster.local
#dns domain
sporteams.app=pigreco66.it
#postgres user password
devVkn9tcHSxK7E=iZmBf42AVzDtD8ZcA9pFvtIGX6cbg9VvzjE0Jd7LkZnplvpceCAYjLqQRr7Z7SsA
#rabbitMQ uri
rabbitmqcluster.dbdev.svc.cluster.local=stmqclusterdev.stsharedservices-dev.svc.cluster.local
#RABBITMQ_USER
t2rW7tOSXkq2Oh6XZIrVlBp8Cd3kzd7M=default_user_wN2410ODWhkuXJynz9C
#RABBITMQ_PASSWORD
q2zB-eeyJwDltXDhEFzJ7QhPsrOg4XDe=oENtKdDBUWOePk5OTQ36JInQ8XPsZD8W
#GECO_INTERNAL_URL =
nest.gecobedev=nest.st-geco-backend
#docker image
eu.gcr.io/sporteamscloud/nest%name%:%tag%=<IMAGE_TAG_nest>
eu.gcr.io/sporteamscloud/nginx%name%:%tag%=<IMAGE_TAG_nginx>
#parametri
%backendurl%=<backendurl>