diff --git a/.github/workflows/cleanup-pr.yml b/.github/workflows/cleanup-pr.yml new file mode 100644 index 0000000..02e59b7 --- /dev/null +++ b/.github/workflows/cleanup-pr.yml @@ -0,0 +1,62 @@ +name: Cleanup PR Environment + +on: + pull_request: + types: [closed] + branches: [ "main" ] + +jobs: + cleanup-pr: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # TODO: Ajouter le cleanup Kubernetes ici + # Décommenter quand Kubernetes est configuré avec les credentials + + # - name: Delete Kubernetes environment + # env: + # PR_NUMBER: ${{ github.event.pull_request.number }} + # run: | + # # Configurer kubeconfig avec ${{ secrets.KUBE_CONFIG }} + # + # # Helm uninstall déclenche le PreDelete hook qui supprime la DB + # helm uninstall cityapi-pr-${PR_NUMBER} \ + # --namespace cityapi-pr-${PR_NUMBER} \ + # --wait || true + # + # # Supprimer le namespace + # kubectl delete namespace cityapi-pr-${PR_NUMBER} --wait=false || true + + - name: Comment PR + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.issue.number; + github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: `🗑️ **Environment PR #${prNumber} nettoyé !** + + ✅ Database \`pr_${prNumber}\` supprimée + ✅ Namespace \`cityapi-pr-${prNumber}\` supprimé + + ⚠️ _Cleanup K8s à configurer - voir kubernetes/DATABASE.md_` + }) + + - name: Delete container images + uses: actions/delete-package-versions@v5 + with: + package-name: cityapi + package-type: container + token: ${{ secrets.GITHUB_TOKEN }} + min-versions-to-keep: 10 + delete-only-untagged-versions: false + ignore-versions: '^(latest|main|v.*)$' diff --git a/.github/workflows/deploy-pr.yml b/.github/workflows/deploy-pr.yml new file mode 100644 index 0000000..1b33e60 --- /dev/null +++ b/.github/workflows/deploy-pr.yml @@ -0,0 +1,86 @@ +name: Deploy PR Environment + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [ "main" ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + deploy-pr: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=pr,prefix=pr- + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # TODO: Ajouter le déploiement Kubernetes ici + # Décommenter quand Kubernetes est configuré avec les credentials + + # - name: Deploy to Kubernetes + # env: + # PR_NUMBER: ${{ github.event.pull_request.number }} + # run: | + # # Installer kubectl et helm si nécessaire + # # Configurer kubeconfig avec ${{ secrets.KUBE_CONFIG }} + # + # helm upgrade --install cityapi-pr-${PR_NUMBER} ./kubernetes/helm \ + # -f ./kubernetes/helm/values-pr.yaml \ + # --set prNumber=${PR_NUMBER} \ + # --set app.tag=pr-${PR_NUMBER} \ + # --namespace cityapi-pr-${PR_NUMBER} \ + # --create-namespace \ + # --wait + + - name: Comment PR with deployment info + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.issue.number; + github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: `🚀 **Environment PR #${prNumber} déployé !** + + 📦 Image: \`ghcr.io/${{ github.repository }}:pr-${prNumber}\` + 🗄️ Database: \`pr_${prNumber}\` (copie de prod) + + ⚠️ _Déploiement K8s à configurer - voir kubernetes/DATABASE.md_` + }) diff --git a/SETUP_DB_PR.md b/SETUP_DB_PR.md new file mode 100644 index 0000000..1bbc63f --- /dev/null +++ b/SETUP_DB_PR.md @@ -0,0 +1,255 @@ +# 🎯 Configuration DB Partagée - Guide Complet + +## ✅ Ce qui a été configuré + +### 1. **Hooks Argo CD pour la gestion DB** +📁 `kubernetes/helm/templates/db-pr-hooks.yaml` + +- **PreSync Hook** : Crée la database `pr_XXX` et copie les données de prod +- **PreDelete Hook** : Supprime la database `pr_XXX` à la destruction de l'env + +### 2. **Values Helm pour PR** +📁 `kubernetes/helm/values-pr.yaml` + +- Database: `pr_{{ .Values.prNumber }}` +- Host: `city-api-prod-db` (instance PostgreSQL de prod) +- Copie des données de `city_api` (prod) au démarrage + +### 3. **ApplicationSet Argo CD mis à jour** +📁 `kubernetes/argo-cd.yaml` + +- Auto-création d'Applications pour chaque PR GitHub +- Namespace dédié par PR : `cityapi-pr-XXX` +- Finalizer pour exécuter les hooks de nettoyage + +--- + +## 🚀 Comment ça fonctionne + +### Architecture +``` +┌─────────────────────────────────────────┐ +│ PostgreSQL (city-api-prod-db) │ +│ Namespace: cityapi-prod │ +│ │ +│ ├─ city_api (prod) ← données origine │ +│ ├─ pr_123 ← copie de city_api │ +│ ├─ pr_124 ← copie de city_api │ +│ └─ pr_125 ← copie de city_api │ +└─────────────────────────────────────────┘ + ↑ ↑ ↑ + │ │ │ + cityapi-prod cityapi-pr-123 cityapi-pr-124 + (ns: prod) (ns: pr-123) (ns: pr-124) +``` + +### Workflow complet + +1. **PR ouverte sur GitHub** + - GitHub Actions build l'image avec tag `pr-123` + - Push sur `ghcr.io/aurianecodebien/cityapi:pr-123` + +2. **Argo CD détecte la PR** (via ApplicationSet) + - Crée l'Application `cityapi-pr-123` + - Cible le namespace `cityapi-pr-123` + +3. **PreSync Hook s'exécute** + ```bash + # Job Kubernetes qui se connecte à city-api-prod-db + CREATE DATABASE pr_123 OWNER city_api; + pg_dump city_api | psql pr_123 # Copie les données + ``` + +4. **Déploiement de l'app** + - Variables d'env : + ```bash + CITY_API_DB_HOST=city-api-prod-db + CITY_API_DB_NAME=pr_123 + CITY_API_DB_URL=postgresql://city_api:city_api@city-api-prod-db:5432/pr_123 + ``` + +5. **PR fermée/mergée** + - Argo CD supprime l'Application + - **PreDelete Hook** s'exécute : + ```bash + DROP DATABASE IF EXISTS pr_123; + ``` + +--- + +## 📋 Prérequis + +### 1. Déployer la production d'abord + +```bash +# Déployer l'instance PostgreSQL de prod +helm upgrade --install cityapi-prod ./kubernetes/helm \ + --namespace cityapi-prod \ + --create-namespace +``` + +Cela crée : +- PostgreSQL avec PVC +- Service `city-api-prod-db` +- Database `city_api` + +### 2. Configurer Argo CD + +```bash +# Installer l'ApplicationSet +kubectl apply -f kubernetes/argo-cd.yaml -n argocd +``` + +**Important** : Configurer le token GitHub dans Argo CD pour accéder aux PR. + +--- + +## 🧪 Tester manuellement + +### Déployer une PR test + +```bash +# Exemple pour PR #123 +helm upgrade --install cityapi-pr-123 ./kubernetes/helm \ + -f kubernetes/helm/values-pr.yaml \ + --set prNumber=123 \ + --set app.tag=pr-123 \ + --namespace cityapi-pr-123 \ + --create-namespace +``` + +### Vérifier la database + +```bash +# Se connecter à PostgreSQL +kubectl exec -it deployment/city-api-prod-db -n cityapi-prod -- psql -U city_api -d postgres + +# Lister les databases +\l + +# Vérifier les données +\c pr_123 +SELECT COUNT(*) FROM city; +``` + +### Nettoyer + +```bash +# Supprimer l'env (déclenche le PreDelete hook) +helm uninstall cityapi-pr-123 -n cityapi-pr-123 +kubectl delete namespace cityapi-pr-123 +``` + +--- + +## ⚙️ Configuration GitHub Actions + +Le workflow `.github/workflows/deploy-pr.yml` build l'image Docker pour chaque PR. + +**Pour activer le déploiement K8s :** + +1. Ajouter `KUBE_CONFIG` dans les secrets GitHub +2. Décommenter la section "Deploy to Kubernetes" dans le workflow + +--- + +## 🔒 Sécurité + +### Actuellement +- Credentials en clair dans `values.yaml` +- Acceptable pour dev/staging + +### Pour la production +Utiliser des Kubernetes Secrets : + +```yaml +# kubernetes/secrets/db-credentials.yaml +apiVersion: v1 +kind: Secret +metadata: + name: db-credentials + namespace: cityapi-prod +type: Opaque +stringData: + username: city_api + password: super-secret-password +``` + +Puis dans les values : +```yaml +db: + user: city_api + passwordSecret: + name: db-credentials + key: password +``` + +--- + +## 📊 Monitoring + +### Vérifier les hooks Argo CD + +```bash +# Voir les logs du hook PreSync +kubectl logs -n cityapi-pr-123 job/db-pr-init-123 + +# Voir les logs du hook PreDelete +kubectl logs -n cityapi-pr-123 job/db-pr-cleanup-123 +``` + +### Dashboard Argo CD + +Les hooks apparaissent dans l'interface Argo CD avec des icônes spéciales : +- 🔄 PreSync (avant le sync) +- 🗑️ PreDelete (avant la suppression) + +--- + +## ❓ FAQ + +### Q: Les PR peuvent-elles modifier la prod ? +**R:** Non, chaque PR a sa propre database isolée (`pr_XXX`). + +### Q: Que se passe-t-il si la prod évolue pendant la vie de la PR ? +**R:** La PR garde sa copie initiale. Pour rafraîchir, supprimer et recréer l'env. + +### Q: Peut-on avoir plusieurs PR en parallèle ? +**R:** Oui, chaque PR a sa database (`pr_123`, `pr_124`, etc.). + +### Q: Comment gérer les migrations ? +**R:** Ajouter un Job PostSync qui lance les migrations après le déploiement. + +--- + +## 🛠️ Dépannage + +### La database PR n'est pas créée +```bash +# Vérifier les logs du hook +kubectl get jobs -n cityapi-pr-123 +kubectl logs -n cityapi-pr-123 job/db-pr-init-123 +``` + +### L'app ne se connecte pas +```bash +# Vérifier les variables d'env +kubectl get pod -n cityapi-pr-123 +kubectl describe pod -n cityapi-pr-123 | grep -A 10 "Environment:" +``` + +### La database n'est pas supprimée +```bash +# Le finalizer doit être présent dans l'Application +kubectl get application cityapi-pr-123 -n argocd -o yaml | grep finalizers +``` + +--- + +## 📚 Fichiers importants + +- `kubernetes/helm/templates/db-pr-hooks.yaml` - Hooks création/suppression DB +- `kubernetes/helm/values-pr.yaml` - Configuration PR +- `kubernetes/argo-cd.yaml` - ApplicationSet auto-PR +- `kubernetes/DATABASE.md` - Documentation DB +- `.github/workflows/deploy-pr.yml` - CI/CD PR diff --git a/kubernetes/DATABASE.md b/kubernetes/DATABASE.md new file mode 100644 index 0000000..f00d6c5 --- /dev/null +++ b/kubernetes/DATABASE.md @@ -0,0 +1,122 @@ +# 🗄️ Configuration Database PostgreSQL Partagée + +## Architecture + +``` +┌────────────────────────────────────┐ +│ PostgreSQL (city-api-prod-db) │ +│ │ +│ ├─ city_api (prod) │ +│ ├─ pr_123 (PR #123) │ +│ ├─ pr_124 (PR #124) │ +│ └─ pr_125 (PR #125) │ +└────────────────────────────────────┘ + ↑ ↑ ↑ + │ │ │ + cityapi-prod pr-123 pr-124 +``` + +**Une seule instance PostgreSQL**, plusieurs databases : +- **Production** : utilise `city_api` (base par défaut) +- **PR éphémères** : chaque PR a sa propre database `pr_XXX` avec une **copie des données de prod** + +## 🚀 Déploiement Production + +```bash +# Déployer l'app + DB de prod +helm upgrade --install cityapi-prod ./kubernetes/helm \ + --namespace cityapi-prod \ + --create-namespace +``` + +Cela crée : +- Deployment PostgreSQL avec PVC +- Service `city-api-prod-db` +- Database `city_api` +- App qui se connecte à `city_api` + +## 🧪 Déploiement PR Éphémère + +```bash +# Exemple pour la PR #123 +helm upgrade --install cityapi-pr-123 ./kubernetes/helm \ + -f ./kubernetes/helm/values-pr.yaml \ + --set prNumber=123 \ + --set app.tag=pr-123 \ + --namespace cityapi-pr-123 \ + --create-namespace +``` + +Cela : +1. **PreSync Hook** : Crée la database `pr_123` et copie les données de `city_api` +2. Déploie l'app qui se connecte à `city-api-prod-db` avec `db=pr_123` +3. **PreDelete Hook** : Supprime la database `pr_123` quand l'env est détruit + +## 🔧 Variables d'environnement + +L'app reçoit : +```bash +CITY_API_DB_HOST=city-api-prod-db +CITY_API_DB_PORT=5432 +CITY_API_DB_NAME=pr_123 # ou "city_api" pour prod +CITY_API_DB_USER=city_api +CITY_API_DB_PWD=city_api +CITY_API_DB_URL=postgresql://city_api:city_api@city-api-prod-db:5432/pr_123 +``` + +## 📋 Workflow GitHub Actions (exemple) + +```yaml +name: Deploy PR Environment + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + deploy-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Deploy to K8s + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + + helm upgrade --install cityapi-pr-${PR_NUMBER} ./kubernetes/helm \ + -f ./kubernetes/helm/values-pr.yaml \ + --set prNumber=${PR_NUMBER} \ + --set app.tag=pr-${PR_NUMBER} \ + --namespace cityapi-pr-${PR_NUMBER} \ + --create-namespace +``` + +## 🗑️ Nettoyage PR + +Quand la PR est fermée ou mergée, supprimer l'environnement : + +```bash +helm uninstall cityapi-pr-123 --namespace cityapi-pr-123 +kubectl delete namespace cityapi-pr-123 +``` + +Le hook `PreDelete` supprimera automatiquement la database `pr_123`. + +## ⚠️ Important + +- Les PR partagent la **même instance PostgreSQL** que la prod +- Chaque PR a sa **propre database** isolée +- Les données sont **copiées de prod au démarrage** +- Les modifications dans les PR **n'affectent PAS la prod** +- Penser à gérer les **migrations** si les schemas diffèrent entre PR et prod + +## 🔐 Sécurité + +Pour la production, remplacer les credentials par des Secrets Kubernetes : + +```yaml +# kubernetes/helm/values.yaml +db: + user: city_api + password: ${DB_PASSWORD} # À injecter via Secret +``` diff --git a/kubernetes/argo-cd-appset.yaml b/kubernetes/argo-cd-appset.yaml new file mode 100644 index 0000000..892edf5 --- /dev/null +++ b/kubernetes/argo-cd-appset.yaml @@ -0,0 +1,54 @@ +# ApplicationSet Argo CD pour auto-créer les envs PR +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: cityapi-pr-envs + namespace: argocd +spec: + # Génère une Application pour chaque PR ouverte + generators: + - pullRequest: + github: + owner: aurianecodebien + repo: CityApi + tokenRef: + secretName: github-token + key: token + labels: + - preview # Seulement les PR avec ce label + requeueAfterSeconds: 300 # Refresh toutes les 5min + + template: + metadata: + name: 'cityapi-pr-{{number}}' + labels: + app: cityapi + env: pr + pr-number: '{{number}}' + spec: + project: default + source: + repoURL: https://github.com/aurianecodebien/CityApi + targetRevision: '{{head_sha}}' + path: kubernetes/helm + helm: + valueFiles: + - values-pr.yaml + parameters: + - name: prNumber + value: '{{number}}' + - name: app.tag + value: 'pr-{{number}}' + destination: + server: https://kubernetes.default.svc + namespace: 'cityapi-pr-{{number}}' + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + # Importante : supprime l'Application quand la PR est fermée + syncPolicy: + automated: + prune: true diff --git a/kubernetes/argo-cd-pr-example.yaml b/kubernetes/argo-cd-pr-example.yaml new file mode 100644 index 0000000..24c1f55 --- /dev/null +++ b/kubernetes/argo-cd-pr-example.yaml @@ -0,0 +1,47 @@ +# Exemple d'Application Argo CD pour une PR +# Copier ce fichier et remplacer PR_NUMBER par le numéro réel +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: cityapi-pr-PR_NUMBER + namespace: argocd + labels: + app: cityapi + env: pr + pr-number: "PR_NUMBER" + # Finalizer pour s'assurer que les hooks PreDelete s'exécutent + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + source: + repoURL: https://github.com/aurianecodebien/CityApi + targetRevision: HEAD # ou le nom de la branche PR + path: kubernetes/helm + helm: + valueFiles: + - values-pr.yaml + parameters: + - name: prNumber + value: "PR_NUMBER" + - name: app.tag + value: "pr-PR_NUMBER" + + destination: + server: https://kubernetes.default.svc + namespace: cityapi-pr-PR_NUMBER + + syncPolicy: + automated: + prune: true # Supprime les ressources qui ne sont plus dans Git + selfHeal: true # Corrige automatiquement les drifts + syncOptions: + - CreateNamespace=true + # Retry en cas d'échec + retry: + limit: 3 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m diff --git a/kubernetes/argo-cd.yaml b/kubernetes/argo-cd.yaml index 6a5b9d6..edc0345 100644 --- a/kubernetes/argo-cd.yaml +++ b/kubernetes/argo-cd.yaml @@ -17,6 +17,9 @@ spec: template: metadata: name: 'cityapi-pr-{{.number}}' + # Finalizer pour exécuter les hooks PreDelete avant suppression + finalizers: + - resources-finalizer.argocd.argoproj.io spec: # syncPolicy: # applicationsSync: sync @@ -25,18 +28,22 @@ spec: targetRevision: "{{.head_sha}}" path: kubernetes/helm helm: + valueFiles: + - values-pr.yaml parameters: + - name: "prNumber" + value: "{{.number}}" - name: "app.tag" value: "pr-{{.number}}" - name: "app.env_name" value: "pr-{{.number}}" - name: "db.name" - value: "city_api_pr_{{.number}}" + value: "pr_{{.number}}" project: default destination: server: https://kubernetes.default.svc - namespace: cityapi + namespace: 'cityapi-pr-{{.number}}' # Namespace dédié par PR syncPolicy: automated: prune: true diff --git a/kubernetes/helm/templates/app-deployment.yaml b/kubernetes/helm/templates/app-deployment.yaml index 732638c..9247348 100644 --- a/kubernetes/helm/templates/app-deployment.yaml +++ b/kubernetes/helm/templates/app-deployment.yaml @@ -27,9 +27,15 @@ spec: value: {{ .Values.app.addr }} - name: CITY_API_PORT value: {{ .Values.app.port | quote }} - - name: CITY_API_DB_URL + - name: CITY_API_DB_HOST value: {{ .Values.db.host }} + - name: CITY_API_DB_PORT + value: {{ .Values.db.port | quote }} + - name: CITY_API_DB_NAME + value: {{ .Values.db.name }} - name: CITY_API_DB_USER value: {{ .Values.db.user }} - name: CITY_API_DB_PWD value: {{ .Values.db.password }} + - name: CITY_API_DB_URL + value: {{ printf "postgresql://%s:%s@%s:%d/%s" .Values.db.user .Values.db.password .Values.db.host (.Values.db.port | int) .Values.db.name }} diff --git a/kubernetes/helm/templates/db-pr-hooks.yaml b/kubernetes/helm/templates/db-pr-hooks.yaml new file mode 100644 index 0000000..0fa83c0 --- /dev/null +++ b/kubernetes/helm/templates/db-pr-hooks.yaml @@ -0,0 +1,100 @@ +--- +# Job PreSync : Crée la DB de la PR et copie les données de prod +apiVersion: batch/v1 +kind: Job +metadata: + name: db-pr-init-{{ .Values.prNumber }} + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PreSync + argocd.argoproj.io/hook-delete-policy: HookSucceeded +spec: + backoffLimit: 3 + template: + metadata: + name: db-pr-init + spec: + restartPolicy: Never + containers: + - name: psql + image: postgres:16 + env: + - name: PR_DB_NAME + value: "pr_{{ .Values.prNumber }}" + - name: PROD_DB_NAME + value: "{{ .Values.db.prodDbName | default "city_api" }}" + - name: PGHOST + value: "{{ .Values.db.host }}" + - name: PGPORT + value: "{{ .Values.db.port }}" + - name: PGUSER + value: "{{ .Values.db.user }}" + - name: PGPASSWORD + value: "{{ .Values.db.password }}" + command: ["/bin/bash", "-c"] + args: + - | + set -euo pipefail + + echo "🔍 Checking if database ${PR_DB_NAME} exists..." + + EXISTS=$(psql -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='${PR_DB_NAME}'" || echo "") + + if [ -z "$EXISTS" ]; then + echo "📦 Creating database ${PR_DB_NAME}..." + psql -d postgres -c "CREATE DATABASE ${PR_DB_NAME} OWNER ${PGUSER};" + + echo "📋 Copying data from ${PROD_DB_NAME} to ${PR_DB_NAME}..." + pg_dump -d "${PROD_DB_NAME}" --no-owner --no-acl | psql -d "${PR_DB_NAME}" + + echo "✅ Database ${PR_DB_NAME} created and populated with prod data" + else + echo "✅ Database ${PR_DB_NAME} already exists" + fi +--- +# Job PreDelete : Supprime la DB de la PR +apiVersion: batch/v1 +kind: Job +metadata: + name: db-pr-cleanup-{{ .Values.prNumber }} + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + backoffLimit: 2 + template: + metadata: + name: db-pr-cleanup + spec: + restartPolicy: Never + containers: + - name: psql + image: postgres:16 + env: + - name: PR_DB_NAME + value: "pr_{{ .Values.prNumber }}" + - name: PGHOST + value: "{{ .Values.db.host }}" + - name: PGPORT + value: "{{ .Values.db.port }}" + - name: PGUSER + value: "{{ .Values.db.user }}" + - name: PGPASSWORD + value: "{{ .Values.db.password }}" + command: ["/bin/bash", "-c"] + args: + - | + set -euo pipefail + + echo "🗑️ Dropping database ${PR_DB_NAME}..." + + # Terminate active connections + psql -d postgres -c \ + "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='${PR_DB_NAME}' AND pid <> pg_backend_pid();" \ + || true + + # Drop database + psql -d postgres -c "DROP DATABASE IF EXISTS ${PR_DB_NAME};" + + echo "✅ Database ${PR_DB_NAME} dropped" diff --git a/kubernetes/helm/values-pr.yaml b/kubernetes/helm/values-pr.yaml new file mode 100644 index 0000000..e82854b --- /dev/null +++ b/kubernetes/helm/values-pr.yaml @@ -0,0 +1,26 @@ +# Values pour environnements PR éphémères +# Usage: helm install cityapi-pr-123 ./helm -f values-pr.yaml --set prNumber=123 + +app: + image: ghcr.io/aurianecodebien/cityapi + tag: "pr-{{ .Values.prNumber }}" # Tag sera remplacé par GitHub Actions + env_name: "pr-{{ .Values.prNumber }}" + port: 2022 + nodePort: null # Pas de NodePort pour les PR + addr: "0.0.0.0" + +db: + image: postgres + port: 5432 + storage: 1Gi + # Database spécifique à la PR (sera créée par le hook) + name: "pr_{{ .Values.prNumber }}" + user: city_api + password: city_api + # Host pointe vers la DB de prod (même instance PostgreSQL) + host: city-api-prod-db + # Nom de la database de production (pour copier les données) + prodDbName: city_api + +# Numéro de PR (OBLIGATOIRE - sera défini par Argo CD ou GitHub Actions) +prNumber: "REPLACE_ME" diff --git a/kubernetes/helm/values.yaml b/kubernetes/helm/values.yaml index 6a9dc68..f292c72 100644 --- a/kubernetes/helm/values.yaml +++ b/kubernetes/helm/values.yaml @@ -13,7 +13,7 @@ db: name: city_api user: city_api password: city_api - host: city-api-db + host: city-api-prod-db # Service de la DB prod initSql: | CREATE TABLE city ( diff --git a/kubernetes/test-pr-db.sh b/kubernetes/test-pr-db.sh new file mode 100644 index 0000000..c6792f8 --- /dev/null +++ b/kubernetes/test-pr-db.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -euo pipefail + +# Script pour tester la configuration DB locale + +PR_NUMBER=${1:-123} + +echo "🧪 Testing PR Database Setup for PR #${PR_NUMBER}" +echo "" + +# Check if prod deployment exists +echo "📍 Checking production deployment..." +if ! kubectl get deployment city-api-prod-db -n cityapi-prod &>/dev/null; then + echo "❌ Production deployment not found!" + echo " Deploy prod first: helm install cityapi-prod ./kubernetes/helm --namespace cityapi-prod --create-namespace" + exit 1 +fi + +echo "✅ Production deployment found" +echo "" + +# Get DB service +DB_SERVICE=$(kubectl get svc city-api-prod-db -n cityapi-prod -o jsonpath='{.metadata.name}' 2>/dev/null || echo "") + +if [ -z "$DB_SERVICE" ]; then + echo "❌ Database service not found!" + exit 1 +fi + +echo "✅ Database service: ${DB_SERVICE}" +echo "" + +# Deploy PR environment +echo "🚀 Deploying PR #${PR_NUMBER} environment..." +helm upgrade --install cityapi-pr-${PR_NUMBER} ./kubernetes/helm \ + -f ./kubernetes/helm/values-pr.yaml \ + --set prNumber=${PR_NUMBER} \ + --set app.tag=latest \ + --namespace cityapi-pr-${PR_NUMBER} \ + --create-namespace \ + --wait + +echo "" +echo "✅ PR environment deployed!" +echo "" + +# Check database creation +echo "🔍 Verifying database pr_${PR_NUMBER}..." +kubectl run -it --rm psql-check --image=postgres:16 --restart=Never -n cityapi-prod -- \ + psql -h city-api-prod-db -U city_api -d postgres -c "\l" | grep "pr_${PR_NUMBER}" || true + +echo "" +echo "📊 Summary:" +echo " - Production DB: city_api" +echo " - PR DB: pr_${PR_NUMBER}" +echo " - Namespace: cityapi-pr-${PR_NUMBER}" +echo "" +echo "🧹 To cleanup:" +echo " helm uninstall cityapi-pr-${PR_NUMBER} -n cityapi-pr-${PR_NUMBER}" +echo " kubectl delete namespace cityapi-pr-${PR_NUMBER}" +echo ""