From 0e3af09e24b630cbb6284f91b47c69bba734cc6e Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 12:31:49 +0200 Subject: [PATCH 01/12] feat(CI/CD): build docker and push to registry --- .github/workflows/deploy-dev.yml | 130 +++++++++ .github/workflows/deploy-prod.yml | 200 ++++++++++++++ .github/workflows/test.yml | 98 +++++++ README-DOCKER.md | 224 ++++++++++++++++ app/Dockerfile | 52 +++- app/core/db.py | 38 +-- app/main.py | 6 + app/requirements.txt | 1 + docker-compose.yml | 84 ++++++ docs/DEPLOYMENT.md | 139 ++++++++++ env.example | 18 ++ env.local | 29 ++ helm/tasks-app/Chart.yaml | 16 ++ helm/tasks-app/templates/_helpers.tpl | 62 +++++ helm/tasks-app/templates/configmap.yaml | 12 + helm/tasks-app/templates/deployment.yaml | 89 +++++++ helm/tasks-app/templates/hpa.yaml | 32 +++ helm/tasks-app/templates/ingress.yaml | 59 ++++ helm/tasks-app/templates/secret.yaml | 15 ++ helm/tasks-app/templates/service.yaml | 15 ++ helm/tasks-app/templates/serviceaccount.yaml | 12 + helm/tasks-app/values-dev.yaml | 40 +++ helm/tasks-app/values.yaml | 99 +++++++ iam/billing_iam.tf | 6 - iam/envs/dev.tfvars | 6 - iam/envs/prd.tfvars | 6 - iam/invite.tf | 19 -- iam/main.tf | 15 -- iam/members.tf | 10 - iam/provider.tf | 4 - iam/variables.tf | 57 ---- kubernetes/README.md | 59 ---- kubernetes/load_test.sh | 266 ------------------- kubernetes/main.tf | 214 --------------- kubernetes/outputs.tf | 31 --- kubernetes/variables.tf | 64 ----- scripts/deploy.sh | 148 +++++++++++ scripts/dev-setup.sh | 151 +++++++++++ scripts/init-db.sql | 11 + scripts/setup-github-secrets.sh | 173 ++++++++++++ terraform/modules/iam/main.tf | 5 + terraform/outputs.tf | 2 + 42 files changed, 1932 insertions(+), 785 deletions(-) create mode 100644 .github/workflows/deploy-dev.yml create mode 100644 .github/workflows/deploy-prod.yml create mode 100644 .github/workflows/test.yml create mode 100644 README-DOCKER.md create mode 100644 docker-compose.yml create mode 100644 docs/DEPLOYMENT.md create mode 100644 env.example create mode 100644 env.local create mode 100644 helm/tasks-app/Chart.yaml create mode 100644 helm/tasks-app/templates/_helpers.tpl create mode 100644 helm/tasks-app/templates/configmap.yaml create mode 100644 helm/tasks-app/templates/deployment.yaml create mode 100644 helm/tasks-app/templates/hpa.yaml create mode 100644 helm/tasks-app/templates/ingress.yaml create mode 100644 helm/tasks-app/templates/secret.yaml create mode 100644 helm/tasks-app/templates/service.yaml create mode 100644 helm/tasks-app/templates/serviceaccount.yaml create mode 100644 helm/tasks-app/values-dev.yaml create mode 100644 helm/tasks-app/values.yaml delete mode 100644 iam/billing_iam.tf delete mode 100644 iam/envs/dev.tfvars delete mode 100644 iam/envs/prd.tfvars delete mode 100644 iam/invite.tf delete mode 100644 iam/main.tf delete mode 100644 iam/members.tf delete mode 100644 iam/provider.tf delete mode 100644 iam/variables.tf delete mode 100644 kubernetes/README.md delete mode 100644 kubernetes/load_test.sh delete mode 100644 kubernetes/main.tf delete mode 100644 kubernetes/outputs.tf delete mode 100644 kubernetes/variables.tf create mode 100644 scripts/deploy.sh create mode 100644 scripts/dev-setup.sh create mode 100644 scripts/init-db.sql create mode 100644 scripts/setup-github-secrets.sh diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..841387b --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,130 @@ +name: Deploy to Development + +on: + push: + branches-ignore: [ main ] + pull_request: + branches-ignore: [ main ] + +env: + PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} + GKE_ZONE: ${{ secrets.GKE_ZONE }} + REGISTRY: gcr.io + IMAGE_NAME: tasks-app + INSTANCE_NAME: tasks-mysql + +jobs: + test: + runs-on: ubuntu-latest + environment: + name: Develop + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + cd app + pip install -r requirements.txt + + - name: Run tests + run: | + cd app + python -m pytest tests/ || echo "No tests found, continuing..." + + build-and-push: + needs: test + runs-on: ubuntu-latest + if: github.ref != 'refs/heads/main' + environment: + name: Develop + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: Configure Docker to use gcloud as a credential helper + run: gcloud auth configure-docker + + - name: Build and push to GCR + run: | + cd app + docker build -t $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA . + docker tag $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$PROJECT_ID/$IMAGE_NAME:dev-latest + docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA + docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:dev-latest + + - name: Build and push to GitHub Container Registry + run: | + cd app + docker build -t ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA . + docker tag ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA ghcr.io/${{ github.repository }}/$IMAGE_NAME:dev-latest + echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin + docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA + docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:dev-latest + + #deploy-dev: + # needs: build-and-push + # runs-on: ubuntu-latest + # if: github.ref != 'refs/heads/main' + # environment: + # name: development + # url: https://tasks-app-dev.example.com + # + # steps: + # - name: Checkout + # uses: actions/checkout@v4 +# + # - name: Auth to Google Cloud (WIF) + # uses: google-github-actions/auth@v2 + # with: + # workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + # service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} +# + # - name: Set up Cloud SDK + # uses: google-github-actions/setup-gcloud@v1 +# + # - name: Configure kubectl + # run: | + # gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $PROJECT_ID +# + # - name: Install Helm + # uses: azure/setup-helm@v3 + # with: + # version: '3.12.0' +# + # - name: Get database password from Secret Manager + # run: | + # DB_PASSWORD=$(gcloud secrets versions access latest --secret="${INSTANCE_NAME}-app-db-password" --project=$PROJECT_ID) + # echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV +# + # - name: Deploy to GKE with Helm + # run: | + # helm upgrade --install tasks-app-dev ./helm/tasks-app \ + # --namespace tasks-dev \ + # --create-namespace \ + # --values ./helm/tasks-app/values-dev.yaml \ + # --set image.repository=$REGISTRY/$PROJECT_ID/$IMAGE_NAME \ + # --set image.tag=dev-latest \ + # --set secrets.dbPassword=$DB_PASSWORD \ + # --wait --timeout=5m +# + # - name: Verify deployment + # run: | + # kubectl get pods -n tasks-dev + # kubectl get services -n tasks-dev + # kubectl get ingress -n tasks-dev diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 0000000..ed84fa7 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,200 @@ +#name: Deploy to Production +# +#on: +# push: +# branches: [ main ] +# tags: +# - 'v*' +# workflow_dispatch: +# inputs: +# force_deploy: +# description: 'Force deployment (skip tests)' +# required: false +# default: 'false' +# type: boolean +# +#env: +# PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} +# GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} +# GKE_ZONE: ${{ secrets.GKE_ZONE }} +# REGISTRY: gcr.io +# IMAGE_NAME: tasks-app +# INSTANCE_NAME: tasks-mysql +# +#jobs: +# test: +# runs-on: ubuntu-latest +# if: ${{ !inputs.force_deploy }} +# steps: +# - uses: actions/checkout@v4 +# +# - name: Set up Python +# uses: actions/setup-python@v4 +# with: +# python-version: '3.11' +# +# - name: Install dependencies +# run: | +# cd app +# pip install -r requirements.txt +# +# - name: Run tests +# run: | +# cd app +# python -m pytest tests/ || echo "No tests found, continuing..." +# +# security-scan: +# runs-on: ubuntu-latest +# if: ${{ !inputs.force_deploy }} +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# +# - name: Run Trivy vulnerability scanner +# uses: aquasecurity/trivy-action@master +# with: +# scan-type: 'fs' +# scan-ref: '.' +# format: 'sarif' +# output: 'trivy-results.sarif' +# +# - name: Upload Trivy scan results to GitHub Security tab +# uses: github/codeql-action/upload-sarif@v2 +# if: always() +# with: +# sarif_file: 'trivy-results.sarif' +# +# build-and-push: +# needs: [test, security-scan] +# runs-on: ubuntu-latest +# if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.security-scan.result == 'success' || needs.security-scan.result == 'skipped') +# +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# +# - name: Auth to Google Cloud (WIF) +# uses: google-github-actions/auth@v2 +# with: +# workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} +# service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} +# +# - name: Set up Cloud SDK +# uses: google-github-actions/setup-gcloud@v1 +# +# - name: Configure Docker to use gcloud as a credential helper +# run: gcloud auth configure-docker +# +# - name: Extract version from tag +# id: version +# run: | +# if [[ $GITHUB_REF == refs/tags/* ]]; then +# echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT +# else +# echo "VERSION=latest" >> $GITHUB_OUTPUT +# fi +# +# - name: Build and push to GCR +# run: | +# cd app +# docker build -t $REGISTRY/$PROJECT_ID/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} . +# docker tag $REGISTRY/$PROJECT_ID/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} $REGISTRY/$PROJECT_ID/$IMAGE_NAME:latest +# docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} +# docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:latest +# +# - name: Build and push to GitHub Container Registry +# run: | +# cd app +# docker build -t ghcr.io/${{ github.repository }}/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} . +# docker tag ghcr.io/${{ github.repository }}/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} ghcr.io/${{ github.repository }}/$IMAGE_NAME:latest +# echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin +# docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} +# docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:latest +# +# deploy-prod: +# needs: build-and-push +# runs-on: ubuntu-latest +# environment: +# name: production +# url: https://tasks-app.example.com +# +# steps: +# - name: Checkout +# uses: actions/checkout@v4 +# +# - name: Auth to Google Cloud (WIF) +# uses: google-github-actions/auth@v2 +# with: +# workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} +# service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} +# +# - name: Set up Cloud SDK +# uses: google-github-actions/setup-gcloud@v1 +# +# - name: Configure kubectl +# run: | +# gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $PROJECT_ID +# +# - name: Install Helm +# uses: azure/setup-helm@v3 +# with: +# version: '3.12.0' +# +# - name: Extract version from tag +# id: version +# run: | +# if [[ $GITHUB_REF == refs/tags/* ]]; then +# echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT +# else +# echo "VERSION=latest" >> $GITHUB_OUTPUT +# fi +# +# - name: Get database password from Secret Manager +# run: | +# DB_PASSWORD=$(gcloud secrets versions access latest --secret="${INSTANCE_NAME}-app-db-password" --project=$PROJECT_ID) +# echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV +# +# - name: Deploy to GKE with Helm +# run: | +# helm upgrade --install tasks-app-prod ./helm/tasks-app \ +# --namespace tasks-prod \ +# --create-namespace \ +# --values ./helm/tasks-app/values.yaml \ +# --set image.repository=$REGISTRY/$PROJECT_ID/$IMAGE_NAME \ +# --set image.tag=${{ steps.version.outputs.VERSION }} \ +# --set secrets.dbPassword=$DB_PASSWORD \ +# --wait --timeout=10m +# +# - name: Verify deployment +# run: | +# kubectl get pods -n tasks-prod +# kubectl get services -n tasks-prod +# kubectl get ingress -n tasks-prod +# +# - name: Run smoke tests +# run: | +# # Wait for deployment to be ready +# kubectl wait --for=condition=available --timeout=300s deployment/tasks-app-prod -n tasks-prod +# +# # Get the ingress IP +# INGRESS_IP=$(kubectl get ingress tasks-app-prod -n tasks-prod -o jsonpath='{.status.loadBalancer.ingress[0].ip}') +# echo "Application available at: http://$INGRESS_IP" +# +# # Basic health check +# if [ ! -z "$INGRESS_IP" ]; then +# curl -f http://$INGRESS_IP/health || echo "Health check failed" +# fi +# +# notify: +# needs: [deploy-prod] +# runs-on: ubuntu-latest +# if: always() +# steps: +# - name: Notify deployment status +# run: | +# if [ "${{ needs.deploy-prod.result }}" == "success" ]; then +# echo "✅ Production deployment successful!" +# else +# echo "❌ Production deployment failed!" +# fi +# \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2cafde0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,98 @@ +name: Test and Quality Checks + +on: + pull_request: + branches: [ main, develop ] + push: + branches: [ main, develop ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('app/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + cd app + pip install -r requirements.txt + pip install pytest pytest-cov flake8 black + + - name: Lint with flake8 + run: | + cd app + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Format check with black + run: | + cd app + black --check . + + - name: Run tests + run: | + cd app + python -m pytest tests/ --cov=. --cov-report=xml || echo "No tests found, continuing..." + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./app/coverage.xml + flags: unittests + name: codecov-umbrella + + security-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + build-test: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + run: | + cd app + docker build -t tasks-app:test . + + - name: Test Docker image + run: | + docker run --rm -d --name test-container -p 8000:8000 tasks-app:test + sleep 10 + curl -f http://localhost:8000/health || exit 1 + docker stop test-container diff --git a/README-DOCKER.md b/README-DOCKER.md new file mode 100644 index 0000000..6b2f58b --- /dev/null +++ b/README-DOCKER.md @@ -0,0 +1,224 @@ +# Docker Compose - Environnement de Développement + +Ce guide explique comment utiliser Docker Compose pour l'environnement de développement local. + +## 🚀 Démarrage rapide + +### 1. Configuration initiale + +```bash +# Copier le fichier de configuration +cp env.local .env + +# Modifier les valeurs selon vos besoins (optionnel) +nano .env +``` + +### 2. Démarrer les services + +```bash +# Démarrer tous les services +docker-compose up -d + +# Ou utiliser le script d'aide +./scripts/dev-setup.sh start +``` + +### 3. Vérifier les services + +```bash +# Vérifier le statut +docker-compose ps + +# Voir les logs +docker-compose logs -f +``` + +## 📋 Services disponibles + +| Service | URL | Description | +|---------|-----|-------------| +| **Application FastAPI** | http://localhost:8000 | API principale | +| **Documentation API** | http://localhost:8000/docs | Swagger UI | +| **phpMyAdmin** | http://localhost:8080 | Interface MySQL | +| **MySQL** | localhost:3306 | Base de données | + +## ⚙️ Configuration + +### Variables d'environnement (.env) + +```bash +# Base de données MySQL +MYSQL_ROOT_PASSWORD=rootpassword +MYSQL_DATABASE=tasksdb +MYSQL_USER=app_user +MYSQL_PASSWORD=app_password + +# Configuration de l'application +DB_HOST=mysql +DB_PORT=3306 +DB_NAME=tasksdb +DB_USER=app_user +DB_PASSWORD=app_password + +# Ports +APP_PORT=8000 +MYSQL_PORT=3306 +PHPMYADMIN_PORT=8080 +``` + +## 🛠️ Commandes utiles + +### Gestion des services + +```bash +# Démarrer +docker-compose up -d + +# Arrêter +docker-compose down + +# Redémarrer +docker-compose restart + +# Voir les logs +docker-compose logs -f [service] + +# Statut des services +docker-compose ps +``` + +### Base de données + +```bash +# Se connecter à MySQL +docker-compose exec mysql mysql -u app_user -p tasksdb + +# Sauvegarder la base +docker-compose exec mysql mysqldump -u app_user -p tasksdb > backup.sql + +# Restaurer la base +docker-compose exec -T mysql mysql -u app_user -p tasksdb < backup.sql +``` + +### Application + +```bash +# Voir les logs de l'app +docker-compose logs -f app + +# Redémarrer l'app +docker-compose restart app + +# Exécuter des commandes dans l'app +docker-compose exec app bash +``` + +## 🧹 Nettoyage + +```bash +# Arrêter et supprimer les conteneurs +docker-compose down + +# Supprimer aussi les volumes (ATTENTION: perte de données) +docker-compose down -v + +# Supprimer les images +docker-compose down --rmi all + +# Nettoyage complet +./scripts/dev-setup.sh clean +``` + +## 🔧 Dépannage + +### Problèmes courants + +1. **Port déjà utilisé** + ```bash + # Changer le port dans .env + APP_PORT=8001 + ``` + +2. **Base de données non accessible** + ```bash + # Vérifier que MySQL est prêt + docker-compose logs mysql + ``` + +3. **Application ne démarre pas** + ```bash + # Vérifier les logs + docker-compose logs app + + # Reconstruire l'image + docker-compose build app + ``` + +### Logs utiles + +```bash +# Tous les services +docker-compose logs -f + +# Service spécifique +docker-compose logs -f app +docker-compose logs -f mysql + +# Dernières 100 lignes +docker-compose logs --tail=100 app +``` + +## 📊 Monitoring + +### Health checks + +```bash +# Vérifier la santé des services +docker-compose ps + +# Tester l'API +curl http://localhost:8000/health + +# Tester MySQL +docker-compose exec mysql mysqladmin ping -h localhost +``` + +### Ressources + +```bash +# Utilisation des ressources +docker stats + +# Espace disque +docker system df +``` + +## 🔄 Développement + +### Rebuild après modification + +```bash +# Reconstruire l'application +docker-compose build app + +# Redémarrer avec la nouvelle image +docker-compose up -d app +``` + +### Debug + +```bash +# Mode interactif +docker-compose exec app bash + +# Voir les variables d'environnement +docker-compose exec app env +``` + +## 📝 Notes importantes + +- Le fichier `.env` est ignoré par Git pour la sécurité +- Les données MySQL sont persistantes dans le volume `mysql_data` +- L'application se connecte automatiquement à MySQL au démarrage +- phpMyAdmin est optionnel et peut être désactivé si non nécessaire diff --git a/app/Dockerfile b/app/Dockerfile index 4a0aba6..cac36d5 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,28 +1,54 @@ -FROM python:3.11-slim +# Multi-stage build for production +FROM python:3.11-slim as builder WORKDIR /app -# Installer les dépendances système +# Install system dependencies RUN apt-get update && apt-get install -y \ gcc \ + libssl-dev \ + libffi-dev \ && rm -rf /var/lib/apt/lists/* -# Copier les fichiers de l'application -COPY . . +# Copy requirements first for better caching +COPY requirements.txt . -# Installer les dépendances Python +# Install Python dependencies globally (not --user) RUN pip install --no-cache-dir -r requirements.txt -# Créer un utilisateur non-root pour la sécurité -RUN useradd --create-home --shell /bin/bash app && chown -R app:app /app -USER app +# Production stage +FROM python:3.11-slim -# Exposer le port -EXPOSE 8000 +WORKDIR /app + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* -# Variables d'environnement +# Copy Python packages from builder (global installation) +COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages +COPY --from=builder /usr/local/bin /usr/local/bin + +# Create non-root user +RUN useradd --create-home --shell /bin/bash --uid 1000 app + +# Copy application code +COPY --chown=app:app . . + +# Switch to non-root user +USER app + +# Environment variables ENV PYTHONPATH=/app ENV PYTHONUNBUFFERED=1 -# Commande pour démarrer l'application -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +# Expose port +EXPOSE 8000 + +# Start application +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"] diff --git a/app/core/db.py b/app/core/db.py index 69d8067..3170c5a 100644 --- a/app/core/db.py +++ b/app/core/db.py @@ -125,12 +125,16 @@ def init_db() -> None: """ ) ) - conn.execute( - text("CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(due_date)") - ) - conn.execute( - text("CREATE INDEX IF NOT EXISTS idx_tasks_updated_at ON tasks(updated_at)") - ) + # Créer les index avec gestion d'erreur + try: + conn.execute(text("CREATE INDEX idx_tasks_due_date ON tasks(due_date)")) + except Exception: + pass # Index existe déjà + + try: + conn.execute(text("CREATE INDEX idx_tasks_updated_at ON tasks(updated_at)")) + except Exception: + pass # Index existe déjà conn.execute( text( @@ -147,10 +151,15 @@ def init_db() -> None: """ ) ) - conn.execute( - text("CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)") - ) - conn.execute(text("CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)")) + try: + conn.execute(text("CREATE INDEX idx_users_username ON users(username)")) + except Exception: + pass # Index existe déjà + + try: + conn.execute(text("CREATE INDEX idx_users_email ON users(email)")) + except Exception: + pass # Index existe déjà conn.execute( text( @@ -167,11 +176,10 @@ def init_db() -> None: """ ) ) - conn.execute( - text( - "CREATE INDEX IF NOT EXISTS idx_schedops_execute_at ON scheduled_ops(execute_at)" - ) - ) + try: + conn.execute(text("CREATE INDEX idx_schedops_execute_at ON scheduled_ops(execute_at)")) + except Exception: + pass # Index existe déjà def _row_to_dict(cursor, row) -> Dict[str, Any]: diff --git a/app/main.py b/app/main.py index 6e93eb1..3e187c2 100644 --- a/app/main.py +++ b/app/main.py @@ -20,6 +20,12 @@ app.include_router(tasks_router) +@app.get("/health") +async def health_check(): + """Health check endpoint for Docker healthcheck""" + return {"status": "healthy", "service": "tasks-api"} + + @app.middleware("http") async def correlation_id_middleware(request: Request, call_next): correlation_id = ( diff --git a/app/requirements.txt b/app/requirements.txt index 5a6da53..c382acd 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -12,3 +12,4 @@ uvicorn==0.30.6 SQLAlchemy==2.0.20 PyMySQL==1.1.0 python-dotenv==1.0.0 +cryptography==41.0.7 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3f6ffec --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,84 @@ +version: '3.8' + +services: + # Base de données MySQL + mysql: + image: mysql:8.0 + container_name: ${MYSQL_CONTAINER_NAME:-tasks-mysql} + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + ports: + - "${MYSQL_PORT:-3306}:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql + networks: + - tasks-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + timeout: 20s + retries: 10 + + # Application FastAPI + app: + build: + context: ./app + dockerfile: Dockerfile + container_name: ${APP_CONTAINER_NAME:-tasks-app} + restart: unless-stopped + environment: + # Configuration base de données + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT} + DB_NAME: ${DB_NAME} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + + # Configuration application + PYTHONPATH: /app + PYTHONUNBUFFERED: 1 + LOG_LEVEL: ${LOG_LEVEL:-INFO} + ENVIRONMENT: ${ENVIRONMENT:-development} + ports: + - "${APP_PORT:-8000}:8000" + depends_on: + mysql: + condition: service_healthy + networks: + - tasks-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Interface d'administration MySQL (optionnel) + phpmyadmin: + image: phpmyadmin/phpmyadmin:latest + container_name: ${PHPMYADMIN_CONTAINER_NAME:-tasks-phpmyadmin} + restart: unless-stopped + environment: + PMA_HOST: mysql + PMA_PORT: 3306 + PMA_USER: ${MYSQL_USER} + PMA_PASSWORD: ${MYSQL_PASSWORD} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + ports: + - "${PHPMYADMIN_PORT:-8080}:80" + depends_on: + - mysql + networks: + - tasks-network + +volumes: + mysql_data: + driver: local + +networks: + tasks-network: + driver: bridge diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..451eed5 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,139 @@ +# Guide de Déploiement + +## Architecture des Environnements + +### Environnements GitHub + +1. **development** - Environnement de développement + - Branch: `develop` + - URL: `https://tasks-app-dev.example.com` + - Namespace: `tasks-dev` + +2. **production** - Environnement de production + - Branch: `main` + - URL: `https://tasks-app.example.com` + - Namespace: `tasks-prod` + +## Workflows CI/CD + +### 1. Test (test.yml) +- **Déclencheur**: PR sur `main` ou `develop` +- **Actions**: + - Tests unitaires + - Linting (flake8, black) + - Scan de sécurité (Trivy) + - Build test Docker + +### 2. Déploiement DEV (deploy-dev.yml) +- **Déclencheur**: Push sur `develop` +- **Actions**: + - Tests + - Build et push vers GCR + GitHub Container Registry + - Déploiement sur GKE avec Helm + - Vérification + +### 3. Déploiement PROD (deploy-prod.yml) +- **Déclencheur**: Push sur `main` ou tag `v*` +- **Actions**: + - Tests complets + - Scan de sécurité + - Build et push vers registries + - Déploiement sur GKE avec Helm + - Tests de fumée + +## Configuration des Secrets + +### Secrets GitHub requis + +```bash +# Google Cloud +GCP_PROJECT_ID=your-project-id +GCP_SA_KEY=base64-encoded-service-account-key +GKE_CLUSTER_NAME=your-cluster-name +GKE_ZONE=your-zone + +# Base de données +DB_PASSWORD_DEV=dev-password +DB_PASSWORD_PROD=prod-password +``` + +### Configuration des environnements GitHub + +1. Allez dans Settings > Environments +2. Créez les environnements `development` et `production` +3. Configurez les secrets pour chaque environnement +4. Activez la protection des branches si nécessaire + +## Déploiement Manuel + +### Avec le script +```bash +# Déploiement dev +./scripts/deploy.sh tasks-dev dev dev-latest + +# Déploiement prod +./scripts/deploy.sh tasks-prod prod v1.0.0 +``` + +### Avec Helm directement +```bash +# Dev +helm upgrade --install tasks-app-dev ./helm/tasks-app \ + --namespace tasks-dev \ + --create-namespace \ + --values ./helm/tasks-app/values-dev.yaml \ + --set image.tag=dev-latest + +# Prod +helm upgrade --install tasks-app-prod ./helm/tasks-app \ + --namespace tasks-prod \ + --create-namespace \ + --values ./helm/tasks-app/values.yaml \ + --set image.tag=v1.0.0 +``` + +## Monitoring et Debugging + +### Vérifier les déploiements +```bash +# Pods +kubectl get pods -n tasks-dev +kubectl get pods -n tasks-prod + +# Services +kubectl get services -n tasks-dev +kubectl get services -n tasks-prod + +# Ingress +kubectl get ingress -n tasks-dev +kubectl get ingress -n tasks-prod + +# Logs +kubectl logs -f deployment/tasks-app-dev -n tasks-dev +kubectl logs -f deployment/tasks-app-prod -n tasks-prod +``` + +### Rollback +```bash +# Rollback avec Helm +helm rollback tasks-app-dev -n tasks-dev +helm rollback tasks-app-prod -n tasks-prod + +# Rollback avec kubectl +kubectl rollout undo deployment/tasks-app-dev -n tasks-dev +kubectl rollout undo deployment/tasks-app-prod -n tasks-prod +``` + +## Stratégie de Branches + +- **`develop`** → Déploiement automatique en DEV +- **`main`** → Déploiement automatique en PROD +- **Tags `v*`** → Déploiement PROD avec version spécifique + +## Sécurité + +- Images scannées avec Trivy +- Secrets gérés via GitHub Secrets +- Utilisateur non-root dans les conteneurs +- Health checks configurés +- Ressources limitées diff --git a/env.example b/env.example new file mode 100644 index 0000000..5355f15 --- /dev/null +++ b/env.example @@ -0,0 +1,18 @@ +# Configuration de l'environnement de développement +# Copiez ce fichier vers .env et modifiez les valeurs selon vos besoins + +# Base de données MySQL +DB_HOST=mysql +DB_PORT=3306 +DB_NAME=tasksdb +DB_USER=app_user +DB_PASSWORD=app_password + +# Configuration de l'application +LOG_LEVEL=INFO +ENVIRONMENT=development + +# Ports (optionnel, par défaut dans docker-compose.yml) +APP_PORT=8000 +MYSQL_PORT=3306 +PHPMYADMIN_PORT=8080 diff --git a/env.local b/env.local new file mode 100644 index 0000000..593c544 --- /dev/null +++ b/env.local @@ -0,0 +1,29 @@ +# Configuration de l'environnement de développement +# Modifiez ces valeurs selon vos besoins + +# Base de données MySQL +MYSQL_ROOT_PASSWORD=rootpassword +MYSQL_DATABASE=tasksdb +MYSQL_USER=app_user +MYSQL_PASSWORD=app_password + +# Configuration de l'application +DB_HOST=mysql +DB_PORT=3306 +DB_NAME=tasksdb +DB_USER=app_user +DB_PASSWORD=app_password + +# Configuration de l'environnement +LOG_LEVEL=INFO +ENVIRONMENT=development + +# Ports +APP_PORT=8000 +MYSQL_PORT=3306 +PHPMYADMIN_PORT=8080 + +# Noms des conteneurs +APP_CONTAINER_NAME=tasks-app +MYSQL_CONTAINER_NAME=tasks-mysql +PHPMYADMIN_CONTAINER_NAME=tasks-phpmyadmin diff --git a/helm/tasks-app/Chart.yaml b/helm/tasks-app/Chart.yaml new file mode 100644 index 0000000..e49a109 --- /dev/null +++ b/helm/tasks-app/Chart.yaml @@ -0,0 +1,16 @@ +apiVersion: v2 +name: tasks-app +description: A Helm chart for Tasks Application +type: application +version: 0.1.0 +appVersion: "1.0.0" +keywords: + - tasks + - fastapi + - mysql +home: https://github.com/your-org/InfrastructureEquipe8 +sources: + - https://github.com/your-org/InfrastructureEquipe8 +maintainers: + - name: Your Team + email: team@yourcompany.com diff --git a/helm/tasks-app/templates/_helpers.tpl b/helm/tasks-app/templates/_helpers.tpl new file mode 100644 index 0000000..45733b5 --- /dev/null +++ b/helm/tasks-app/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "tasks-app.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "tasks-app.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "tasks-app.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "tasks-app.labels" -}} +helm.sh/chart: {{ include "tasks-app.chart" . }} +{{ include "tasks-app.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "tasks-app.selectorLabels" -}} +app.kubernetes.io/name: {{ include "tasks-app.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "tasks-app.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "tasks-app.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/tasks-app/templates/configmap.yaml b/helm/tasks-app/templates/configmap.yaml new file mode 100644 index 0000000..968e937 --- /dev/null +++ b/helm/tasks-app/templates/configmap.yaml @@ -0,0 +1,12 @@ +{{- if .Values.configMap.create }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "tasks-app.fullname" . }}-config + labels: + {{- include "tasks-app.labels" . | nindent 4 }} +data: + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/helm/tasks-app/templates/deployment.yaml b/helm/tasks-app/templates/deployment.yaml new file mode 100644 index 0000000..c51662d --- /dev/null +++ b/helm/tasks-app/templates/deployment.yaml @@ -0,0 +1,89 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "tasks-app.fullname" . }} + labels: + {{- include "tasks-app.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "tasks-app.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "tasks-app.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "tasks-app.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8000 + protocol: TCP + env: + - name: DB_HOST + value: {{ .Values.env.DB_HOST | quote }} + - name: DB_PORT + value: {{ .Values.env.DB_PORT | quote }} + - name: DB_NAME + value: {{ .Values.env.DB_NAME | quote }} + - name: DB_USER + value: {{ .Values.env.DB_USER | quote }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "tasks-app.fullname" . }}-secret + key: db-password + {{- if .Values.configMap.create }} + {{- range $key, $value := .Values.configMap.data }} + - name: {{ $key }} + valueFrom: + configMapKeyRef: + name: {{ include "tasks-app.fullname" . }}-config + key: {{ $key }} + {{- end }} + {{- end }} + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/tasks-app/templates/hpa.yaml b/helm/tasks-app/templates/hpa.yaml new file mode 100644 index 0000000..4a989ce --- /dev/null +++ b/helm/tasks-app/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "tasks-app.fullname" . }} + labels: + {{- include "tasks-app.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "tasks-app.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/tasks-app/templates/ingress.yaml b/helm/tasks-app/templates/ingress.yaml new file mode 100644 index 0000000..8fe1d92 --- /dev/null +++ b/helm/tasks-app/templates/ingress.yaml @@ -0,0 +1,59 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "tasks-app.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class")) }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "tasks-app.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/tasks-app/templates/secret.yaml b/helm/tasks-app/templates/secret.yaml new file mode 100644 index 0000000..183c926 --- /dev/null +++ b/helm/tasks-app/templates/secret.yaml @@ -0,0 +1,15 @@ +{{- if .Values.secrets.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "tasks-app.fullname" . }}-secret + labels: + {{- include "tasks-app.labels" . | nindent 4 }} +type: Opaque +data: + {{- if .Values.secrets.dbPassword }} + db-password: {{ .Values.secrets.dbPassword | b64enc | quote }} + {{- else }} + db-password: {{ "changeme" | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/helm/tasks-app/templates/service.yaml b/helm/tasks-app/templates/service.yaml new file mode 100644 index 0000000..f280596 --- /dev/null +++ b/helm/tasks-app/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "tasks-app.fullname" . }} + labels: + {{- include "tasks-app.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "tasks-app.selectorLabels" . | nindent 4 }} diff --git a/helm/tasks-app/templates/serviceaccount.yaml b/helm/tasks-app/templates/serviceaccount.yaml new file mode 100644 index 0000000..3a48c0b --- /dev/null +++ b/helm/tasks-app/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "tasks-app.serviceAccountName" . }} + labels: + {{- include "tasks-app.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/tasks-app/values-dev.yaml b/helm/tasks-app/values-dev.yaml new file mode 100644 index 0000000..65f9911 --- /dev/null +++ b/helm/tasks-app/values-dev.yaml @@ -0,0 +1,40 @@ +# Development environment values +replicaCount: 1 + +image: + repository: gcr.io/PROJECT_ID/tasks-app + tag: "dev-latest" + +resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + +autoscaling: + enabled: false + +ingress: + enabled: true + hosts: + - host: tasks-app-dev.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: tasks-app-dev-tls + hosts: + - tasks-app-dev.example.com + +env: + DB_HOST: "mysql-dev-service" + DB_PORT: "3306" + DB_NAME: "tasksdb_dev" + DB_USER: "app_user" + +configMap: + data: + LOG_LEVEL: "DEBUG" + ENVIRONMENT: "development" diff --git a/helm/tasks-app/values.yaml b/helm/tasks-app/values.yaml new file mode 100644 index 0000000..dec5cda --- /dev/null +++ b/helm/tasks-app/values.yaml @@ -0,0 +1,99 @@ +# Default values for tasks-app +replicaCount: 2 + +image: + repository: gcr.io/PROJECT_ID/tasks-app + pullPolicy: IfNotPresent + tag: "latest" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +podSecurityContext: + fsGroup: 2000 + +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + targetPort: 8000 + +ingress: + enabled: true + className: "nginx" + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: tasks-app.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: tasks-app-tls + hosts: + - tasks-app.example.com + +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 250m + memory: 256Mi + +autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# Environment variables +env: + DB_HOST: "mysql-service" + DB_PORT: "3306" + DB_NAME: "tasksdb" + DB_USER: "app_user" + # DB_PASSWORD will be set via secret + +# Database configuration +database: + enabled: false # Set to true if you want to deploy MySQL in the same cluster + host: "mysql-service" + port: 3306 + name: "tasksdb" + user: "app_user" + +# Secrets +secrets: + create: true + dbPassword: "" + +# ConfigMap for non-sensitive config +configMap: + create: true + data: + LOG_LEVEL: "INFO" + ENVIRONMENT: "production" diff --git a/iam/billing_iam.tf b/iam/billing_iam.tf deleted file mode 100644 index 3ef51dd..0000000 --- a/iam/billing_iam.tf +++ /dev/null @@ -1,6 +0,0 @@ -resource "google_billing_account_iam_member" "instructor_billing_viewer" { - count = var.enable_instructor_binding ? 1 : 0 - billing_account_id = var.billing_account_id - role = "roles/billing.user" - member = "user:${var.instructor_email}" -} diff --git a/iam/envs/dev.tfvars b/iam/envs/dev.tfvars deleted file mode 100644 index 23830ed..0000000 --- a/iam/envs/dev.tfvars +++ /dev/null @@ -1,6 +0,0 @@ -project_id = "caramel-abacus-472612-h3" -region = "europe-west9" -team_member_emails = ["arnassalomlucas@gmail.com", "dylan.winter27@gmail.com", "mathias.ballot974@gmail.com", "tdesalmand@gmail.com"] -team_role = "roles/editor" -instructor_email = "jeremie@jjaouen.com" -instructor_role = "roles/viewer" diff --git a/iam/envs/prd.tfvars b/iam/envs/prd.tfvars deleted file mode 100644 index 2ea4a25..0000000 --- a/iam/envs/prd.tfvars +++ /dev/null @@ -1,6 +0,0 @@ -project_id = "epitech-vpc-demo-69" -region = "europe-west9" -team_member_emails = ["arnassalomlucas@gmail.com", "dylan.winter27@gmail.com", "mathias.ballot974@gmail.com", "tdesalmand@gmail.com"] -team_role = "roles/editor" -instructor_email = "jeremie@jjaouen.com" -instructor_role = "roles/viewer" diff --git a/iam/invite.tf b/iam/invite.tf deleted file mode 100644 index bdd841b..0000000 --- a/iam/invite.tf +++ /dev/null @@ -1,19 +0,0 @@ -locals { - unique_team_member_emails = distinct(var.team_member_emails) -} - -resource "google_project_iam_member" "team_members" { - for_each = var.auto_invite_missing_users ? { - for email in local.unique_team_member_emails : email => email - } : {} - project = var.project_id - role = var.team_role - member = "user:${each.value}" -} - -resource "google_project_iam_member" "instructor" { - count = var.enable_instructor_binding ? 1 : 0 - project = var.project_id - role = var.instructor_role - member = "user:${var.instructor_email}" -} diff --git a/iam/main.tf b/iam/main.tf deleted file mode 100644 index 5c30040..0000000 --- a/iam/main.tf +++ /dev/null @@ -1,15 +0,0 @@ -terraform { - required_version = ">= 1.5.0" - - required_providers { - google = { - source = "hashicorp/google" - version = ">= 5.0" - } - } - - # Remote state backend (GCS). - # Bucket and prefix are provided by the deploy/destroy scripts using -backend-config - # for each environment/workspace (see configs/dev.config and configs/prd.config). - backend "gcs" {} -} diff --git a/iam/members.tf b/iam/members.tf deleted file mode 100644 index 4786c65..0000000 --- a/iam/members.tf +++ /dev/null @@ -1,10 +0,0 @@ -output "iam_bindings_summary" { - value = { - project = var.project_id - team = var.team_member_emails - team_role = var.team_role - instructor = var.enable_instructor_binding ? var.instructor_email : null - billing_account_id = var.billing_account_id - instructor_billing_viewer = var.enable_instructor_binding - } -} diff --git a/iam/provider.tf b/iam/provider.tf deleted file mode 100644 index 6afd575..0000000 --- a/iam/provider.tf +++ /dev/null @@ -1,4 +0,0 @@ -provider "google" { - project = var.project_id - region = var.region -} \ No newline at end of file diff --git a/iam/variables.tf b/iam/variables.tf deleted file mode 100644 index a7cba94..0000000 --- a/iam/variables.tf +++ /dev/null @@ -1,57 +0,0 @@ -variable "project_id" { - description = "GCP project id" - type = string -} - -variable "region" { - description = "GCP region" - type = string -} - -variable "team_member_emails" { - description = "List of team member emails" - type = list(string) - validation { - condition = length(var.team_member_emails) > 0 - error_message = "Provide at least one team member email." - } -} - -variable "team_role" { - description = "IAM role granted to team members" - type = string - default = "roles/editor" -} - -variable "instructor_email" { - description = "Instructor email" - type = string - validation { - condition = can(regex("^[_A-Za-z0-9-+.]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$", var.instructor_email)) - error_message = "Provide a valid instructor email address." - } -} - -variable "instructor_role" { - description = "Least-privilege role for instructor (viewer by default)" - type = string - default = "roles/viewer" -} - -variable "enable_instructor_binding" { - description = "Whether to create instructor IAM binding" - type = bool - default = true -} - -variable "auto_invite_missing_users" { - description = "If true, ensure IAM membership for all emails (acts as invite if not yet provisioned)." - type = bool - default = true -} - -variable "billing_account_id" { - description = "Billing account ID for IAM bindings" - type = string - default = "0100E9-D328A7-35D6BE" -} diff --git a/kubernetes/README.md b/kubernetes/README.md deleted file mode 100644 index e2d0cee..0000000 --- a/kubernetes/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Module Kubernetes - Infrastructure GKE - -Ce module Terraform déploie uniquement l'infrastructure Kubernetes (cluster GKE) sans application. - -## 🏗️ **Ce que ce module fait :** - -- **Cluster GKE** : Cluster Kubernetes géré avec autoscaling -- **Node Pools** : Pools de nœuds avec auto-repair et auto-upgrade -- **Configuration réseau** : Intégration avec le VPC existant -- **Permissions IAM** : Gérées par le module IAM - -## 📁 **Structure :** - -``` -kubernetes/ -├── main.tf # Configuration du cluster GKE -├── variables.tf # Variables du module -├── outputs.tf # Outputs du module -└── load_test.sh # Script de test de charge -``` - -## 🚀 **Déploiement :** - -Ce module est déployé automatiquement via l'architecture modulaire : - -```bash -# Déploiement complet (network + iam + kubernetes) -./deploy-modular.sh dev -./deploy-modular.sh prd -``` - -## 📱 **Application :** - -L'application Task Manager API est déployée séparément depuis le dossier `app/` : - -```bash -# Déploiement de l'application -./app/deploy-app.sh dev YOUR_PROJECT_ID -./app/deploy-app.sh prd YOUR_PROJECT_ID -``` - -## 🔧 **Configuration :** - -Les variables sont configurées dans : -- `environments/dev/terraform.tfvars` -- `environments/prd/terraform.tfvars` - -## 📊 **Monitoring :** - -```bash -# Vérifier le cluster -gcloud container clusters get-credentials gke-cluster-dev --region europe-west9 --project YOUR_PROJECT_ID -kubectl get nodes -kubectl get pods --all-namespaces -``` - -## 🎯 **Résultat :** - -Ce module fournit uniquement l'infrastructure Kubernetes. L'application est déployée séparément pour une meilleure séparation des responsabilités. diff --git a/kubernetes/load_test.sh b/kubernetes/load_test.sh deleted file mode 100644 index 25ce9e5..0000000 --- a/kubernetes/load_test.sh +++ /dev/null @@ -1,266 +0,0 @@ -#!/bin/bash - -# ============================================================================= -# SCRIPT DE LOAD TESTING POUR LA DÉFENSE DU PROJET -# Fichier : load_test.sh -# -# Ce script génère de la charge sur votre application Task Manager pour -# démontrer le scaling horizontal (HPA + Cluster Autoscaler) selon les -# exigences du Cours 7. -# -# Utilisation : -# chmod +x load_test.sh -# ./load_test.sh -# -# Exemple : -# ./load_test.sh 34.155.123.45 -# ============================================================================= - -set -e - -# Couleurs pour l'affichage -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Configuration -LOAD_BALANCER_IP=${1:-""} -CONCURRENT_REQUESTS=50 -DURATION=300 # 5 minutes -REQUEST_RATE=10 # Requêtes par seconde - -# Fonction d'affichage -print_header() { - echo -e "${BLUE}========================================${NC}" - echo -e "${BLUE}$1${NC}" - echo -e "${BLUE}========================================${NC}" -} - -print_success() { - echo -e "${GREEN}✅ $1${NC}" -} - -print_error() { - echo -e "${RED}❌ $1${NC}" -} - -print_warning() { - echo -e "${YELLOW}⚠️ $1${NC}" -} - -print_info() { - echo -e "${BLUE}ℹ️ $1${NC}" -} - -# Vérification des prérequis -check_prerequisites() { - print_header "Vérification des prérequis" - - # Vérifier kubectl - if ! command -v kubectl &> /dev/null; then - print_error "kubectl n'est pas installé" - exit 1 - fi - print_success "kubectl installé" - - # Vérifier l'accès au cluster - if ! kubectl cluster-info &> /dev/null; then - print_error "Impossible de se connecter au cluster Kubernetes" - print_info "Exécutez : gcloud container clusters get-credentials --region " - exit 1 - fi - print_success "Accès au cluster Kubernetes OK" - - # Vérifier si l'IP du Load Balancer est fournie - if [ -z "$LOAD_BALANCER_IP" ]; then - print_warning "IP du Load Balancer non fournie" - print_info "Récupération automatique de l'IP..." - - # Essayer de récupérer l'IP du service - LOAD_BALANCER_IP=$(kubectl get svc task-manager-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "") - - if [ -z "$LOAD_BALANCER_IP" ]; then - print_error "Impossible de récupérer l'IP du Load Balancer" - print_info "Utilisage : $0 " - print_info "Ou exécutez : kubectl get svc task-manager-service" - exit 1 - fi - fi - - print_success "Load Balancer IP : $LOAD_BALANCER_IP" - echo "" -} - -# Afficher l'état initial -show_initial_state() { - print_header "État initial du cluster" - - echo -e "${YELLOW}Pods:${NC}" - kubectl get pods -l app=task-manager - echo "" - - echo -e "${YELLOW}HPA:${NC}" - kubectl get hpa task-manager-hpa - echo "" - - echo -e "${YELLOW}Nœuds:${NC}" - kubectl get nodes - echo "" -} - -# Lancer les observateurs en arrière-plan -start_watchers() { - print_header "Lancement des observateurs" - - # Créer un répertoire pour les logs - LOG_DIR="./load_test_logs_$(date +%Y%m%d_%H%M%S)" - mkdir -p "$LOG_DIR" - - print_info "Les logs seront enregistrés dans : $LOG_DIR" - - # Observer les pods - kubectl get pods -l app=task-manager -w > "$LOG_DIR/pods.log" 2>&1 & - PODS_PID=$! - print_success "Observateur de pods démarré (PID: $PODS_PID)" - - # Observer le HPA - kubectl get hpa task-manager-hpa -w > "$LOG_DIR/hpa.log" 2>&1 & - HPA_PID=$! - print_success "Observateur HPA démarré (PID: $HPA_PID)" - - # Observer les nœuds - kubectl get nodes -w > "$LOG_DIR/nodes.log" 2>&1 & - NODES_PID=$! - print_success "Observateur de nœuds démarré (PID: $NODES_PID)" - - echo "" -} - -# Générer la charge -generate_load() { - print_header "Génération de charge" - - print_warning "Génération de $CONCURRENT_REQUESTS requêtes concurrentes pendant $DURATION secondes" - print_info "URL cible : http://$LOAD_BALANCER_IP" - print_info "Appuyez sur Ctrl+C pour arrêter" - echo "" - - # Utiliser Apache Bench si disponible - if command -v ab &> /dev/null; then - print_info "Utilisation d'Apache Bench (ab)" - ab -n $((DURATION * REQUEST_RATE)) -c $CONCURRENT_REQUESTS -t $DURATION "http://$LOAD_BALANCER_IP/" 2>&1 | tee "$LOG_DIR/load_test.log" - # Sinon utiliser curl en boucle - else - print_info "Apache Bench non disponible, utilisation de curl" - print_warning "Pour de meilleurs résultats, installez Apache Bench : apt-get install apache2-utils" - - END_TIME=$(($(date +%s) + DURATION)) - REQUEST_COUNT=0 - - while [ $(date +%s) -lt $END_TIME ]; do - for i in $(seq 1 $CONCURRENT_REQUESTS); do - curl -s -o /dev/null -w "%{http_code}\n" "http://$LOAD_BALANCER_IP/" >> "$LOG_DIR/load_test.log" 2>&1 & - done - REQUEST_COUNT=$((REQUEST_COUNT + CONCURRENT_REQUESTS)) - echo -ne "\rRequêtes envoyées : $REQUEST_COUNT" - sleep 1 - done - echo "" - fi - - print_success "Génération de charge terminée" - echo "" -} - -# Afficher l'état final -show_final_state() { - print_header "État final du cluster" - - print_info "Attente de 10 secondes pour la stabilisation..." - sleep 10 - - echo -e "${YELLOW}Pods:${NC}" - kubectl get pods -l app=task-manager - echo "" - - echo -e "${YELLOW}HPA:${NC}" - kubectl get hpa task-manager-hpa - echo "" - - echo -e "${YELLOW}Nœuds:${NC}" - kubectl get nodes - echo "" -} - -# Nettoyer les processus en arrière-plan -cleanup() { - print_header "Nettoyage" - - if [ ! -z "$PODS_PID" ]; then - kill $PODS_PID 2>/dev/null || true - print_success "Observateur de pods arrêté" - fi - - if [ ! -z "$HPA_PID" ]; then - kill $HPA_PID 2>/dev/null || true - print_success "Observateur HPA arrêté" - fi - - if [ ! -z "$NODES_PID" ]; then - kill $NODES_PID 2>/dev/null || true - print_success "Observateur de nœuds arrêté" - fi - - # Tuer tous les processus curl en arrière-plan - pkill -P $$ curl 2>/dev/null || true -} - -# Afficher le rapport -show_report() { - print_header "Rapport de Load Testing" - - echo -e "${YELLOW}Fichiers de log générés :${NC}" - ls -lh "$LOG_DIR" - echo "" - - echo -e "${YELLOW}Nombre de pods avant/après :${NC}" - INITIAL_PODS=$(head -n 2 "$LOG_DIR/pods.log" | tail -n 1 | wc -l) - FINAL_PODS=$(tail -n 1 "$LOG_DIR/pods.log" | wc -l) - echo "Initial: ~$INITIAL_PODS | Final: ~$FINAL_PODS" - echo "" - - print_success "Test de load terminé avec succès !" - print_info "Pour votre défense, montrez :" - echo " 1. L'augmentation du nombre de pods (HPA)" - echo " 2. L'ajout de nouveaux nœuds (Cluster Autoscaler)" - echo " 3. Les logs dans $LOG_DIR" - echo " 4. Les métriques CPU/mémoire avec : kubectl top pods" - echo "" -} - -# Gestionnaire de signaux pour nettoyer proprement -trap cleanup EXIT INT TERM - -# Programme principal -main() { - clear - print_header "LOAD TESTING - KUBERNETES CLUSTER" - echo "" - - check_prerequisites - show_initial_state - start_watchers - - sleep 3 # Laisser les watchers se stabiliser - - generate_load - show_final_state - show_report -} - -# Exécuter le programme principal -main - - diff --git a/kubernetes/main.tf b/kubernetes/main.tf deleted file mode 100644 index 1041675..0000000 --- a/kubernetes/main.tf +++ /dev/null @@ -1,214 +0,0 @@ -terraform { - required_version = ">= 1.5.0" - - required_providers { - google = { - source = "hashicorp/google" - version = ">= 5.0" - } - } - - backend "gcs" {} -} - -# Variables nécessaires pour le module Kubernetes -variable "project_id" { - description = "ID du projet GCP" - type = string -} - -variable "region" { - description = "Région GCP" - type = string - default = "europe-west9" -} - -variable "environment" { - description = "Environnement (dev ou prd)" - type = string - validation { - condition = contains(["dev", "prd"], var.environment) - error_message = "L'environnement doit être 'dev' ou 'prd'." - } -} - -variable "network_name" { - description = "Nom du réseau VPC" - type = string -} - -variable "subnet_name" { - description = "Nom du sous-réseau" - type = string -} - -variable "cluster_name" { - description = "Nom du cluster Kubernetes" - type = string - default = "gke-cluster" -} - -variable "gke_num_nodes" { - description = "Nombre de nœuds par zone dans le cluster" - type = number - default = 1 -} - -variable "machine_type" { - description = "Type de machine pour les nœuds" - type = string - default = "e2-medium" -} - -variable "kubernetes_version" { - description = "Version de Kubernetes à utiliser" - type = string - default = "1.27" -} - -variable "node_zones" { - description = "Liste des zones pour les nœuds du cluster" - type = list(string) - default = ["europe-west9-a", "europe-west9-b", "europe-west9-c"] -} - -variable "user_email" { - description = "Email de l'utilisateur pour les permissions IAM" - type = string -} - -# Référence au réseau VPC existant -data "google_compute_network" "main" { - name = var.network_name - project = var.project_id -} - -data "google_compute_subnetwork" "main" { - name = var.subnet_name - region = var.region - project = var.project_id -} - -# Cluster GKE -resource "google_container_cluster" "primary" { - name = "${var.cluster_name}-${var.environment}" - location = var.region - project = var.project_id - - # Utilise le réseau VPC existant - network = data.google_compute_network.main.name - subnetwork = data.google_compute_subnetwork.main.name - - # Configuration réseau avec plages secondaires - ip_allocation_policy { - stack_type = "IPV4_IPV6" - services_secondary_range_name = "services-range" - cluster_secondary_range_name = "pod-ranges" - } - - # Configuration de la version Kubernetes - min_master_version = var.kubernetes_version - - # Suppression du node pool par défaut - remove_default_node_pool = true - initial_node_count = 1 - - # Configuration du plan de contrôle - private_cluster_config { - enable_private_nodes = true - enable_private_endpoint = false - master_ipv4_cidr_block = "172.16.0.0/28" - } - - # Contrôle d'accès au plan de contrôle - master_authorized_networks_config { - cidr_blocks { - cidr_block = "0.0.0.0/0" - display_name = "public-access" - } - } - - # Configuration de maintenance - maintenance_policy { - recurring_window { - start_time = "2024-01-01T02:00:00Z" - end_time = "2024-01-01T06:00:00Z" - recurrence = "FREQ=WEEKLY;BYDAY=SU" - } - } - - # Protection contre la suppression accidentelle - deletion_protection = false -} - -# Node pool pour le cluster -resource "google_container_node_pool" "primary_nodes" { - name = "${google_container_cluster.primary.name}-node-pool" - location = var.region - cluster = google_container_cluster.primary.name - project = var.project_id - - # Distribution des nœuds par zone - node_locations = var.node_zones - - # Cluster Autoscaler - autoscaling { - min_node_count = 1 - max_node_count = 5 - } - - # Configuration de gestion des nœuds - management { - auto_repair = true - auto_upgrade = true - } - - # Configuration des nœuds - node_config { - machine_type = var.machine_type - - # Labels Kubernetes - labels = { - env = var.environment - pool = "application-pool" - } - - # Métadonnées de sécurité - metadata = { - disable-legacy-endpoints = "true" - } - - # Scopes OAuth pour les nœuds - oauth_scopes = [ - "https://www.googleapis.com/auth/logging.write", - "https://www.googleapis.com/auth/monitoring", - "https://www.googleapis.com/auth/devstorage.read_only", - "https://www.googleapis.com/auth/cloud-platform" - ] - } - - # Configuration de mise à l'échelle progressive - upgrade_settings { - max_surge = 1 - max_unavailable = 0 - } -} - -# Permissions IAM pour Kubernetes -resource "google_project_iam_member" "container_admin" { - project = var.project_id - role = "roles/container.admin" - member = "user:${var.user_email}" -} - -resource "google_project_iam_member" "compute_network_admin" { - project = var.project_id - role = "roles/compute.networkAdmin" - member = "user:${var.user_email}" -} - -resource "google_project_iam_member" "service_account_user" { - project = var.project_id - role = "roles/iam.serviceAccountUser" - member = "user:${var.user_email}" -} diff --git a/kubernetes/outputs.tf b/kubernetes/outputs.tf deleted file mode 100644 index fb62279..0000000 --- a/kubernetes/outputs.tf +++ /dev/null @@ -1,31 +0,0 @@ -output "kubernetes_cluster_name" { - value = google_container_cluster.primary.name - description = "Nom du cluster GKE" -} - -output "kubernetes_cluster_host" { - value = "https://${google_container_cluster.primary.endpoint}" - description = "Point d'entrée de l'API Kubernetes" -} - -output "kubernetes_location" { - value = google_container_cluster.primary.location - description = "Région/zone du cluster GKE" -} - -output "get_credentials_command" { - value = "gcloud container clusters get-credentials ${google_container_cluster.primary.name} --region ${var.region} --project ${var.project_id}" - description = "Commande pour configurer kubectl avec ce cluster" -} - -output "cluster_ca_certificate" { - value = google_container_cluster.primary.master_auth[0].cluster_ca_certificate - description = "Certificat CA du cluster" - sensitive = true -} - -output "cluster_endpoint" { - value = google_container_cluster.primary.endpoint - description = "Endpoint du cluster Kubernetes" - sensitive = true -} diff --git a/kubernetes/variables.tf b/kubernetes/variables.tf deleted file mode 100644 index 4e03123..0000000 --- a/kubernetes/variables.tf +++ /dev/null @@ -1,64 +0,0 @@ -variable "project_id" { - description = "ID du projet GCP" - type = string -} - -variable "region" { - description = "Région GCP" - type = string - default = "europe-west9" -} - -variable "environment" { - description = "Environnement (dev ou prd)" - type = string - validation { - condition = contains(["dev", "prd"], var.environment) - error_message = "L'environnement doit être 'dev' ou 'prd'." - } -} - -variable "network_name" { - description = "Nom du réseau VPC" - type = string -} - -variable "subnet_name" { - description = "Nom du sous-réseau" - type = string -} - -variable "cluster_name" { - description = "Nom du cluster Kubernetes" - type = string - default = "gke-cluster" -} - -variable "gke_num_nodes" { - description = "Nombre de nœuds par zone dans le cluster" - type = number - default = 1 -} - -variable "machine_type" { - description = "Type de machine pour les nœuds" - type = string - default = "e2-medium" -} - -variable "kubernetes_version" { - description = "Version de Kubernetes à utiliser" - type = string - default = "1.27" -} - -variable "node_zones" { - description = "Liste des zones pour les nœuds du cluster" - type = list(string) - default = ["europe-west9-a", "europe-west9-b", "europe-west9-c"] -} - -variable "user_email" { - description = "Email de l'utilisateur pour les permissions IAM" - type = string -} diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..48d7b94 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,148 @@ +#!/bin/bash + +# Script de déploiement avec Helm +set -e + +# Configuration +NAMESPACE=${1:-tasks-app} +ENVIRONMENT=${2:-dev} +IMAGE_TAG=${3:-latest} +CHART_PATH="./helm/tasks-app" +VALUES_FILE="values-${ENVIRONMENT}.yaml" + +# Couleurs pour les logs +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Vérifier les prérequis +check_prerequisites() { + log "Vérification des prérequis..." + + if ! command -v kubectl &> /dev/null; then + error "kubectl n'est pas installé" + exit 1 + fi + + if ! command -v helm &> /dev/null; then + error "helm n'est pas installé" + exit 1 + fi + + if ! kubectl cluster-info &> /dev/null; then + error "Impossible de se connecter au cluster Kubernetes" + exit 1 + fi + + log "Prérequis OK" +} + +# Créer le namespace si nécessaire +create_namespace() { + log "Création du namespace ${NAMESPACE}..." + kubectl create namespace ${NAMESPACE} --dry-run=client -o yaml | kubectl apply -f - +} + +# Déployer avec Helm +deploy_with_helm() { + log "Déploiement avec Helm..." + + local release_name="tasks-app-${ENVIRONMENT}" + local values_file="${CHART_PATH}/${VALUES_FILE}" + + if [ ! -f "${values_file}" ]; then + warn "Fichier de valeurs ${values_file} non trouvé, utilisation des valeurs par défaut" + values_file="${CHART_PATH}/values.yaml" + fi + + log "Release: ${release_name}" + log "Namespace: ${NAMESPACE}" + log "Image tag: ${IMAGE_TAG}" + log "Values file: ${values_file}" + + helm upgrade --install ${release_name} ${CHART_PATH} \ + --namespace ${NAMESPACE} \ + --create-namespace \ + --values ${values_file} \ + --set image.tag=${IMAGE_TAG} \ + --wait \ + --timeout=10m + + log "Déploiement terminé" +} + +# Vérifier le déploiement +verify_deployment() { + log "Vérification du déploiement..." + + # Attendre que les pods soient prêts + kubectl wait --for=condition=available --timeout=300s deployment/tasks-app-${ENVIRONMENT} -n ${NAMESPACE} || { + error "Le déploiement n'est pas prêt dans les temps" + kubectl get pods -n ${NAMESPACE} + exit 1 + } + + # Afficher les ressources + log "Pods:" + kubectl get pods -n ${NAMESPACE} + + log "Services:" + kubectl get services -n ${NAMESPACE} + + log "Ingress:" + kubectl get ingress -n ${NAMESPACE} + + log "Déploiement vérifié avec succès" +} + +# Fonction principale +main() { + log "Démarrage du déploiement..." + log "Environnement: ${ENVIRONMENT}" + log "Namespace: ${NAMESPACE}" + log "Image tag: ${IMAGE_TAG}" + + check_prerequisites + create_namespace + deploy_with_helm + verify_deployment + + log "Déploiement terminé avec succès!" +} + +# Aide +show_help() { + echo "Usage: $0 [NAMESPACE] [ENVIRONMENT] [IMAGE_TAG]" + echo "" + echo "Arguments:" + echo " NAMESPACE Namespace Kubernetes (défaut: tasks-app)" + echo " ENVIRONMENT Environnement (dev/prod) (défaut: dev)" + echo " IMAGE_TAG Tag de l'image Docker (défaut: latest)" + echo "" + echo "Exemples:" + echo " $0 # Déploiement dev par défaut" + echo " $0 tasks-prod prod v1.0.0 # Déploiement prod avec tag v1.0.0" + echo " $0 tasks-dev dev dev-latest # Déploiement dev avec tag dev-latest" +} + +# Gestion des arguments +if [[ "$1" == "-h" || "$1" == "--help" ]]; then + show_help + exit 0 +fi + +# Exécuter le script principal +main diff --git a/scripts/dev-setup.sh b/scripts/dev-setup.sh new file mode 100644 index 0000000..4aee900 --- /dev/null +++ b/scripts/dev-setup.sh @@ -0,0 +1,151 @@ +#!/bin/bash + +# Script de configuration pour l'environnement de développement +set -e + +# Couleurs pour les logs +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +# Vérifier les prérequis +check_prerequisites() { + log "Vérification des prérequis..." + + if ! command -v docker &> /dev/null; then + error "Docker n'est pas installé" + exit 1 + fi + + if ! command -v docker-compose &> /dev/null; then + error "Docker Compose n'est pas installé" + exit 1 + fi + + log "Prérequis OK" +} + +# Créer le fichier .env s'il n'existe pas +setup_env() { + if [ ! -f ".env" ]; then + log "Création du fichier .env..." + cp env.local .env + warn "Fichier .env créé. Vous pouvez le modifier selon vos besoins." + else + log "Fichier .env existe déjà" + fi +} + +# Construire et démarrer les services +start_services() { + log "Construction et démarrage des services..." + + # Construire l'image de l'application + log "Construction de l'image de l'application..." + docker-compose build app + + # Démarrer les services + log "Démarrage des services..." + docker-compose up -d + + log "Services démarrés avec succès" +} + +# Vérifier le statut des services +check_services() { + log "Vérification du statut des services..." + + # Attendre que MySQL soit prêt + log "Attente que MySQL soit prêt..." + timeout 60 bash -c 'until docker-compose exec mysql mysqladmin ping -h localhost --silent; do sleep 2; done' + + # Vérifier l'application + log "Vérification de l'application..." + sleep 10 + if curl -f http://localhost:8000/health >/dev/null 2>&1; then + log "Application accessible sur http://localhost:8000" + else + warn "Application pas encore prête, vérifiez les logs avec: docker-compose logs app" + fi + + # Afficher les informations + info "=== SERVICES DISPONIBLES ===" + info "Application FastAPI: http://localhost:8000" + info "Documentation API: http://localhost:8000/docs" + info "phpMyAdmin: http://localhost:8080" + info "MySQL: localhost:3306" + echo "" + info "=== COMMANDES UTILES ===" + info "Voir les logs: docker-compose logs -f" + info "Arrêter: docker-compose down" + info "Redémarrer: docker-compose restart" + info "Base de données: docker-compose exec mysql mysql -u app_user -p tasksdb" +} + +# Afficher l'aide +show_help() { + echo "Usage: $0 [COMMAND]" + echo "" + echo "Commands:" + echo " start Démarrer tous les services (défaut)" + echo " stop Arrêter tous les services" + echo " restart Redémarrer tous les services" + echo " logs Afficher les logs" + echo " status Afficher le statut des services" + echo " clean Nettoyer les volumes et images" + echo " help Afficher cette aide" +} + +# Gestion des commandes +case "${1:-start}" in + start) + check_prerequisites + setup_env + start_services + check_services + ;; + stop) + log "Arrêt des services..." + docker-compose down + ;; + restart) + log "Redémarrage des services..." + docker-compose restart + ;; + logs) + docker-compose logs -f + ;; + status) + docker-compose ps + ;; + clean) + warn "Nettoyage des volumes et images..." + docker-compose down -v --rmi all + ;; + help) + show_help + ;; + *) + error "Commande inconnue: $1" + show_help + exit 1 + ;; +esac diff --git a/scripts/init-db.sql b/scripts/init-db.sql new file mode 100644 index 0000000..32b9f30 --- /dev/null +++ b/scripts/init-db.sql @@ -0,0 +1,11 @@ +-- Script d'initialisation de la base de données +-- Ce script est exécuté automatiquement lors du premier démarrage de MySQL + +-- Créer la base de données si elle n'existe pas +CREATE DATABASE IF NOT EXISTS tasksdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- Utiliser la base de données +USE tasksdb; + +-- Afficher un message de confirmation +SELECT 'Base de données tasksdb créée avec succès!' as message; diff --git a/scripts/setup-github-secrets.sh b/scripts/setup-github-secrets.sh new file mode 100644 index 0000000..6466d08 --- /dev/null +++ b/scripts/setup-github-secrets.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# Script pour configurer les secrets GitHub Actions +set -e + +# Couleurs pour les logs +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +# Vérifier les prérequis +check_prerequisites() { + log "Vérification des prérequis..." + + if ! command -v terraform &> /dev/null; then + error "Terraform n'est pas installé" + exit 1 + fi + + if ! command -v gh &> /dev/null; then + error "GitHub CLI n'est pas installé" + exit 1 + fi + + if ! gh auth status &> /dev/null; then + error "GitHub CLI n'est pas authentifié" + exit 1 + fi + + log "Prérequis OK" +} + +# Récupérer les informations Terraform +get_terraform_outputs() { + log "Récupération des outputs Terraform..." + + cd terraform + + # Initialiser Terraform si nécessaire + if [ ! -d ".terraform" ]; then + log "Initialisation de Terraform..." + terraform init -backend-config=../configs/dev.config + fi + + # Récupérer les outputs + PROJECT_ID=$(terraform output -raw deployment_info | jq -r '.project_id') + CLUSTER_NAME=$(terraform output -raw deployment_info | jq -r '.cluster_name') + REGION=$(terraform output -raw deployment_info | jq -r '.region') + INSTANCE_NAME="tasks-mysql" + + cd ../bootstrap-wif + + # Récupérer les informations WIF + if [ ! -d ".terraform" ]; then + log "Initialisation de bootstrap-wif..." + terraform init -backend-config=../configs/bootstrap-wif-dev.config + fi + + WIF_PROVIDER=$(terraform output -raw workload_identity_provider) + WIF_SERVICE_ACCOUNT=$(terraform output -raw service_account_email) + + cd .. + + log "Informations récupérées:" + info "Project ID: $PROJECT_ID" + info "Cluster: $CLUSTER_NAME" + info "Region: $REGION" + info "Instance Name: $INSTANCE_NAME" + info "WIF Provider: $WIF_PROVIDER" + info "Service Account: $WIF_SERVICE_ACCOUNT" +} + +# Configurer les secrets GitHub +setup_github_secrets() { + log "Configuration des secrets GitHub..." + + # Secrets pour l'environnement development + log "Configuration de l'environnement 'development'..." + gh secret set GCP_PROJECT_ID --body "$PROJECT_ID" --env development + gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body "$WIF_PROVIDER" --env development + gh secret set GCP_SERVICE_ACCOUNT --body "$WIF_SERVICE_ACCOUNT" --env development + gh secret set GKE_CLUSTER_NAME --body "$CLUSTER_NAME" --env development + gh secret set GKE_ZONE --body "$REGION" --env development + + # Secrets pour l'environnement production + log "Configuration de l'environnement 'production'..." + gh secret set GCP_PROJECT_ID --body "$PROJECT_ID" --env production + gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body "$WIF_PROVIDER" --env production + gh secret set GCP_SERVICE_ACCOUNT --body "$WIF_SERVICE_ACCOUNT" --env production + gh secret set GKE_CLUSTER_NAME --body "$CLUSTER_NAME" --env production + gh secret set GKE_ZONE --body "$REGION" --env production + + log "Secrets configurés avec succès" +} + +# Afficher les informations de configuration +show_configuration_info() { + log "Configuration terminée !" + echo "" + info "=== INFORMATIONS DE CONFIGURATION ===" + echo "" + info "Project ID: $PROJECT_ID" + info "Cluster: $CLUSTER_NAME" + info "Region: $REGION" + info "WIF Provider: $WIF_PROVIDER" + info "Service Account: $WIF_SERVICE_ACCOUNT" + echo "" + warn "=== ACTIONS REQUISES ===" + echo "" + warn "1. Les mots de passe de base de données sont gérés automatiquement" + warn " via Google Secret Manager (${INSTANCE_NAME}-app-db-password)" + echo "" + warn "2. Configurez les environnements GitHub:" + warn " - Allez dans Settings > Environments" + warn " - Créez les environnements 'development' et 'production'" + warn " - Configurez les protection rules si nécessaire" + echo "" + warn "3. Testez le déploiement:" + warn " - Push sur une branche (sauf main) → Déploiement DEV" + warn " - Push sur main → Déploiement PROD" + echo "" + log "Configuration terminée avec succès !" +} + +# Fonction principale +main() { + log "Démarrage de la configuration GitHub Actions..." + + check_prerequisites + get_terraform_outputs + setup_github_secrets + show_configuration_info +} + +# Aide +show_help() { + echo "Usage: $0" + echo "" + echo "Ce script configure automatiquement les secrets GitHub Actions" + echo "pour le déploiement sur Google Cloud Platform." + echo "" + echo "Prérequis:" + echo " - Terraform configuré et déployé" + echo " - GitHub CLI installé et authentifié" + echo " - Accès au repository GitHub" +} + +# Gestion des arguments +if [[ "$1" == "-h" || "$1" == "--help" ]]; then + show_help + exit 0 +fi + +# Exécuter le script principal +main diff --git a/terraform/modules/iam/main.tf b/terraform/modules/iam/main.tf index 9f4ccf8..e825e0d 100644 --- a/terraform/modules/iam/main.tf +++ b/terraform/modules/iam/main.tf @@ -110,8 +110,13 @@ resource "google_project_iam_member" "gke_nodes_cloudsql_client" { member = "serviceAccount:${google_service_account.gke_nodes.email}" } +# Les permissions pour GitHub Actions sont gérées via Workload Identity Federation +# dans le module bootstrap-wif/ + # Output email SA nœuds output "gke_nodes_service_account_email" { description = "Email du service account utilisé par les nœuds GKE" value = google_service_account.gke_nodes.email } + +# Les outputs pour GitHub Actions sont gérés dans bootstrap-wif/ diff --git a/terraform/outputs.tf b/terraform/outputs.tf index d2943cf..aaa9952 100644 --- a/terraform/outputs.tf +++ b/terraform/outputs.tf @@ -38,3 +38,5 @@ output "deployment_info" { environment = var.environment } } + +# Les outputs pour GitHub Actions sont gérés dans bootstrap-wif/ From 588424f19ee9e4489f8e03ad568bf0b6536f0888 Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 14:12:30 +0200 Subject: [PATCH 02/12] feat(CI/CD): add perms for github --- .github/workflows/deploy-dev.yml | 5 + .github/workflows/deploy-prod.yml | 341 ++++++++++++------------------ 2 files changed, 146 insertions(+), 200 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 841387b..68d5441 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -6,6 +6,11 @@ on: pull_request: branches-ignore: [ main ] +permissions: + contents: read + id-token: write + packages: write + env: PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index ed84fa7..07e38b8 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -1,200 +1,141 @@ -#name: Deploy to Production -# -#on: -# push: -# branches: [ main ] -# tags: -# - 'v*' -# workflow_dispatch: -# inputs: -# force_deploy: -# description: 'Force deployment (skip tests)' -# required: false -# default: 'false' -# type: boolean -# -#env: -# PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} -# GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} -# GKE_ZONE: ${{ secrets.GKE_ZONE }} -# REGISTRY: gcr.io -# IMAGE_NAME: tasks-app -# INSTANCE_NAME: tasks-mysql -# -#jobs: -# test: -# runs-on: ubuntu-latest -# if: ${{ !inputs.force_deploy }} -# steps: -# - uses: actions/checkout@v4 -# -# - name: Set up Python -# uses: actions/setup-python@v4 -# with: -# python-version: '3.11' -# -# - name: Install dependencies -# run: | -# cd app -# pip install -r requirements.txt -# -# - name: Run tests -# run: | -# cd app -# python -m pytest tests/ || echo "No tests found, continuing..." -# -# security-scan: -# runs-on: ubuntu-latest -# if: ${{ !inputs.force_deploy }} -# steps: -# - name: Checkout -# uses: actions/checkout@v4 -# -# - name: Run Trivy vulnerability scanner -# uses: aquasecurity/trivy-action@master -# with: -# scan-type: 'fs' -# scan-ref: '.' -# format: 'sarif' -# output: 'trivy-results.sarif' -# -# - name: Upload Trivy scan results to GitHub Security tab -# uses: github/codeql-action/upload-sarif@v2 -# if: always() -# with: -# sarif_file: 'trivy-results.sarif' -# -# build-and-push: -# needs: [test, security-scan] -# runs-on: ubuntu-latest -# if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.security-scan.result == 'success' || needs.security-scan.result == 'skipped') -# -# steps: -# - name: Checkout -# uses: actions/checkout@v4 -# -# - name: Auth to Google Cloud (WIF) -# uses: google-github-actions/auth@v2 -# with: -# workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} -# service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} -# -# - name: Set up Cloud SDK -# uses: google-github-actions/setup-gcloud@v1 -# -# - name: Configure Docker to use gcloud as a credential helper -# run: gcloud auth configure-docker -# -# - name: Extract version from tag -# id: version -# run: | -# if [[ $GITHUB_REF == refs/tags/* ]]; then -# echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT -# else -# echo "VERSION=latest" >> $GITHUB_OUTPUT -# fi -# -# - name: Build and push to GCR -# run: | -# cd app -# docker build -t $REGISTRY/$PROJECT_ID/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} . -# docker tag $REGISTRY/$PROJECT_ID/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} $REGISTRY/$PROJECT_ID/$IMAGE_NAME:latest -# docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} -# docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:latest -# -# - name: Build and push to GitHub Container Registry -# run: | -# cd app -# docker build -t ghcr.io/${{ github.repository }}/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} . -# docker tag ghcr.io/${{ github.repository }}/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} ghcr.io/${{ github.repository }}/$IMAGE_NAME:latest -# echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin -# docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:${{ steps.version.outputs.VERSION }} -# docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:latest -# -# deploy-prod: -# needs: build-and-push -# runs-on: ubuntu-latest -# environment: -# name: production -# url: https://tasks-app.example.com -# -# steps: -# - name: Checkout -# uses: actions/checkout@v4 -# -# - name: Auth to Google Cloud (WIF) -# uses: google-github-actions/auth@v2 -# with: -# workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} -# service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} -# -# - name: Set up Cloud SDK -# uses: google-github-actions/setup-gcloud@v1 -# -# - name: Configure kubectl -# run: | -# gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $PROJECT_ID -# -# - name: Install Helm -# uses: azure/setup-helm@v3 -# with: -# version: '3.12.0' -# -# - name: Extract version from tag -# id: version -# run: | -# if [[ $GITHUB_REF == refs/tags/* ]]; then -# echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT -# else -# echo "VERSION=latest" >> $GITHUB_OUTPUT -# fi -# -# - name: Get database password from Secret Manager -# run: | -# DB_PASSWORD=$(gcloud secrets versions access latest --secret="${INSTANCE_NAME}-app-db-password" --project=$PROJECT_ID) -# echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV -# -# - name: Deploy to GKE with Helm -# run: | -# helm upgrade --install tasks-app-prod ./helm/tasks-app \ -# --namespace tasks-prod \ -# --create-namespace \ -# --values ./helm/tasks-app/values.yaml \ -# --set image.repository=$REGISTRY/$PROJECT_ID/$IMAGE_NAME \ -# --set image.tag=${{ steps.version.outputs.VERSION }} \ -# --set secrets.dbPassword=$DB_PASSWORD \ -# --wait --timeout=10m -# -# - name: Verify deployment -# run: | -# kubectl get pods -n tasks-prod -# kubectl get services -n tasks-prod -# kubectl get ingress -n tasks-prod -# -# - name: Run smoke tests -# run: | -# # Wait for deployment to be ready -# kubectl wait --for=condition=available --timeout=300s deployment/tasks-app-prod -n tasks-prod -# -# # Get the ingress IP -# INGRESS_IP=$(kubectl get ingress tasks-app-prod -n tasks-prod -o jsonpath='{.status.loadBalancer.ingress[0].ip}') -# echo "Application available at: http://$INGRESS_IP" -# -# # Basic health check -# if [ ! -z "$INGRESS_IP" ]; then -# curl -f http://$INGRESS_IP/health || echo "Health check failed" -# fi -# -# notify: -# needs: [deploy-prod] -# runs-on: ubuntu-latest -# if: always() -# steps: -# - name: Notify deployment status -# run: | -# if [ "${{ needs.deploy-prod.result }}" == "success" ]; then -# echo "✅ Production deployment successful!" -# else -# echo "❌ Production deployment failed!" -# fi -# \ No newline at end of file +name: Deploy to Production + +on: + push: + branches: [ main ] + tags: + - 'v*' + workflow_dispatch: + inputs: + force_deploy: + description: 'Force deployment (skip tests)' + required: false + default: 'false' + type: boolean + +permissions: + contents: read + id-token: write + packages: write + +env: + PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} + GKE_ZONE: ${{ secrets.GKE_ZONE }} + REGISTRY: gcr.io + IMAGE_NAME: tasks-app + INSTANCE_NAME: tasks-mysql + +jobs: + test: + runs-on: ubuntu-latest + if: ${{ !inputs.force_deploy }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + cd app + pip install -r requirements.txt + + - name: Run tests + run: | + cd app + python -m pytest tests/ || echo "No tests found, continuing..." + + build-and-push: + needs: test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') + environment: + name: Production + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: Configure Docker to use gcloud as a credential helper + run: gcloud auth configure-docker + + - name: Build and push to GCR + run: | + cd app + docker build -t $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA . + docker tag $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$PROJECT_ID/$IMAGE_NAME:prod-latest + docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA + docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:prod-latest + + - name: Build and push to GitHub Container Registry + run: | + cd app + docker build -t ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA . + docker tag ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA ghcr.io/${{ github.repository }}/$IMAGE_NAME:prod-latest + echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin + docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA + docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:prod-latest + + deploy-prod: + needs: build-and-push + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') + environment: + name: Production + url: https://tasks-app-prod.example.com + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: Configure kubectl + run: | + gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $PROJECT_ID + + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: '3.12.0' + + - name: Get database password from Secret Manager + run: | + DB_PASSWORD=$(gcloud secrets versions access latest --secret="${INSTANCE_NAME}-app-db-password" --project=$PROJECT_ID) + echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV + + - name: Deploy to GKE with Helm + run: | + helm upgrade --install tasks-app-prod ./helm/tasks-app \ + --namespace tasks-prod \ + --create-namespace \ + --values ./helm/tasks-app/values-prod.yaml \ + --set image.repository=$REGISTRY/$PROJECT_ID/$IMAGE_NAME \ + --set image.tag=prod-latest \ + --set secrets.dbPassword=$DB_PASSWORD \ + --wait --timeout=5m + + - name: Verify deployment + run: | + kubectl get pods -n tasks-prod + kubectl get services -n tasks-prod + kubectl get ingress -n tasks-prod \ No newline at end of file From e391153208a42f708d5ba11b1895112c63e184d0 Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 15:10:08 +0200 Subject: [PATCH 03/12] fix(CI/CD): add artifact registry to push my docker app and also change the execution of my workflow --- .github/workflows/deploy-dev.yml | 33 +- bootstrap-wif/roles.tf | 18 ++ bootstrap-wif/variables.tf | 4 +- environments/dev/terraform.tfvars | 29 ++ environments/prd/terraform.tfvars | 25 ++ scripts/check-registry.sh | 108 +++++++ scripts/configure-artifact-registry.sh | 106 +++++++ scripts/get-artifact-registry-url.sh | 115 +++++++ scripts/get-wif-info.sh | 122 ++++++++ scripts/setup-github-secrets.sh | 290 ++++++++---------- scripts/update-wif-permissions.sh | 65 ++++ terraform/main.tf | 18 +- terraform/modules/artifact-registry/main.tf | 54 ++++ .../modules/artifact-registry/outputs.tf | 18 ++ .../modules/artifact-registry/variables.tf | 48 +++ terraform/modules/iam/main.tf | 7 + terraform/outputs.tf | 10 + terraform/variables.tf | 18 ++ 18 files changed, 916 insertions(+), 172 deletions(-) create mode 100644 bootstrap-wif/roles.tf create mode 100644 scripts/check-registry.sh create mode 100644 scripts/configure-artifact-registry.sh create mode 100644 scripts/get-artifact-registry-url.sh create mode 100644 scripts/get-wif-info.sh create mode 100644 scripts/update-wif-permissions.sh create mode 100644 terraform/modules/artifact-registry/main.tf create mode 100644 terraform/modules/artifact-registry/outputs.tf create mode 100644 terraform/modules/artifact-registry/variables.tf diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 68d5441..f5307b9 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -1,6 +1,10 @@ name: Deploy to Development on: + workflow_run: + workflows: ["Terraform (Develop)"] + types: [completed] + branches-ignore: [ main ] push: branches-ignore: [ main ] pull_request: @@ -15,7 +19,7 @@ env: PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} GKE_ZONE: ${{ secrets.GKE_ZONE }} - REGISTRY: gcr.io + REGISTRY: ${{ vars.ARTIFACT_REGISTRY_URL || 'gcr.io' }} IMAGE_NAME: tasks-app INSTANCE_NAME: tasks-mysql @@ -45,7 +49,7 @@ jobs: build-and-push: needs: test runs-on: ubuntu-latest - if: github.ref != 'refs/heads/main' + if: github.ref != 'refs/heads/main' && (github.event_name == 'workflow_run' || github.event_name == 'push') environment: name: Develop @@ -62,16 +66,31 @@ jobs: - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@v1 + - name: Get Artifact Registry URL + run: | + # Obtenir l'URL d'Artifact Registry depuis Terraform + ARTIFACT_REGISTRY_URL=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + if [ -n "$ARTIFACT_REGISTRY_URL" ]; then + echo "ARTIFACT_REGISTRY_URL=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV + echo "REGISTRY=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV + echo "✅ Artifact Registry trouvé: $ARTIFACT_REGISTRY_URL" + else + echo "⚠️ Aucun Artifact Registry trouvé, utilisation de GCR" + echo "REGISTRY=gcr.io" >> $GITHUB_ENV + fi + - name: Configure Docker to use gcloud as a credential helper run: gcloud auth configure-docker - - name: Build and push to GCR + - name: Build and push to Artifact Registry run: | cd app - docker build -t $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA . - docker tag $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$PROJECT_ID/$IMAGE_NAME:dev-latest - docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA - docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:dev-latest + # Configuration de l'authentification pour Artifact Registry + gcloud auth configure-docker $REGISTRY --quiet + docker build -t $REGISTRY/$IMAGE_NAME:$GITHUB_SHA . + docker tag $REGISTRY/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$IMAGE_NAME:dev-latest + docker push $REGISTRY/$IMAGE_NAME:$GITHUB_SHA + docker push $REGISTRY/$IMAGE_NAME:dev-latest - name: Build and push to GitHub Container Registry run: | diff --git a/bootstrap-wif/roles.tf b/bootstrap-wif/roles.tf new file mode 100644 index 0000000..9710588 --- /dev/null +++ b/bootstrap-wif/roles.tf @@ -0,0 +1,18 @@ +# Configuration des rôles supplémentaires pour GitHub Actions +# Ce fichier peut être utilisé pour ajouter des rôles spécifiques si nécessaire + +# Les rôles par défaut sont définis dans variables.tf +# Pour ajouter des rôles supplémentaires, vous pouvez : +# 1. Modifier la variable "roles" dans variables.tf +# 2. Ou créer des ressources google_project_iam_member supplémentaires ici + +# Exemple d'ajout de rôles supplémentaires si nécessaire : +# resource "google_project_iam_member" "additional_roles" { +# for_each = toset([ +# "roles/artifactregistry.admin", +# "roles/artifactregistry.writer" +# ]) +# project = var.project_id +# role = each.key +# member = "serviceAccount:${google_service_account.sa.email}" +# } diff --git a/bootstrap-wif/variables.tf b/bootstrap-wif/variables.tf index 5014027..ba4dec9 100644 --- a/bootstrap-wif/variables.tf +++ b/bootstrap-wif/variables.tf @@ -68,6 +68,8 @@ variable "roles" { "roles/servicenetworking.networksAdmin", "roles/container.admin", "roles/iam.serviceAccountAdmin", - "roles/resourcemanager.projectIamAdmin" + "roles/resourcemanager.projectIamAdmin", + "roles/artifactregistry.admin", + "roles/artifactregistry.writer" ] } \ No newline at end of file diff --git a/environments/dev/terraform.tfvars b/environments/dev/terraform.tfvars index 989ca99..4f47358 100644 --- a/environments/dev/terraform.tfvars +++ b/environments/dev/terraform.tfvars @@ -29,6 +29,35 @@ database_config = { db_tier = "db-f1-micro" db_version = "MYSQL_8_0" private_ip_prefix_len = 16 + import_global_address = true + import_sql_instance = false + import_secret = false + import_service_networking_connection = true +} + +# Configuration Artifact Registry +artifact_registry_config = { + repository_name = "tasks-app" + retention_days = 7 + cleanup_policies = [ + { + id = "delete-prerelease" + action = "DELETE" + condition = { + tag_state = "TAGGED" + tag_prefixes = ["dev-", "test-", "staging-"] + older_than = "604800s" # 7 jours + } + }, + { + id = "keep-production" + action = "KEEP" + condition = { + tag_state = "TAGGED" + tag_prefixes = ["v", "prod-", "main-"] + } + } + ] } # Configuration IAM diff --git a/environments/prd/terraform.tfvars b/environments/prd/terraform.tfvars index 376317e..ea4e04c 100644 --- a/environments/prd/terraform.tfvars +++ b/environments/prd/terraform.tfvars @@ -31,6 +31,31 @@ database_config = { private_ip_prefix_len = 16 } +# Configuration Artifact Registry +artifact_registry_config = { + repository_name = "tasks-app" + retention_days = 30 + cleanup_policies = [ + { + id = "delete-prerelease" + action = "DELETE" + condition = { + tag_state = "TAGGED" + tag_prefixes = ["dev-", "test-", "staging-"] + older_than = "2592000s" # 30 jours + } + }, + { + id = "keep-production" + action = "KEEP" + condition = { + tag_state = "TAGGED" + tag_prefixes = ["v", "prod-", "main-"] + } + } + ] +} + # Configuration IAM team_member_emails = [ "arnassalomlucas@gmail.com", diff --git a/scripts/check-registry.sh b/scripts/check-registry.sh new file mode 100644 index 0000000..541b9f1 --- /dev/null +++ b/scripts/check-registry.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# Script pour vérifier l'existence des registries et éviter d'en créer par hasard + +set -e + +# Couleurs pour les messages +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${GREEN}🔍 Vérification des registries existants${NC}" + +# Obtenir le projet actuel +PROJECT_ID=$(gcloud config get-value project) +if [ -z "$PROJECT_ID" ]; then + echo -e "${RED}❌ Aucun projet Google Cloud configuré.${NC}" + echo "Commande: gcloud config set project YOUR_PROJECT_ID" + exit 1 +fi + +echo -e "${YELLOW}📋 Projet: ${PROJECT_ID}${NC}" + +# Vérifier Google Container Registry (GCR) +echo -e "${YELLOW}🔍 Vérification de Google Container Registry (GCR)...${NC}" +GCR_BUCKETS=$(gcloud storage buckets list --filter="name:artifacts.${PROJECT_ID}.appspot.com" --format="value(name)" 2>/dev/null || echo "") + +if [ -n "$GCR_BUCKETS" ]; then + echo -e "${GREEN}✅ GCR bucket trouvé: ${GCR_BUCKETS}${NC}" +else + echo -e "${YELLOW}⚠️ Aucun bucket GCR trouvé.${NC}" + echo "Le bucket GCR sera créé automatiquement lors du premier push d'image." +fi + +# Vérifier Artifact Registry +echo -e "${YELLOW}🔍 Vérification d'Artifact Registry...${NC}" +ARTIFACT_REGISTRIES=$(gcloud artifacts repositories list --format="value(name)" --project=$PROJECT_ID 2>/dev/null || echo "") + +if [ -n "$ARTIFACT_REGISTRIES" ]; then + echo -e "${GREEN}✅ Artifact Registries trouvés:${NC}" + echo "$ARTIFACT_REGISTRIES" +else + echo -e "${YELLOW}⚠️ Aucun Artifact Registry trouvé.${NC}" + echo "Vous pouvez en créer un si nécessaire avec:" + echo "gcloud artifacts repositories create REPO_NAME --repository-format=docker --location=LOCATION" +fi + +# Vérifier les permissions du service account +echo -e "${YELLOW}🔍 Vérification des permissions du service account...${NC}" + +# Obtenir le service account GitHub Actions +SA_EMAIL=$(gcloud iam service-accounts list --filter="displayName:GitHub Terraform" --format="value(email)" --project=$PROJECT_ID) + +if [ -z "$SA_EMAIL" ]; then + echo -e "${RED}❌ Service account GitHub Actions non trouvé.${NC}" + echo "Assurez-vous que le module bootstrap-wif/ a été déployé." + exit 1 +fi + +echo -e "${GREEN}✅ Service account trouvé: ${SA_EMAIL}${NC}" + +# Vérifier les permissions IAM +echo -e "${YELLOW}🔍 Vérification des permissions IAM...${NC}" + +# Vérifier les permissions sur le projet +IAM_POLICY=$(gcloud projects get-iam-policy $PROJECT_ID --format="json" 2>/dev/null || echo "{}") + +# Vérifier les permissions spécifiques +STORAGE_ADMIN=$(echo "$IAM_POLICY" | jq -r ".bindings[] | select(.role == \"roles/storage.admin\") | .members[] | select(. == \"serviceAccount:${SA_EMAIL}\")" 2>/dev/null || echo "") +CONTAINER_ADMIN=$(echo "$IAM_POLICY" | jq -r ".bindings[] | select(.role == \"roles/container.admin\") | .members[] | select(. == \"serviceAccount:${SA_EMAIL}\")" 2>/dev/null || echo "") +ARTIFACT_ADMIN=$(echo "$IAM_POLICY" | jq -r ".bindings[] | select(.role == \"roles/artifactregistry.admin\") | .members[] | select(. == \"serviceAccount:${SA_EMAIL}\")" 2>/dev/null || echo "") + +if [ -n "$STORAGE_ADMIN" ]; then + echo -e "${GREEN}✅ Permission storage.admin accordée${NC}" +else + echo -e "${RED}❌ Permission storage.admin manquante${NC}" +fi + +if [ -n "$CONTAINER_ADMIN" ]; then + echo -e "${GREEN}✅ Permission container.admin accordée${NC}" +else + echo -e "${RED}❌ Permission container.admin manquante${NC}" +fi + +if [ -n "$ARTIFACT_ADMIN" ]; then + echo -e "${GREEN}✅ Permission artifactregistry.admin accordée${NC}" +else + echo -e "${YELLOW}⚠️ Permission artifactregistry.admin manquante${NC}" + echo "Cette permission sera ajoutée lors du prochain déploiement Terraform." +fi + +echo "" +echo -e "${BLUE}📋 Résumé:${NC}" +echo " - Projet: $PROJECT_ID" +echo " - Service Account: $SA_EMAIL" +echo " - GCR Bucket: ${GCR_BUCKETS:-'Sera créé automatiquement'}" +echo " - Artifact Registries: ${ARTIFACT_REGISTRIES:-'Aucun'}" +echo "" + +if [ -n "$STORAGE_ADMIN" ] && [ -n "$CONTAINER_ADMIN" ]; then + echo -e "${GREEN}✅ Les permissions de base sont configurées.${NC}" + echo -e "${YELLOW}💡 Vous pouvez maintenant tester le push d'images.${NC}" +else + echo -e "${RED}❌ Des permissions manquent.${NC}" + echo -e "${YELLOW}💡 Redéployez le module bootstrap-wif/ avec: terraform apply${NC}" +fi diff --git a/scripts/configure-artifact-registry.sh b/scripts/configure-artifact-registry.sh new file mode 100644 index 0000000..d9ec44a --- /dev/null +++ b/scripts/configure-artifact-registry.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +# Script pour configurer Artifact Registry et les variables GitHub + +set -e + +# Couleurs pour les messages +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${GREEN}🔧 Configuration d'Artifact Registry${NC}" + +# Vérifier que nous sommes dans le bon répertoire +if [ ! -d "terraform" ]; then + echo -e "${RED}❌ Le répertoire terraform/ n'existe pas.${NC}" + echo "Assurez-vous d'être dans le répertoire racine du projet." + exit 1 +fi + +# Aller dans le répertoire terraform +cd terraform + +# Vérifier que Terraform est initialisé +if [ ! -d ".terraform" ]; then + echo -e "${YELLOW}⚠️ Terraform n'est pas initialisé.${NC}" + echo -e "${YELLOW}🔧 Initialisation de Terraform...${NC}" + terraform init +fi + +# Obtenir les informations d'Artifact Registry +echo -e "${YELLOW}🔍 Récupération des informations d'Artifact Registry...${NC}" + +ARTIFACT_REGISTRY_URL=$(terraform output -raw artifact_registry_info 2>/dev/null | jq -r '.repository_url' 2>/dev/null || echo "") +ARTIFACT_REGISTRY_ID=$(terraform output -raw artifact_registry_info 2>/dev/null | jq -r '.repository_id' 2>/dev/null || echo "") +WRITER_SA=$(terraform output -raw artifact_registry_info 2>/dev/null | jq -r '.writer_service_account' 2>/dev/null || echo "") + +if [ -z "$ARTIFACT_REGISTRY_URL" ]; then + echo -e "${RED}❌ Impossible de récupérer les informations d'Artifact Registry.${NC}" + echo "Assurez-vous que le module artifact-registry a été déployé avec succès." + echo "Exécutez: terraform apply" + exit 1 +fi + +echo -e "${GREEN}✅ Informations d'Artifact Registry récupérées !${NC}" +echo " - URL: $ARTIFACT_REGISTRY_URL" +echo " - ID: $ARTIFACT_REGISTRY_ID" +echo " - Writer SA: $WRITER_SA" + +# Retourner au répertoire parent +cd .. + +# Vérifier que gh CLI est installé +if ! command -v gh &> /dev/null; then + echo -e "${RED}❌ GitHub CLI (gh) n'est pas installé. Veuillez l'installer d'abord.${NC}" + echo "Installation: https://cli.github.com/" + exit 1 +fi + +# Vérifier que l'utilisateur est connecté à GitHub +if ! gh auth status &> /dev/null; then + echo -e "${RED}❌ Vous n'êtes pas connecté à GitHub CLI. Veuillez vous connecter d'abord.${NC}" + echo "Commande: gh auth login" + exit 1 +fi + +# Configuration des variables GitHub +echo -e "${YELLOW}🔧 Configuration des variables GitHub...${NC}" + +# Variables pour l'environnement de développement +echo -e "${BLUE}📝 Configuration de l'environnement 'Develop'...${NC}" +gh variable set ARTIFACT_REGISTRY_URL --body="$ARTIFACT_REGISTRY_URL" --env=Develop +gh variable set REGISTRY --body="$ARTIFACT_REGISTRY_URL" --env=Develop +gh variable set IMAGE_NAME --body="tasks-app" --env=Develop +gh variable set INSTANCE_NAME --body="tasks-mysql" --env=Develop + +# Variables pour l'environnement de production +echo -e "${BLUE}📝 Configuration de l'environnement 'Production'...${NC}" +gh variable set ARTIFACT_REGISTRY_URL --body="$ARTIFACT_REGISTRY_URL" --env=Production +gh variable set REGISTRY --body="$ARTIFACT_REGISTRY_URL" --env=Production +gh variable set IMAGE_NAME --body="tasks-app" --env=Production +gh variable set INSTANCE_NAME --body="tasks-mysql" --env=Production + +echo -e "${GREEN}✅ Configuration terminée avec succès !${NC}" +echo "" +echo -e "${BLUE}📋 Résumé de la configuration:${NC}" +echo " - Artifact Registry URL: $ARTIFACT_REGISTRY_URL" +echo " - Repository ID: $ARTIFACT_REGISTRY_ID" +echo " - Writer Service Account: $WRITER_SA" +echo "" +echo -e "${YELLOW}💡 Les workflows GitHub Actions utiliseront maintenant Artifact Registry.${NC}" +echo -e "${YELLOW}💡 Vous pouvez maintenant pousser du code pour déclencher les workflows.${NC}" + +# Afficher les commandes Docker pour tester +echo "" +echo -e "${BLUE}🐳 Commandes Docker pour tester:${NC}" +echo " # Authentification" +echo " gcloud auth configure-docker $ARTIFACT_REGISTRY_URL" +echo "" +echo " # Build et push d'une image de test" +echo " docker build -t $ARTIFACT_REGISTRY_URL/test:latest ./app" +echo " docker push $ARTIFACT_REGISTRY_URL/test:latest" +echo "" +echo -e "${GREEN}🎉 Artifact Registry est maintenant configuré !${NC}" diff --git a/scripts/get-artifact-registry-url.sh b/scripts/get-artifact-registry-url.sh new file mode 100644 index 0000000..abf85ff --- /dev/null +++ b/scripts/get-artifact-registry-url.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +# Script pour obtenir l'URL d'Artifact Registry et configurer GitHub + +set -e + +# Couleurs pour les messages +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${GREEN}🔍 Recherche de l'URL d'Artifact Registry${NC}" + +# Vérifier que gcloud CLI est installé +if ! command -v gcloud &> /dev/null; then + echo -e "${RED}❌ Google Cloud CLI (gcloud) n'est pas installé.${NC}" + echo "Installation: https://cloud.google.com/sdk/docs/install" + exit 1 +fi + +# Vérifier que l'utilisateur est connecté à Google Cloud +if ! gcloud auth list --filter=status:ACTIVE --format="value(account)" | grep -q .; then + echo -e "${RED}❌ Vous n'êtes pas connecté à Google Cloud.${NC}" + echo "Commande: gcloud auth login" + exit 1 +fi + +# Obtenir le projet actuel +PROJECT_ID=$(gcloud config get-value project) +if [ -z "$PROJECT_ID" ]; then + echo -e "${RED}❌ Aucun projet Google Cloud configuré.${NC}" + echo "Commande: gcloud config set project YOUR_PROJECT_ID" + exit 1 +fi + +echo -e "${YELLOW}📋 Projet: ${PROJECT_ID}${NC}" + +# Rechercher les Artifact Registries +echo -e "${YELLOW}🔍 Recherche des Artifact Registries...${NC}" +ARTIFACT_REGISTRIES=$(gcloud artifacts repositories list --format="table(name,format,location)" --project=$PROJECT_ID 2>/dev/null || echo "") + +if [ -z "$ARTIFACT_REGISTRIES" ] || [ "$ARTIFACT_REGISTRIES" = "NAME FORMAT LOCATION" ]; then + echo -e "${RED}❌ Aucun Artifact Registry trouvé dans le projet ${PROJECT_ID}${NC}" + echo "Assurez-vous que Terraform a été déployé avec succès." + echo "Exécutez: terraform apply" + exit 1 +fi + +echo -e "${GREEN}✅ Artifact Registries trouvés:${NC}" +echo "$ARTIFACT_REGISTRIES" + +# Obtenir l'URL du premier registry Docker +echo -e "${YELLOW}🔍 Récupération de l'URL du registry Docker...${NC}" +ARTIFACT_REGISTRY_URL=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + +if [ -z "$ARTIFACT_REGISTRY_URL" ]; then + echo -e "${RED}❌ Aucun registry Docker trouvé.${NC}" + exit 1 +fi + +echo -e "${GREEN}✅ Registry Docker trouvé: ${ARTIFACT_REGISTRY_URL}${NC}" + +# Vérifier que gh CLI est installé +if ! command -v gh &> /dev/null; then + echo -e "${RED}❌ GitHub CLI (gh) n'est pas installé. Veuillez l'installer d'abord.${NC}" + echo "Installation: https://cli.github.com/" + exit 1 +fi + +# Vérifier que l'utilisateur est connecté à GitHub +if ! gh auth status &> /dev/null; then + echo -e "${RED}❌ Vous n'êtes pas connecté à GitHub CLI. Veuillez vous connecter d'abord.${NC}" + echo "Commande: gh auth login" + exit 1 +fi + +# Configuration des variables GitHub +echo -e "${YELLOW}🔧 Configuration des variables GitHub...${NC}" + +# Variables pour l'environnement de développement +echo -e "${BLUE}📝 Configuration de l'environnement 'Develop'...${NC}" +gh variable set ARTIFACT_REGISTRY_URL --body="$ARTIFACT_REGISTRY_URL" --env=Develop +gh variable set REGISTRY --body="$ARTIFACT_REGISTRY_URL" --env=Develop +gh variable set IMAGE_NAME --body="tasks-app" --env=Develop +gh variable set INSTANCE_NAME --body="tasks-mysql" --env=Develop + +# Variables pour l'environnement de production +echo -e "${BLUE}📝 Configuration de l'environnement 'Production'...${NC}" +gh variable set ARTIFACT_REGISTRY_URL --body="$ARTIFACT_REGISTRY_URL" --env=Production +gh variable set REGISTRY --body="$ARTIFACT_REGISTRY_URL" --env=Production +gh variable set IMAGE_NAME --body="tasks-app" --env=Production +gh variable set INSTANCE_NAME --body="tasks-mysql" --env=Production + +echo -e "${GREEN}✅ Configuration terminée avec succès !${NC}" +echo "" +echo -e "${BLUE}📋 Résumé de la configuration:${NC}" +echo " - Projet GCP: $PROJECT_ID" +echo " - Artifact Registry URL: $ARTIFACT_REGISTRY_URL" +echo "" +echo -e "${YELLOW}💡 Les workflows GitHub Actions utiliseront maintenant Artifact Registry.${NC}" +echo -e "${YELLOW}💡 Le workflow deploy-dev.yml s'exécutera après terraform.yml.${NC}" + +# Afficher les commandes Docker pour tester +echo "" +echo -e "${BLUE}🐳 Commandes Docker pour tester:${NC}" +echo " # Authentification" +echo " gcloud auth configure-docker $ARTIFACT_REGISTRY_URL" +echo "" +echo " # Build et push d'une image de test" +echo " docker build -t $ARTIFACT_REGISTRY_URL/test:latest ./app" +echo " docker push $ARTIFACT_REGISTRY_URL/test:latest" +echo "" +echo -e "${GREEN}🎉 Configuration terminée !${NC}" diff --git a/scripts/get-wif-info.sh b/scripts/get-wif-info.sh new file mode 100644 index 0000000..db5a469 --- /dev/null +++ b/scripts/get-wif-info.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +# Script pour obtenir les informations du module bootstrap-wif +# et afficher les valeurs nécessaires pour configurer GitHub + +set -e + +# Couleurs pour les messages +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${GREEN}🔧 Informations du module bootstrap-wif${NC}" + +# Vérifier que nous sommes dans le bon répertoire +if [ ! -d "bootstrap-wif" ]; then + echo -e "${RED}❌ Le répertoire bootstrap-wif/ n'existe pas.${NC}" + echo "Assurez-vous d'être dans le répertoire racine du projet." + exit 1 +fi + +# Aller dans le répertoire bootstrap-wif +cd bootstrap-wif + +# Vérifier que Terraform est initialisé +if [ ! -d ".terraform" ]; then + echo -e "${YELLOW}⚠️ Terraform n'est pas initialisé dans bootstrap-wif/${NC}" + echo "Exécutez d'abord: terraform init" + exit 1 +fi + +# Obtenir les outputs +echo -e "${YELLOW}📋 Récupération des informations du module WIF...${NC}" + +WIF_PROVIDER=$(terraform output -raw workload_identity_provider_name 2>/dev/null || echo "") +SA_EMAIL=$(terraform output -raw service_account_email 2>/dev/null || echo "") + +if [ -z "$WIF_PROVIDER" ] || [ -z "$SA_EMAIL" ]; then + echo -e "${RED}❌ Impossible de récupérer les informations du module WIF.${NC}" + echo "Assurez-vous que le module bootstrap-wif/ a été déployé avec succès." + echo "Exécutez: terraform apply" + exit 1 +fi + +# Obtenir les informations du projet +PROJECT_ID=$(gcloud config get-value project 2>/dev/null || echo "") + +if [ -z "$PROJECT_ID" ]; then + echo -e "${RED}❌ Aucun projet Google Cloud configuré.${NC}" + echo "Commande: gcloud config set project YOUR_PROJECT_ID" + exit 1 +fi + +# Afficher les informations +echo -e "${GREEN}✅ Informations récupérées avec succès !${NC}" +echo "" +echo -e "${BLUE}📋 Configuration GitHub Actions:${NC}" +echo " - Projet GCP: $PROJECT_ID" +echo " - Service Account: $SA_EMAIL" +echo " - WIF Provider: $WIF_PROVIDER" +echo "" + +# Afficher les commandes pour configurer GitHub +echo -e "${YELLOW}🔧 Commandes pour configurer GitHub:${NC}" +echo "" +echo -e "${BLUE}# Secrets pour l'environnement 'Develop':${NC}" +echo "gh secret set GCP_PROJECT_ID --body=\"$PROJECT_ID\" --env=Develop" +echo "gh secret set GCP_SERVICE_ACCOUNT --body=\"$SA_EMAIL\" --env=Develop" +echo "gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body=\"$WIF_PROVIDER\" --env=Develop" +echo "" +echo -e "${BLUE}# Variables pour l'environnement 'Develop':${NC}" +echo "gh variable set REGISTRY --body=\"gcr.io\" --env=Develop" +echo "gh variable set IMAGE_NAME --body=\"tasks-app\" --env=Develop" +echo "gh variable set INSTANCE_NAME --body=\"tasks-mysql\" --env=Develop" +echo "" +echo -e "${BLUE}# Secrets pour l'environnement 'Production':${NC}" +echo "gh secret set GCP_PROJECT_ID --body=\"$PROJECT_ID\" --env=Production" +echo "gh secret set GCP_SERVICE_ACCOUNT --body=\"$SA_EMAIL\" --env=Production" +echo "gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body=\"$WIF_PROVIDER\" --env=Production" +echo "" +echo -e "${BLUE}# Variables pour l'environnement 'Production':${NC}" +echo "gh variable set REGISTRY --body=\"gcr.io\" --env=Production" +echo "gh variable set IMAGE_NAME --body=\"tasks-app\" --env=Production" +echo "gh variable set INSTANCE_NAME --body=\"tasks-mysql\" --env=Production" +echo "" + +# Demander si l'utilisateur veut exécuter automatiquement +echo -e "${YELLOW}🤔 Voulez-vous exécuter automatiquement ces commandes ? (y/N)${NC}" +read -p "> " AUTO_EXECUTE + +if [[ $AUTO_EXECUTE =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}🔧 Exécution automatique des commandes...${NC}" + + # Secrets pour Develop + echo -e "${BLUE}📝 Configuration de l'environnement 'Develop'...${NC}" + gh secret set GCP_PROJECT_ID --body="$PROJECT_ID" --env=Develop + gh secret set GCP_SERVICE_ACCOUNT --body="$SA_EMAIL" --env=Develop + gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body="$WIF_PROVIDER" --env=Develop + gh variable set REGISTRY --body="gcr.io" --env=Develop + gh variable set IMAGE_NAME --body="tasks-app" --env=Develop + gh variable set INSTANCE_NAME --body="tasks-mysql" --env=Develop + + # Secrets pour Production + echo -e "${BLUE}📝 Configuration de l'environnement 'Production'...${NC}" + gh secret set GCP_PROJECT_ID --body="$PROJECT_ID" --env=Production + gh secret set GCP_SERVICE_ACCOUNT --body="$SA_EMAIL" --env=Production + gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body="$WIF_PROVIDER" --env=Production + gh variable set REGISTRY --body="gcr.io" --env=Production + gh variable set IMAGE_NAME --body="tasks-app" --env=Production + gh variable set INSTANCE_NAME --body="tasks-mysql" --env=Production + + echo -e "${GREEN}✅ Configuration terminée avec succès !${NC}" +else + echo -e "${YELLOW}💡 Copiez et exécutez les commandes ci-dessus pour configurer GitHub.${NC}" +fi + +# Retourner au répertoire parent +cd .. + +echo -e "${GREEN}🎉 Les workflows CI/CD sont maintenant prêts !${NC}" diff --git a/scripts/setup-github-secrets.sh b/scripts/setup-github-secrets.sh index 6466d08..0e68de9 100644 --- a/scripts/setup-github-secrets.sh +++ b/scripts/setup-github-secrets.sh @@ -1,173 +1,139 @@ #!/bin/bash -# Script pour configurer les secrets GitHub Actions +# Script pour configurer les secrets GitHub pour les workflows CI/CD +# Ce script doit être exécuté après le déploiement Terraform + set -e -# Couleurs pour les logs +# Couleurs pour les messages RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' -BLUE='\033[0;34m' NC='\033[0m' # No Color -log() { - echo -e "${GREEN}[INFO]${NC} $1" -} - -warn() { - echo -e "${YELLOW}[WARN]${NC} $1" -} - -error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -# Vérifier les prérequis -check_prerequisites() { - log "Vérification des prérequis..." - - if ! command -v terraform &> /dev/null; then - error "Terraform n'est pas installé" - exit 1 - fi - - if ! command -v gh &> /dev/null; then - error "GitHub CLI n'est pas installé" - exit 1 - fi - - if ! gh auth status &> /dev/null; then - error "GitHub CLI n'est pas authentifié" - exit 1 - fi - - log "Prérequis OK" -} - -# Récupérer les informations Terraform -get_terraform_outputs() { - log "Récupération des outputs Terraform..." - - cd terraform - - # Initialiser Terraform si nécessaire - if [ ! -d ".terraform" ]; then - log "Initialisation de Terraform..." - terraform init -backend-config=../configs/dev.config - fi - - # Récupérer les outputs - PROJECT_ID=$(terraform output -raw deployment_info | jq -r '.project_id') - CLUSTER_NAME=$(terraform output -raw deployment_info | jq -r '.cluster_name') - REGION=$(terraform output -raw deployment_info | jq -r '.region') - INSTANCE_NAME="tasks-mysql" - - cd ../bootstrap-wif - - # Récupérer les informations WIF - if [ ! -d ".terraform" ]; then - log "Initialisation de bootstrap-wif..." - terraform init -backend-config=../configs/bootstrap-wif-dev.config - fi - - WIF_PROVIDER=$(terraform output -raw workload_identity_provider) - WIF_SERVICE_ACCOUNT=$(terraform output -raw service_account_email) - - cd .. - - log "Informations récupérées:" - info "Project ID: $PROJECT_ID" - info "Cluster: $CLUSTER_NAME" - info "Region: $REGION" - info "Instance Name: $INSTANCE_NAME" - info "WIF Provider: $WIF_PROVIDER" - info "Service Account: $WIF_SERVICE_ACCOUNT" -} - -# Configurer les secrets GitHub -setup_github_secrets() { - log "Configuration des secrets GitHub..." - - # Secrets pour l'environnement development - log "Configuration de l'environnement 'development'..." - gh secret set GCP_PROJECT_ID --body "$PROJECT_ID" --env development - gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body "$WIF_PROVIDER" --env development - gh secret set GCP_SERVICE_ACCOUNT --body "$WIF_SERVICE_ACCOUNT" --env development - gh secret set GKE_CLUSTER_NAME --body "$CLUSTER_NAME" --env development - gh secret set GKE_ZONE --body "$REGION" --env development - - # Secrets pour l'environnement production - log "Configuration de l'environnement 'production'..." - gh secret set GCP_PROJECT_ID --body "$PROJECT_ID" --env production - gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body "$WIF_PROVIDER" --env production - gh secret set GCP_SERVICE_ACCOUNT --body "$WIF_SERVICE_ACCOUNT" --env production - gh secret set GKE_CLUSTER_NAME --body "$CLUSTER_NAME" --env production - gh secret set GKE_ZONE --body "$REGION" --env production - - log "Secrets configurés avec succès" -} - -# Afficher les informations de configuration -show_configuration_info() { - log "Configuration terminée !" - echo "" - info "=== INFORMATIONS DE CONFIGURATION ===" - echo "" - info "Project ID: $PROJECT_ID" - info "Cluster: $CLUSTER_NAME" - info "Region: $REGION" - info "WIF Provider: $WIF_PROVIDER" - info "Service Account: $WIF_SERVICE_ACCOUNT" - echo "" - warn "=== ACTIONS REQUISES ===" - echo "" - warn "1. Les mots de passe de base de données sont gérés automatiquement" - warn " via Google Secret Manager (${INSTANCE_NAME}-app-db-password)" - echo "" - warn "2. Configurez les environnements GitHub:" - warn " - Allez dans Settings > Environments" - warn " - Créez les environnements 'development' et 'production'" - warn " - Configurez les protection rules si nécessaire" - echo "" - warn "3. Testez le déploiement:" - warn " - Push sur une branche (sauf main) → Déploiement DEV" - warn " - Push sur main → Déploiement PROD" - echo "" - log "Configuration terminée avec succès !" -} - -# Fonction principale -main() { - log "Démarrage de la configuration GitHub Actions..." - - check_prerequisites - get_terraform_outputs - setup_github_secrets - show_configuration_info -} - -# Aide -show_help() { - echo "Usage: $0" - echo "" - echo "Ce script configure automatiquement les secrets GitHub Actions" - echo "pour le déploiement sur Google Cloud Platform." - echo "" - echo "Prérequis:" - echo " - Terraform configuré et déployé" - echo " - GitHub CLI installé et authentifié" - echo " - Accès au repository GitHub" -} - -# Gestion des arguments -if [[ "$1" == "-h" || "$1" == "--help" ]]; then - show_help - exit 0 +echo -e "${GREEN}🔧 Configuration des secrets GitHub pour les workflows CI/CD${NC}" + +# Vérifier que gh CLI est installé +if ! command -v gh &> /dev/null; then + echo -e "${RED}❌ GitHub CLI (gh) n'est pas installé. Veuillez l'installer d'abord.${NC}" + echo "Installation: https://cli.github.com/" + exit 1 fi -# Exécuter le script principal -main +# Vérifier que l'utilisateur est connecté à GitHub +if ! gh auth status &> /dev/null; then + echo -e "${RED}❌ Vous n'êtes pas connecté à GitHub CLI. Veuillez vous connecter d'abord.${NC}" + echo "Commande: gh auth login" + exit 1 +fi + +# Vérifier que gcloud CLI est installé +if ! command -v gcloud &> /dev/null; then + echo -e "${RED}❌ Google Cloud CLI (gcloud) n'est pas installé. Veuillez l'installer d'abord.${NC}" + echo "Installation: https://cloud.google.com/sdk/docs/install" + exit 1 +fi + +# Vérifier que l'utilisateur est connecté à Google Cloud +if ! gcloud auth list --filter=status:ACTIVE --format="value(account)" | grep -q .; then + echo -e "${RED}❌ Vous n'êtes pas connecté à Google Cloud. Veuillez vous connecter d'abord.${NC}" + echo "Commande: gcloud auth login" + exit 1 +fi + +# Obtenir les informations du projet +PROJECT_ID=$(gcloud config get-value project) +if [ -z "$PROJECT_ID" ]; then + echo -e "${RED}❌ Aucun projet Google Cloud configuré. Veuillez configurer un projet.${NC}" + echo "Commande: gcloud config set project YOUR_PROJECT_ID" + exit 1 +fi + +echo -e "${YELLOW}📋 Configuration pour le projet: ${PROJECT_ID}${NC}" + +# Obtenir les informations du cluster GKE +echo -e "${YELLOW}🔍 Recherche des clusters GKE...${NC}" +CLUSTERS=$(gcloud container clusters list --format="value(name,location)" --project=$PROJECT_ID) + +if [ -z "$CLUSTERS" ]; then + echo -e "${RED}❌ Aucun cluster GKE trouvé dans le projet ${PROJECT_ID}${NC}" + exit 1 +fi + +# Afficher les clusters disponibles +echo -e "${YELLOW}📋 Clusters GKE disponibles:${NC}" +echo "$CLUSTERS" + +# Demander à l'utilisateur de choisir le cluster +echo -e "${YELLOW}🤔 Veuillez choisir le cluster pour l'environnement de développement:${NC}" +read -p "Nom du cluster: " CLUSTER_NAME +read -p "Région du cluster: " CLUSTER_REGION + +# Obtenir les informations du service account GitHub Actions depuis bootstrap-wif +echo -e "${YELLOW}🔍 Recherche du service account GitHub Actions...${NC}" +SA_EMAIL=$(gcloud iam service-accounts list --filter="displayName:GitHub Terraform" --format="value(email)" --project=$PROJECT_ID) + +if [ -z "$SA_EMAIL" ]; then + echo -e "${RED}❌ Service account GitHub Actions non trouvé.${NC}" + echo "Assurez-vous que le module bootstrap-wif/ a été déployé avec succès." + exit 1 +fi + +echo -e "${GREEN}✅ Service account trouvé: ${SA_EMAIL}${NC}" + +# Obtenir les informations Workload Identity Federation +echo -e "${YELLOW}🔍 Recherche de la configuration Workload Identity Federation...${NC}" +WIF_PROVIDER=$(gcloud iam workload-identity-pools providers list --location=global --format="value(name)" --project=$PROJECT_ID | head -1) + +if [ -z "$WIF_PROVIDER" ]; then + echo -e "${RED}❌ Workload Identity Federation non configuré.${NC}" + echo "Veuillez d'abord configurer WIF avec le module bootstrap-wif/" + exit 1 +fi + +echo -e "${GREEN}✅ WIF Provider trouvé: ${WIF_PROVIDER}${NC}" + +# Configuration des secrets GitHub +echo -e "${YELLOW}🔧 Configuration des secrets GitHub...${NC}" + +# Secrets pour l'environnement de développement +echo -e "${YELLOW}📝 Configuration des secrets pour l'environnement 'Develop'...${NC}" + +gh secret set GCP_PROJECT_ID --body="$PROJECT_ID" --env=Develop +gh secret set GKE_CLUSTER_NAME --body="$CLUSTER_NAME" --env=Develop +gh secret set GKE_ZONE --body="$CLUSTER_REGION" --env=Develop +gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body="$WIF_PROVIDER" --env=Develop +gh secret set GCP_SERVICE_ACCOUNT --body="$SA_EMAIL" --env=Develop + +# Variables pour l'environnement de développement +echo -e "${YELLOW}📝 Configuration des variables pour l'environnement 'Develop'...${NC}" + +gh variable set REGISTRY --body="gcr.io" --env=Develop +gh variable set IMAGE_NAME --body="tasks-app" --env=Develop +gh variable set INSTANCE_NAME --body="tasks-mysql" --env=Develop + +# Secrets pour l'environnement de production +echo -e "${YELLOW}📝 Configuration des secrets pour l'environnement 'Production'...${NC}" + +gh secret set GCP_PROJECT_ID --body="$PROJECT_ID" --env=Production +gh secret set GKE_CLUSTER_NAME --body="$CLUSTER_NAME" --env=Production +gh secret set GKE_ZONE --body="$CLUSTER_REGION" --env=Production +gh secret set GCP_WORKLOAD_IDENTITY_PROVIDER --body="$WIF_PROVIDER" --env=Production +gh secret set GCP_SERVICE_ACCOUNT --body="$SA_EMAIL" --env=Production + +# Variables pour l'environnement de production +echo -e "${YELLOW}📝 Configuration des variables pour l'environnement 'Production'...${NC}" + +gh variable set REGISTRY --body="gcr.io" --env=Production +gh variable set IMAGE_NAME --body="tasks-app" --env=Production +gh variable set INSTANCE_NAME --body="tasks-mysql" --env=Production + +echo -e "${GREEN}✅ Configuration terminée avec succès !${NC}" +echo -e "${YELLOW}📋 Résumé de la configuration:${NC}" +echo " - Projet GCP: $PROJECT_ID" +echo " - Cluster GKE: $CLUSTER_NAME ($CLUSTER_REGION)" +echo " - Service Account: $SA_EMAIL" +echo " - WIF Provider: $WIF_PROVIDER" +echo "" +echo -e "${GREEN}🎉 Les workflows CI/CD sont maintenant configurés !${NC}" +echo -e "${YELLOW}💡 Vous pouvez maintenant pousser du code pour déclencher les workflows.${NC}" \ No newline at end of file diff --git a/scripts/update-wif-permissions.sh b/scripts/update-wif-permissions.sh new file mode 100644 index 0000000..f0ef0a5 --- /dev/null +++ b/scripts/update-wif-permissions.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Script pour mettre à jour les permissions du module bootstrap-wif +# et éviter de créer des registries par hasard + +set -e + +# Couleurs pour les messages +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${GREEN}🔧 Mise à jour des permissions WIF${NC}" + +# Vérifier que nous sommes dans le bon répertoire +if [ ! -d "bootstrap-wif" ]; then + echo -e "${RED}❌ Le répertoire bootstrap-wif/ n'existe pas.${NC}" + echo "Assurez-vous d'être dans le répertoire racine du projet." + exit 1 +fi + +# Aller dans le répertoire bootstrap-wif +cd bootstrap-wif + +# Vérifier que Terraform est initialisé +if [ ! -d ".terraform" ]; then + echo -e "${YELLOW}⚠️ Terraform n'est pas initialisé.${NC}" + echo -e "${YELLOW}🔧 Initialisation de Terraform...${NC}" + terraform init +fi + +# Vérifier l'état actuel +echo -e "${YELLOW}🔍 Vérification de l'état actuel...${NC}" +terraform plan + +# Demander confirmation +echo -e "${YELLOW}🤔 Voulez-vous appliquer les changements ? (y/N)${NC}" +read -p "> " CONFIRM + +if [[ $CONFIRM =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}🔧 Application des changements...${NC}" + terraform apply -auto-approve + + echo -e "${GREEN}✅ Permissions mises à jour avec succès !${NC}" + + # Afficher les nouvelles informations + echo -e "${BLUE}📋 Nouvelles informations:${NC}" + WIF_PROVIDER=$(terraform output -raw workload_identity_provider_name) + SA_EMAIL=$(terraform output -raw service_account_email) + + echo " - WIF Provider: $WIF_PROVIDER" + echo " - Service Account: $SA_EMAIL" + + echo -e "${YELLOW}💡 Vous pouvez maintenant configurer GitHub avec ces informations.${NC}" + echo -e "${YELLOW}💡 Utilisez: ./scripts/get-wif-info.sh${NC}" +else + echo -e "${YELLOW}❌ Mise à jour annulée.${NC}" +fi + +# Retourner au répertoire parent +cd .. + +echo -e "${GREEN}🎉 Script terminé !${NC}" diff --git a/terraform/main.tf b/terraform/main.tf index dbfcca4..0b72aa3 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -60,7 +60,21 @@ module "database" { depends_on = [module.network] } -# Module Kubernetes (dépend du réseau, IAM et base de données) +# Module Artifact Registry +module "artifact_registry" { + source = "./modules/artifact-registry" + + project_id = var.project_id + region = var.region + environment = var.environment + repository_name = var.artifact_registry_config.repository_name + + # Configuration de rétention + retention_days = var.artifact_registry_config.retention_days + cleanup_policies = var.artifact_registry_config.cleanup_policies +} + +# Module Kubernetes (dépend du réseau, IAM, base de données et Artifact Registry) module "kubernetes" { source = "./modules/kubernetes" @@ -78,5 +92,5 @@ module "kubernetes" { node_zones = var.kubernetes_config.node_zones nodes_service_account_email = module.iam.gke_nodes_service_account_email - depends_on = [module.network, module.iam, module.database] + depends_on = [module.network, module.iam, module.database, module.artifact_registry] } diff --git a/terraform/modules/artifact-registry/main.tf b/terraform/modules/artifact-registry/main.tf new file mode 100644 index 0000000..2ec65be --- /dev/null +++ b/terraform/modules/artifact-registry/main.tf @@ -0,0 +1,54 @@ +# Module Artifact Registry - Gestion des registries Docker et permissions IAM + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.0" + } + } +} + +# Artifact Registry pour les images Docker +resource "google_artifact_registry_repository" "docker_repo" { + project = var.project_id + location = var.region + repository_id = "${var.repository_name}-${var.environment}" + description = "Docker repository for ${var.environment} environment" + format = "DOCKER" + + labels = { + environment = var.environment + purpose = "docker-images" + } + + # Configuration de la rétention des images + cleanup_policies { + id = "delete-prerelease" + action = "DELETE" + condition { + tag_state = "TAGGED" + tag_prefixes = [ + "dev-", + "test-", + "staging-" + ] + older_than = "604800s" # 7 jours + } + } + + cleanup_policies { + id = "keep-minimum-versions" + action = "KEEP" + condition { + tag_state = "TAGGED" + tag_prefixes = [ + "v", + "prod-" + ] + } + } +} + +# Les permissions IAM sont gérées par les modules iam/ et bootstrap-wif/ +# Ce module se contente de créer le registry Artifact Registry diff --git a/terraform/modules/artifact-registry/outputs.tf b/terraform/modules/artifact-registry/outputs.tf new file mode 100644 index 0000000..a0172bb --- /dev/null +++ b/terraform/modules/artifact-registry/outputs.tf @@ -0,0 +1,18 @@ +# Outputs pour le module Artifact Registry + +output "repository_id" { + description = "ID du repository Artifact Registry" + value = google_artifact_registry_repository.docker_repo.repository_id +} + +output "repository_name" { + description = "Nom complet du repository Artifact Registry" + value = google_artifact_registry_repository.docker_repo.name +} + +output "repository_url" { + description = "URL du repository Artifact Registry" + value = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.docker_repo.repository_id}" +} + +# Les outputs IAM sont gérés par les modules iam/ et bootstrap-wif/ diff --git a/terraform/modules/artifact-registry/variables.tf b/terraform/modules/artifact-registry/variables.tf new file mode 100644 index 0000000..31fe121 --- /dev/null +++ b/terraform/modules/artifact-registry/variables.tf @@ -0,0 +1,48 @@ +# Variables pour le module Artifact Registry + +variable "project_id" { + description = "ID du projet GCP" + type = string +} + +variable "region" { + description = "Région GCP pour le registry" + type = string +} + +variable "environment" { + description = "Environnement (dev, staging, prod)" + type = string + validation { + condition = contains(["dev", "staging", "prod"], var.environment) + error_message = "L'environnement doit être 'dev', 'staging' ou 'prod'." + } +} + +variable "repository_name" { + description = "Nom du repository Artifact Registry" + type = string + default = "tasks-app" +} + +# Les variables IAM sont gérées par les modules iam/ et bootstrap-wif/ + +variable "retention_days" { + description = "Nombre de jours de rétention pour les images de développement" + type = number + default = 7 +} + +variable "cleanup_policies" { + description = "Politiques de nettoyage personnalisées" + type = list(object({ + id = string + action = string + condition = object({ + tag_state = optional(string) + tag_prefixes = optional(list(string)) + older_than = optional(string) + }) + })) + default = [] +} diff --git a/terraform/modules/iam/main.tf b/terraform/modules/iam/main.tf index e825e0d..1196dd9 100644 --- a/terraform/modules/iam/main.tf +++ b/terraform/modules/iam/main.tf @@ -110,6 +110,13 @@ resource "google_project_iam_member" "gke_nodes_cloudsql_client" { member = "serviceAccount:${google_service_account.gke_nodes.email}" } +# Permissions pour Artifact Registry +resource "google_project_iam_member" "gke_nodes_artifactregistry_reader" { + project = var.project_id + role = "roles/artifactregistry.reader" + member = "serviceAccount:${google_service_account.gke_nodes.email}" +} + # Les permissions pour GitHub Actions sont gérées via Workload Identity Federation # dans le module bootstrap-wif/ diff --git a/terraform/outputs.tf b/terraform/outputs.tf index aaa9952..1fef2e8 100644 --- a/terraform/outputs.tf +++ b/terraform/outputs.tf @@ -39,4 +39,14 @@ output "deployment_info" { } } +# Outputs pour Artifact Registry +output "artifact_registry_info" { + description = "Informations sur Artifact Registry" + value = { + repository_id = module.artifact_registry.repository_id + repository_name = module.artifact_registry.repository_name + repository_url = module.artifact_registry.repository_url + } +} + # Les outputs pour GitHub Actions sont gérés dans bootstrap-wif/ diff --git a/terraform/variables.tf b/terraform/variables.tf index eec6d35..5d7ecd6 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -101,3 +101,21 @@ variable "network_config" { ip_range = string }) } + +variable "artifact_registry_config" { + description = "Configuration pour Artifact Registry" + type = object({ + repository_name = optional(string, "tasks-app") + retention_days = optional(number, 7) + cleanup_policies = optional(list(object({ + id = string + action = string + condition = object({ + tag_state = optional(string) + tag_prefixes = optional(list(string)) + older_than = optional(string) + }) + })), []) + }) + default = {} +} From b2ac3150aab46b4c5981b91d45e5afc753948168 Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 15:21:55 +0200 Subject: [PATCH 04/12] fix(CI/CD): fix the pipeline so that the deployement can be execute after the terraform workflow in dev --- .github/workflows/deploy-dev.yml | 31 ++++++++++++++++++++++++------- terraform/main.tf | 8 ++++---- terraform/outputs.tf | 4 ++-- terraform/variables.tf | 2 +- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index f5307b9..70f954e 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -49,7 +49,11 @@ jobs: build-and-push: needs: test runs-on: ubuntu-latest - if: github.ref != 'refs/heads/main' && (github.event_name == 'workflow_run' || github.event_name == 'push') + if: | + github.ref != 'refs/heads/main' && ( + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || + github.event_name == 'push' + ) environment: name: Develop @@ -69,28 +73,41 @@ jobs: - name: Get Artifact Registry URL run: | # Obtenir l'URL d'Artifact Registry depuis Terraform + echo "🔍 Recherche d'Artifact Registry dans le projet $PROJECT_ID..." ARTIFACT_REGISTRY_URL=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + if [ -n "$ARTIFACT_REGISTRY_URL" ]; then echo "ARTIFACT_REGISTRY_URL=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV echo "REGISTRY=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV echo "✅ Artifact Registry trouvé: $ARTIFACT_REGISTRY_URL" else - echo "⚠️ Aucun Artifact Registry trouvé, utilisation de GCR" - echo "REGISTRY=gcr.io" >> $GITHUB_ENV + echo "❌ Aucun Artifact Registry trouvé !" + echo "Vérifiez que Terraform a été déployé avec succès." + exit 1 fi - - name: Configure Docker to use gcloud as a credential helper - run: gcloud auth configure-docker + - name: Configure Docker for Artifact Registry + run: | + echo "🔧 Configuration de Docker pour Artifact Registry..." + gcloud auth configure-docker $REGISTRY --quiet + echo "✅ Docker configuré pour $REGISTRY" - name: Build and push to Artifact Registry run: | cd app - # Configuration de l'authentification pour Artifact Registry - gcloud auth configure-docker $REGISTRY --quiet + echo "🐳 Construction de l'image Docker..." + echo "Registry: $REGISTRY" + echo "Image: $IMAGE_NAME" + echo "Tag: $GITHUB_SHA" + + # Construction de l'image avec le bon registry docker build -t $REGISTRY/$IMAGE_NAME:$GITHUB_SHA . docker tag $REGISTRY/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$IMAGE_NAME:dev-latest + + echo "📤 Push vers Artifact Registry..." docker push $REGISTRY/$IMAGE_NAME:$GITHUB_SHA docker push $REGISTRY/$IMAGE_NAME:dev-latest + echo "✅ Images poussées avec succès vers $REGISTRY" - name: Build and push to GitHub Container Registry run: | diff --git a/terraform/main.tf b/terraform/main.tf index 0b72aa3..1d8b0dc 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -64,13 +64,13 @@ module "database" { module "artifact_registry" { source = "./modules/artifact-registry" - project_id = var.project_id - region = var.region - environment = var.environment + project_id = var.project_id + region = var.region + environment = var.environment repository_name = var.artifact_registry_config.repository_name # Configuration de rétention - retention_days = var.artifact_registry_config.retention_days + retention_days = var.artifact_registry_config.retention_days cleanup_policies = var.artifact_registry_config.cleanup_policies } diff --git a/terraform/outputs.tf b/terraform/outputs.tf index 1fef2e8..cacbe00 100644 --- a/terraform/outputs.tf +++ b/terraform/outputs.tf @@ -43,9 +43,9 @@ output "deployment_info" { output "artifact_registry_info" { description = "Informations sur Artifact Registry" value = { - repository_id = module.artifact_registry.repository_id + repository_id = module.artifact_registry.repository_id repository_name = module.artifact_registry.repository_name - repository_url = module.artifact_registry.repository_url + repository_url = module.artifact_registry.repository_url } } diff --git a/terraform/variables.tf b/terraform/variables.tf index 5d7ecd6..9bf2b04 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -106,7 +106,7 @@ variable "artifact_registry_config" { description = "Configuration pour Artifact Registry" type = object({ repository_name = optional(string, "tasks-app") - retention_days = optional(number, 7) + retention_days = optional(number, 7) cleanup_policies = optional(list(object({ id = string action = string From cb0649ca0dd53dd084504d5b2e54a696f94cfab5 Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 15:46:43 +0200 Subject: [PATCH 05/12] feat(CI/CD): execute all of the workflow into one file --- .github/workflows/deploy-dev-unified.yml | 317 +++++++++++++++++++++++ .github/workflows/deploy-dev.yml | 171 ------------ .github/workflows/terraform.yml | 211 --------------- 3 files changed, 317 insertions(+), 382 deletions(-) create mode 100644 .github/workflows/deploy-dev-unified.yml delete mode 100644 .github/workflows/deploy-dev.yml delete mode 100644 .github/workflows/terraform.yml diff --git a/.github/workflows/deploy-dev-unified.yml b/.github/workflows/deploy-dev-unified.yml new file mode 100644 index 0000000..a6262da --- /dev/null +++ b/.github/workflows/deploy-dev-unified.yml @@ -0,0 +1,317 @@ +name: Deploy to Development (Unified) + +on: + push: + branches-ignore: [ main ] + pull_request: + branches-ignore: [ main ] + +permissions: + contents: read + id-token: write + packages: write + pull-requests: write + +env: + TF_IN_AUTOMATION: true + TF_INPUT: false + PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} + GKE_ZONE: ${{ secrets.GKE_ZONE }} + IMAGE_NAME: tasks-app + INSTANCE_NAME: tasks-mysql + +jobs: + # ===== PHASE 1: TERRAFORM ===== + terraform-fmt-validate: + name: Terraform Format & Validate + runs-on: ubuntu-latest + environment: + name: Develop + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.8.5 + terraform_wrapper: true + + - name: Cache Terraform plugins + uses: actions/cache@v4 + with: + path: | + ~/.terraform.d/plugin-cache + ./.terraform + key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} + restore-keys: | + ${{ runner.os }}-terraform- + + - name: Terraform Init (backend dev) + run: | + cd terraform + terraform init \ + -backend-config=../configs/dev.config + + - name: Terraform Format Check + run: | + cd terraform + terraform fmt -check -recursive + + - name: Terraform Validate + run: | + cd terraform + terraform validate + + terraform-plan: + name: Terraform Plan + runs-on: ubuntu-latest + needs: [terraform-fmt-validate] + if: github.event_name == 'pull_request' || github.event_name == 'push' + environment: + name: Develop + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.8.5 + terraform_wrapper: true + + - name: Cache Terraform plugins + uses: actions/cache@v4 + with: + path: | + ~/.terraform.d/plugin-cache + ./.terraform + key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} + restore-keys: | + ${{ runner.os }}-terraform- + + - name: Terraform Init (backend dev) + run: | + cd terraform + terraform init \ + -backend-config=../configs/dev.config + + - name: Terraform Plan (env dev) + run: | + cd terraform + terraform plan \ + -var-file=../environments/dev/terraform.tfvars \ + -input=false + + terraform-apply: + name: Terraform Apply + runs-on: ubuntu-latest + needs: [terraform-plan] + if: github.event_name == 'push' + environment: + name: Develop + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.8.5 + terraform_wrapper: true + + - name: Cache Terraform plugins + uses: actions/cache@v4 + with: + path: | + ~/.terraform.d/plugin-cache + ./.terraform + key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} + restore-keys: | + ${{ runner.os }}-terraform- + + - name: Terraform Init (backend dev) + run: | + cd terraform + terraform init \ + -backend-config=../configs/dev.config + + - name: Terraform Apply (env dev) + run: | + cd terraform + terraform apply \ + -auto-approve \ + -var-file=../environments/dev/terraform.tfvars \ + -input=false + + # ===== PHASE 2: TESTS ===== + test: + name: Run Tests + runs-on: ubuntu-latest + needs: [terraform-apply] + if: github.event_name == 'push' + environment: + name: Develop + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + cd app + pip install -r requirements.txt + + - name: Run tests + run: | + cd app + python -m pytest tests/ || echo "No tests found, continuing..." + + # ===== PHASE 3: BUILD & DEPLOY ===== + build-and-push: + name: Build & Push Docker Images + runs-on: ubuntu-latest + needs: [test] + if: github.event_name == 'push' + environment: + name: Develop + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: Get Artifact Registry URL + run: | + echo "🔍 Recherche d'Artifact Registry dans le projet $PROJECT_ID..." + ARTIFACT_REGISTRY_URL=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + + if [ -n "$ARTIFACT_REGISTRY_URL" ]; then + echo "ARTIFACT_REGISTRY_URL=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV + echo "REGISTRY=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV + echo "✅ Artifact Registry trouvé: $ARTIFACT_REGISTRY_URL" + else + echo "❌ Aucun Artifact Registry trouvé !" + echo "Vérifiez que Terraform a été déployé avec succès." + exit 1 + fi + + - name: Configure Docker for Artifact Registry + run: | + echo "🔧 Configuration de Docker pour Artifact Registry..." + gcloud auth configure-docker $REGISTRY --quiet + echo "✅ Docker configuré pour $REGISTRY" + + - name: Build and push to Artifact Registry + run: | + cd app + echo "🐳 Construction de l'image Docker..." + echo "Registry: $REGISTRY" + echo "Image: $IMAGE_NAME" + echo "Tag: $GITHUB_SHA" + + # Construction de l'image avec le bon registry + docker build -t $REGISTRY/$IMAGE_NAME:$GITHUB_SHA . + docker tag $REGISTRY/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$IMAGE_NAME:dev-latest + + echo "📤 Push vers Artifact Registry..." + docker push $REGISTRY/$IMAGE_NAME:$GITHUB_SHA + docker push $REGISTRY/$IMAGE_NAME:dev-latest + echo "✅ Images poussées avec succès vers $REGISTRY" + + - name: Build and push to GitHub Container Registry + run: | + cd app + docker build -t ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA . + docker tag ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA ghcr.io/${{ github.repository }}/$IMAGE_NAME:dev-latest + echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin + docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA + docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:dev-latest + + # ===== PHASE 4: KUBERNETES DEPLOYMENT ===== + #deploy-dev: + # name: Deploy to GKE + # runs-on: ubuntu-latest + # needs: [build-and-push] + # if: github.event_name == 'push' + # environment: + # name: development + # url: https://tasks-app-dev.example.com + # steps: + # - name: Checkout + # uses: actions/checkout@v4 +# + # - name: Auth to Google Cloud (WIF) + # uses: google-github-actions/auth@v2 + # with: + # workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + # service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} +# + # - name: Set up Cloud SDK + # uses: google-github-actions/setup-gcloud@v1 +# + # - name: Get Artifact Registry URL + # run: | + # ARTIFACT_REGISTRY_URL=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + # echo "REGISTRY=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV +# + # - name: Configure kubectl + # run: | + # gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $PROJECT_ID +# + # - name: Install Helm + # uses: azure/setup-helm@v3 + # with: + # version: '3.12.0' +# + # - name: Get database password from Secret Manager + # run: | + # DB_PASSWORD=$(gcloud secrets versions access latest --secret="${INSTANCE_NAME}-app-db-password" --project=$PROJECT_ID) + # echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV +# + # - name: Deploy to GKE with Helm + # run: | + # helm upgrade --install tasks-app-dev ./helm/tasks-app \ + # --namespace tasks-dev \ + # --create-namespace \ + # --values ./helm/tasks-app/values-dev.yaml \ + # --set image.repository=$REGISTRY/$PROJECT_ID/$IMAGE_NAME \ + # --set image.tag=dev-latest \ + # --set secrets.dbPassword=$DB_PASSWORD \ + # --wait --timeout=5m +# + # - name: Verify deployment + # run: | + # kubectl get pods -n tasks-dev + # kubectl get services -n tasks-dev + # kubectl get ingress -n tasks-dev diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml deleted file mode 100644 index 70f954e..0000000 --- a/.github/workflows/deploy-dev.yml +++ /dev/null @@ -1,171 +0,0 @@ -name: Deploy to Development - -on: - workflow_run: - workflows: ["Terraform (Develop)"] - types: [completed] - branches-ignore: [ main ] - push: - branches-ignore: [ main ] - pull_request: - branches-ignore: [ main ] - -permissions: - contents: read - id-token: write - packages: write - -env: - PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} - GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} - GKE_ZONE: ${{ secrets.GKE_ZONE }} - REGISTRY: ${{ vars.ARTIFACT_REGISTRY_URL || 'gcr.io' }} - IMAGE_NAME: tasks-app - INSTANCE_NAME: tasks-mysql - -jobs: - test: - runs-on: ubuntu-latest - environment: - name: Develop - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install dependencies - run: | - cd app - pip install -r requirements.txt - - - name: Run tests - run: | - cd app - python -m pytest tests/ || echo "No tests found, continuing..." - - build-and-push: - needs: test - runs-on: ubuntu-latest - if: | - github.ref != 'refs/heads/main' && ( - (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || - github.event_name == 'push' - ) - environment: - name: Develop - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Set up Cloud SDK - uses: google-github-actions/setup-gcloud@v1 - - - name: Get Artifact Registry URL - run: | - # Obtenir l'URL d'Artifact Registry depuis Terraform - echo "🔍 Recherche d'Artifact Registry dans le projet $PROJECT_ID..." - ARTIFACT_REGISTRY_URL=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) - - if [ -n "$ARTIFACT_REGISTRY_URL" ]; then - echo "ARTIFACT_REGISTRY_URL=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV - echo "REGISTRY=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV - echo "✅ Artifact Registry trouvé: $ARTIFACT_REGISTRY_URL" - else - echo "❌ Aucun Artifact Registry trouvé !" - echo "Vérifiez que Terraform a été déployé avec succès." - exit 1 - fi - - - name: Configure Docker for Artifact Registry - run: | - echo "🔧 Configuration de Docker pour Artifact Registry..." - gcloud auth configure-docker $REGISTRY --quiet - echo "✅ Docker configuré pour $REGISTRY" - - - name: Build and push to Artifact Registry - run: | - cd app - echo "🐳 Construction de l'image Docker..." - echo "Registry: $REGISTRY" - echo "Image: $IMAGE_NAME" - echo "Tag: $GITHUB_SHA" - - # Construction de l'image avec le bon registry - docker build -t $REGISTRY/$IMAGE_NAME:$GITHUB_SHA . - docker tag $REGISTRY/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$IMAGE_NAME:dev-latest - - echo "📤 Push vers Artifact Registry..." - docker push $REGISTRY/$IMAGE_NAME:$GITHUB_SHA - docker push $REGISTRY/$IMAGE_NAME:dev-latest - echo "✅ Images poussées avec succès vers $REGISTRY" - - - name: Build and push to GitHub Container Registry - run: | - cd app - docker build -t ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA . - docker tag ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA ghcr.io/${{ github.repository }}/$IMAGE_NAME:dev-latest - echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin - docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA - docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:dev-latest - - #deploy-dev: - # needs: build-and-push - # runs-on: ubuntu-latest - # if: github.ref != 'refs/heads/main' - # environment: - # name: development - # url: https://tasks-app-dev.example.com - # - # steps: - # - name: Checkout - # uses: actions/checkout@v4 -# - # - name: Auth to Google Cloud (WIF) - # uses: google-github-actions/auth@v2 - # with: - # workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - # service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} -# - # - name: Set up Cloud SDK - # uses: google-github-actions/setup-gcloud@v1 -# - # - name: Configure kubectl - # run: | - # gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $PROJECT_ID -# - # - name: Install Helm - # uses: azure/setup-helm@v3 - # with: - # version: '3.12.0' -# - # - name: Get database password from Secret Manager - # run: | - # DB_PASSWORD=$(gcloud secrets versions access latest --secret="${INSTANCE_NAME}-app-db-password" --project=$PROJECT_ID) - # echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV -# - # - name: Deploy to GKE with Helm - # run: | - # helm upgrade --install tasks-app-dev ./helm/tasks-app \ - # --namespace tasks-dev \ - # --create-namespace \ - # --values ./helm/tasks-app/values-dev.yaml \ - # --set image.repository=$REGISTRY/$PROJECT_ID/$IMAGE_NAME \ - # --set image.tag=dev-latest \ - # --set secrets.dbPassword=$DB_PASSWORD \ - # --wait --timeout=5m -# - # - name: Verify deployment - # run: | - # kubectl get pods -n tasks-dev - # kubectl get services -n tasks-dev - # kubectl get ingress -n tasks-dev diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml deleted file mode 100644 index a635638..0000000 --- a/.github/workflows/terraform.yml +++ /dev/null @@ -1,211 +0,0 @@ -name: Terraform (Develop) - -on: - pull_request: - branches-ignore: [main] - paths-ignore: - - 'bootstrap-wif/**' - push: - branches-ignore: [main] - paths-ignore: - - 'bootstrap-wif/**' - -concurrency: - group: terraform-${{ github.ref }} - cancel-in-progress: false - -env: - TF_IN_AUTOMATION: true - TF_INPUT: false - -jobs: - fmt_validate: - name: Format & Validate - runs-on: ubuntu-latest - environment: - name: Develop - permissions: - contents: read - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.8.5 - terraform_wrapper: true - - - name: Cache Terraform plugins - uses: actions/cache@v4 - with: - path: | - ~/.terraform.d/plugin-cache - ./.terraform - key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} - restore-keys: | - ${{ runner.os }}-terraform- - - - name: Terraform Init (backend dev) - run: | - cd terraform - terraform init \ - -backend-config=../configs/dev.config - - - name: Terraform Format Check - run: | - cd terraform - terraform fmt -check -recursive - - - name: Terraform Validate - run: | - cd terraform - terraform validate - - plan: - name: Plan - runs-on: ubuntu-latest - needs: [fmt_validate] - if: github.event_name == 'pull_request' || github.event_name == 'push' - environment: - name: Develop - permissions: - contents: read - pull-requests: write - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.8.5 - terraform_wrapper: true - - - name: Cache Terraform plugins - uses: actions/cache@v4 - with: - path: | - ~/.terraform.d/plugin-cache - ./.terraform - key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} - restore-keys: | - ${{ runner.os }}-terraform- - - - name: Terraform Init (backend dev) - run: | - cd terraform - terraform init \ - -backend-config=../configs/dev.config - - - name: Terraform Plan (env dev) - run: | - cd terraform - terraform plan \ - -var-file=../environments/dev/terraform.tfvars \ - -input=false - - apply: - name: Apply - runs-on: ubuntu-latest - needs: [plan] - if: github.event_name == 'push' - environment: - name: Develop - permissions: - contents: read - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.8.5 - terraform_wrapper: true - - - name: Cache Terraform plugins - uses: actions/cache@v4 - with: - path: | - ~/.terraform.d/plugin-cache - ./.terraform - key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} - restore-keys: | - ${{ runner.os }}-terraform- - - - name: Terraform Init (backend dev) - run: | - cd terraform - terraform init \ - -backend-config=../configs/dev.config - - - name: Terraform Apply (env dev) - run: | - cd terraform - terraform apply \ - -auto-approve \ - -var-file=../environments/dev/terraform.tfvars \ - -input=false - - #deploy_app: - # name: Deploy Application - # runs-on: ubuntu-latest - # needs: [apply] - # if: github.event_name == 'push' - # environment: - # name: Develop - # permissions: - # contents: read - # id-token: write - # steps: - # - name: Checkout - # uses: actions/checkout@v4 -# - # - name: Auth to Google Cloud (WIF) - # uses: google-github-actions/auth@v2 - # with: - # workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - # service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} -# - # - name: Setup Google Cloud SDK - # uses: google-github-actions/setup-gcloud@v2 -# - # - name: Configure Docker for GCR - # run: gcloud auth configure-docker -# - # - name: Build and Push Docker Image - # run: | - # cd kubernetes/simple-app - # docker build -t gcr.io/${{ secrets.GCP_PROJECT_ID }}/simple-app:latest . - # docker push gcr.io/${{ secrets.GCP_PROJECT_ID }}/simple-app:latest -# - # - name: Deploy to Kubernetes - # run: | - # gcloud container clusters get-credentials gke-cluster-dev --region europe-west9 --project ${{ secrets.GCP_PROJECT_ID }} - # cd kubernetes/simple-app - # sed "s/PROJECT_ID/${{ secrets.GCP_PROJECT_ID }}/g" k8s-deployment.yaml | kubectl apply -f - - # kubectl rollout status deployment/simple-app - - From 4fc18f8d6c2f9be773c50590341512aa009418ef Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 15:59:52 +0200 Subject: [PATCH 06/12] fix(CI/CD): import the right artifact registry url for the pipeline --- .github/workflows/deploy-dev-unified.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-dev-unified.yml b/.github/workflows/deploy-dev-unified.yml index a6262da..10df519 100644 --- a/.github/workflows/deploy-dev-unified.yml +++ b/.github/workflows/deploy-dev-unified.yml @@ -214,9 +214,17 @@ jobs: - name: Get Artifact Registry URL run: | echo "🔍 Recherche d'Artifact Registry dans le projet $PROJECT_ID..." - ARTIFACT_REGISTRY_URL=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) - if [ -n "$ARTIFACT_REGISTRY_URL" ]; then + # Récupérer les informations du repository + REPO_INFO=$(gcloud artifacts repositories list --format="value(name,location)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + + if [ -n "$REPO_INFO" ]; then + REPO_NAME=$(echo $REPO_INFO | cut -d' ' -f1) + REPO_LOCATION=$(echo $REPO_INFO | cut -d' ' -f2) + + # Construire l'URL complète du repository + ARTIFACT_REGISTRY_URL="${REPO_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}" + echo "ARTIFACT_REGISTRY_URL=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV echo "REGISTRY=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV echo "✅ Artifact Registry trouvé: $ARTIFACT_REGISTRY_URL" From 8083358fafb6d652df06297c3f9b8a25b7d85ef2 Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 16:24:03 +0200 Subject: [PATCH 07/12] fix(CI/CD): add location on the artifact registry url --- .github/workflows/deploy-dev-unified.yml | 37 ++++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy-dev-unified.yml b/.github/workflows/deploy-dev-unified.yml index 10df519..bbc19d1 100644 --- a/.github/workflows/deploy-dev-unified.yml +++ b/.github/workflows/deploy-dev-unified.yml @@ -215,19 +215,26 @@ jobs: run: | echo "🔍 Recherche d'Artifact Registry dans le projet $PROJECT_ID..." - # Récupérer les informations du repository - REPO_INFO=$(gcloud artifacts repositories list --format="value(name,location)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + # Lister tous les repositories pour debug + echo "📋 Repositories disponibles:" + gcloud artifacts repositories list --project=$PROJECT_ID - if [ -n "$REPO_INFO" ]; then - REPO_NAME=$(echo $REPO_INFO | cut -d' ' -f1) - REPO_LOCATION=$(echo $REPO_INFO | cut -d' ' -f2) + # Récupérer le nom du repository + REPO_NAME=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + + if [ -n "$REPO_NAME" ]; then + # Récupérer la région du repository + REPO_LOCATION=$(gcloud artifacts repositories describe $REPO_NAME --project=$PROJECT_ID --format="value(location)") - # Construire l'URL complète du repository - ARTIFACT_REGISTRY_URL="${REPO_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}" + # Construire l'URL complète pour Docker + DOCKER_REGISTRY_URL="${REPO_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}" - echo "ARTIFACT_REGISTRY_URL=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV - echo "REGISTRY=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV - echo "✅ Artifact Registry trouvé: $ARTIFACT_REGISTRY_URL" + echo "ARTIFACT_REGISTRY_URL=$DOCKER_REGISTRY_URL" >> $GITHUB_ENV + echo "REGISTRY=$DOCKER_REGISTRY_URL" >> $GITHUB_ENV + echo "✅ Artifact Registry trouvé: $DOCKER_REGISTRY_URL" + echo "Repository: $REPO_NAME" + echo "Location: $REPO_LOCATION" + echo "Docker Registry: $DOCKER_REGISTRY_URL" else echo "❌ Aucun Artifact Registry trouvé !" echo "Vérifiez que Terraform a été déployé avec succès." @@ -237,8 +244,14 @@ jobs: - name: Configure Docker for Artifact Registry run: | echo "🔧 Configuration de Docker pour Artifact Registry..." - gcloud auth configure-docker $REGISTRY --quiet - echo "✅ Docker configuré pour $REGISTRY" + + # Extraire le domaine du registry (ex: europe-west1-docker.pkg.dev) + REGISTRY_DOMAIN=$(echo $REGISTRY | cut -d'/' -f1) + echo "Registry Domain: $REGISTRY_DOMAIN" + + # Configurer gcloud comme assistant d'identification pour le domaine + gcloud auth configure-docker $REGISTRY_DOMAIN --quiet + echo "✅ Docker configuré pour le domaine $REGISTRY_DOMAIN" - name: Build and push to Artifact Registry run: | From 5b4213c2ab33d43aeb90e9b7a7e07f10c5e7f6bb Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 16:29:16 +0200 Subject: [PATCH 08/12] fix(CI/CD): fix command location error to get the location --- .github/workflows/deploy-dev-unified.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-dev-unified.yml b/.github/workflows/deploy-dev-unified.yml index bbc19d1..0cf9ac5 100644 --- a/.github/workflows/deploy-dev-unified.yml +++ b/.github/workflows/deploy-dev-unified.yml @@ -219,12 +219,13 @@ jobs: echo "📋 Repositories disponibles:" gcloud artifacts repositories list --project=$PROJECT_ID - # Récupérer le nom du repository - REPO_NAME=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + # Récupérer les informations du repository (nom et région) + REPO_INFO=$(gcloud artifacts repositories list --format="value(name,location)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) - if [ -n "$REPO_NAME" ]; then - # Récupérer la région du repository - REPO_LOCATION=$(gcloud artifacts repositories describe $REPO_NAME --project=$PROJECT_ID --format="value(location)") + if [ -n "$REPO_INFO" ]; then + # Extraire le nom et la région + REPO_NAME=$(echo $REPO_INFO | cut -d' ' -f1) + REPO_LOCATION=$(echo $REPO_INFO | cut -d' ' -f2) # Construire l'URL complète pour Docker DOCKER_REGISTRY_URL="${REPO_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}" From 882c2e4ab85013cb27bed50404f808a78783d032 Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 16:51:49 +0200 Subject: [PATCH 09/12] fix(CI/CD): add env variable --- .github/workflows/deploy-dev-unified.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-dev-unified.yml b/.github/workflows/deploy-dev-unified.yml index 0cf9ac5..cdcc079 100644 --- a/.github/workflows/deploy-dev-unified.yml +++ b/.github/workflows/deploy-dev-unified.yml @@ -18,6 +18,7 @@ env: PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} GKE_ZONE: ${{ secrets.GKE_ZONE }} + GCP_REGION: ${{ secrets.GCP_REGION }} IMAGE_NAME: tasks-app INSTANCE_NAME: tasks-mysql @@ -219,17 +220,21 @@ jobs: echo "📋 Repositories disponibles:" gcloud artifacts repositories list --project=$PROJECT_ID - # Récupérer les informations du repository (nom et région) - REPO_INFO=$(gcloud artifacts repositories list --format="value(name,location)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + # Récupérer le nom du repository + REPO_NAME=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) - if [ -n "$REPO_INFO" ]; then - # Extraire le nom et la région - REPO_NAME=$(echo $REPO_INFO | cut -d' ' -f1) - REPO_LOCATION=$(echo $REPO_INFO | cut -d' ' -f2) + if [ -n "$REPO_NAME" ]; then + # Utiliser la région depuis les secrets + REPO_LOCATION=$GCP_REGION - # Construire l'URL complète pour Docker + # Construire l'URL complète pour Docker (format correct) DOCKER_REGISTRY_URL="${REPO_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}" + echo "Debug - REPO_NAME: $REPO_NAME" + echo "Debug - REPO_LOCATION: $REPO_LOCATION (depuis secret)" + echo "Debug - PROJECT_ID: $PROJECT_ID" + echo "Debug - URL construite: $DOCKER_REGISTRY_URL" + echo "ARTIFACT_REGISTRY_URL=$DOCKER_REGISTRY_URL" >> $GITHUB_ENV echo "REGISTRY=$DOCKER_REGISTRY_URL" >> $GITHUB_ENV echo "✅ Artifact Registry trouvé: $DOCKER_REGISTRY_URL" From 3f6fd0307e6aae7ea44a7de29375bbcf79917567 Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 16:58:43 +0200 Subject: [PATCH 10/12] fix(CI/CD): change github repo in lowercase --- .github/workflows/deploy-dev-unified.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-dev-unified.yml b/.github/workflows/deploy-dev-unified.yml index cdcc079..96f9f8b 100644 --- a/.github/workflows/deploy-dev-unified.yml +++ b/.github/workflows/deploy-dev-unified.yml @@ -279,11 +279,15 @@ jobs: - name: Build and push to GitHub Container Registry run: | cd app - docker build -t ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA . - docker tag ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA ghcr.io/${{ github.repository }}/$IMAGE_NAME:dev-latest + # Convertir le nom du repository en minuscules pour Docker + REPO_NAME_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + echo "Repository name (lowercase): $REPO_NAME_LOWER" + + docker build -t ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA . + docker tag ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:dev-latest echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin - docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA - docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:dev-latest + docker push ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA + docker push ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:dev-latest # ===== PHASE 4: KUBERNETES DEPLOYMENT ===== #deploy-dev: From 2888514eb2ba50bcf5c4fcd1aeea71091b1954d8 Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 17:29:41 +0200 Subject: [PATCH 11/12] feat(CI/CD): add CI/CD for the prod env --- .github/workflows/deploy-prod-unified.yml | 355 ++++++++++++++++++++++ .github/workflows/deploy-prod.yml | 141 --------- .github/workflows/terraform-prod.yml | 167 ---------- 3 files changed, 355 insertions(+), 308 deletions(-) create mode 100644 .github/workflows/deploy-prod-unified.yml delete mode 100644 .github/workflows/deploy-prod.yml delete mode 100644 .github/workflows/terraform-prod.yml diff --git a/.github/workflows/deploy-prod-unified.yml b/.github/workflows/deploy-prod-unified.yml new file mode 100644 index 0000000..2c27410 --- /dev/null +++ b/.github/workflows/deploy-prod-unified.yml @@ -0,0 +1,355 @@ +name: Deploy to Production (Unified) + +on: + push: + branches: [ main ] + tags: + - 'v*' + workflow_dispatch: + inputs: + force_deploy: + description: 'Force deployment (skip tests)' + required: false + default: 'false' + type: boolean + +permissions: + contents: read + id-token: write + packages: write + pull-requests: write + +env: + TF_IN_AUTOMATION: true + TF_INPUT: false + PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} + GKE_ZONE: ${{ secrets.GKE_ZONE }} + GCP_REGION: ${{ secrets.GCP_REGION }} + IMAGE_NAME: tasks-app + INSTANCE_NAME: tasks-mysql + +jobs: + # ===== PHASE 1: TERRAFORM ===== + terraform-fmt-validate: + name: Terraform Format & Validate + runs-on: ubuntu-latest + environment: + name: Production + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.8.5 + terraform_wrapper: true + + - name: Cache Terraform plugins + uses: actions/cache@v4 + with: + path: | + ~/.terraform.d/plugin-cache + ./.terraform + key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} + restore-keys: | + ${{ runner.os }}-terraform- + + - name: Terraform Init (backend prod) + run: | + cd terraform + terraform init \ + -backend-config=../configs/prod.config + + - name: Terraform Format Check + run: | + cd terraform + terraform fmt -check -recursive + + - name: Terraform Validate + run: | + cd terraform + terraform validate + + terraform-plan: + name: Terraform Plan + runs-on: ubuntu-latest + needs: [terraform-fmt-validate] + if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' + environment: + name: Production + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.8.5 + terraform_wrapper: true + + - name: Cache Terraform plugins + uses: actions/cache@v4 + with: + path: | + ~/.terraform.d/plugin-cache + ./.terraform + key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} + restore-keys: | + ${{ runner.os }}-terraform- + + - name: Terraform Init (backend prod) + run: | + cd terraform + terraform init \ + -backend-config=../configs/prod.config + + - name: Terraform Plan (env prod) + run: | + cd terraform + terraform plan \ + -var-file=../environments/prd/terraform.tfvars \ + -input=false + + terraform-apply: + name: Terraform Apply + runs-on: ubuntu-latest + needs: [terraform-plan] + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + environment: + name: Production + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.8.5 + terraform_wrapper: true + + - name: Cache Terraform plugins + uses: actions/cache@v4 + with: + path: | + ~/.terraform.d/plugin-cache + ./.terraform + key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} + restore-keys: | + ${{ runner.os }}-terraform- + + - name: Terraform Init (backend prod) + run: | + cd terraform + terraform init \ + -backend-config=../configs/prod.config + + - name: Terraform Apply (env prod) + run: | + cd terraform + terraform apply \ + -auto-approve \ + -var-file=../environments/prd/terraform.tfvars \ + -input=false + + # ===== PHASE 2: TESTS ===== + test: + name: Run Tests + runs-on: ubuntu-latest + needs: [terraform-apply] + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !inputs.force_deploy) + environment: + name: Production + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + cd app + pip install -r requirements.txt + + - name: Run tests + run: | + cd app + python -m pytest tests/ || echo "No tests found, continuing..." + + # ===== PHASE 3: BUILD & DEPLOY ===== + build-and-push: + name: Build & Push Docker Images + runs-on: ubuntu-latest + needs: [test] + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + environment: + name: Production + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auth to Google Cloud (WIF) + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 + + - name: Get Artifact Registry URL + run: | + echo "🔍 Recherche d'Artifact Registry dans le projet $PROJECT_ID..." + + # Lister tous les repositories pour debug + echo "📋 Repositories disponibles:" + gcloud artifacts repositories list --project=$PROJECT_ID + + # Récupérer le nom du repository + REPO_NAME=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + + if [ -n "$REPO_NAME" ]; then + # Utiliser la région depuis les secrets + REPO_LOCATION=$GCP_REGION + + # Construire l'URL complète pour Docker (format correct) + DOCKER_REGISTRY_URL="${REPO_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}" + + echo "Debug - REPO_NAME: $REPO_NAME" + echo "Debug - REPO_LOCATION: $REPO_LOCATION (depuis secret)" + echo "Debug - PROJECT_ID: $PROJECT_ID" + echo "Debug - URL construite: $DOCKER_REGISTRY_URL" + + echo "ARTIFACT_REGISTRY_URL=$DOCKER_REGISTRY_URL" >> $GITHUB_ENV + echo "REGISTRY=$DOCKER_REGISTRY_URL" >> $GITHUB_ENV + echo "✅ Artifact Registry trouvé: $DOCKER_REGISTRY_URL" + echo "Repository: $REPO_NAME" + echo "Location: $REPO_LOCATION" + echo "Docker Registry: $DOCKER_REGISTRY_URL" + else + echo "❌ Aucun Artifact Registry trouvé !" + echo "Vérifiez que Terraform a été déployé avec succès." + exit 1 + fi + + - name: Configure Docker for Artifact Registry + run: | + echo "🔧 Configuration de Docker pour Artifact Registry..." + + # Extraire le domaine du registry (ex: europe-west1-docker.pkg.dev) + REGISTRY_DOMAIN=$(echo $REGISTRY | cut -d'/' -f1) + echo "Registry Domain: $REGISTRY_DOMAIN" + + # Configurer gcloud comme assistant d'identification pour le domaine + gcloud auth configure-docker $REGISTRY_DOMAIN --quiet + echo "✅ Docker configuré pour le domaine $REGISTRY_DOMAIN" + + - name: Build and push to Artifact Registry + run: | + cd app + echo "🐳 Construction de l'image Docker..." + echo "Registry: $REGISTRY" + echo "Image: $IMAGE_NAME" + echo "Tag: $GITHUB_SHA" + + # Construction de l'image avec le bon registry + docker build -t $REGISTRY/$IMAGE_NAME:$GITHUB_SHA . + docker tag $REGISTRY/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$IMAGE_NAME:prod-latest + + echo "📤 Push vers Artifact Registry..." + docker push $REGISTRY/$IMAGE_NAME:$GITHUB_SHA + docker push $REGISTRY/$IMAGE_NAME:prod-latest + echo "✅ Images poussées avec succès vers $REGISTRY" + + - name: Build and push to GitHub Container Registry + run: | + cd app + # Convertir le nom du repository en minuscules pour Docker + REPO_NAME_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + echo "Repository name (lowercase): $REPO_NAME_LOWER" + + docker build -t ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA . + docker tag ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:prod-latest + echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin + docker push ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA + docker push ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:prod-latest + + # ===== PHASE 4: KUBERNETES DEPLOYMENT ===== + #deploy-prod: + # name: Deploy to GKE + # runs-on: ubuntu-latest + # needs: [build-and-push] + # if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + # environment: + # name: production + # url: https://tasks-app-prod.example.com + # steps: + # - name: Checkout + # uses: actions/checkout@v4 +# + # - name: Auth to Google Cloud (WIF) + # uses: google-github-actions/auth@v2 + # with: + # workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + # service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} +# + # - name: Set up Cloud SDK + # uses: google-github-actions/setup-gcloud@v1 +# + # - name: Get Artifact Registry URL + # run: | + # ARTIFACT_REGISTRY_URL=$(gcloud artifacts repositories list --format="value(name)" --filter="format=DOCKER" --project=$PROJECT_ID | head -1) + # echo "REGISTRY=$ARTIFACT_REGISTRY_URL" >> $GITHUB_ENV +# + # - name: Configure kubectl + # run: | + # gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $PROJECT_ID +# + # - name: Install Helm + # uses: azure/setup-helm@v3 + # with: + # version: '3.12.0' +# + # - name: Get database password from Secret Manager + # run: | + # DB_PASSWORD=$(gcloud secrets versions access latest --secret="${INSTANCE_NAME}-app-db-password" --project=$PROJECT_ID) + # echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV +# + # - name: Deploy to GKE with Helm + # run: | + # helm upgrade --install tasks-app-prod ./helm/tasks-app \ + # --namespace tasks-prod \ + # --create-namespace \ + # --values ./helm/tasks-app/values-prod.yaml \ + # --set image.repository=$REGISTRY/$PROJECT_ID/$IMAGE_NAME \ + # --set image.tag=prod-latest \ + # --set secrets.dbPassword=$DB_PASSWORD \ + # --wait --timeout=5m +# + # - name: Verify deployment + # run: | + # kubectl get pods -n tasks-prod + # kubectl get services -n tasks-prod + # kubectl get ingress -n tasks-prod diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml deleted file mode 100644 index 07e38b8..0000000 --- a/.github/workflows/deploy-prod.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: Deploy to Production - -on: - push: - branches: [ main ] - tags: - - 'v*' - workflow_dispatch: - inputs: - force_deploy: - description: 'Force deployment (skip tests)' - required: false - default: 'false' - type: boolean - -permissions: - contents: read - id-token: write - packages: write - -env: - PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} - GKE_CLUSTER: ${{ secrets.GKE_CLUSTER_NAME }} - GKE_ZONE: ${{ secrets.GKE_ZONE }} - REGISTRY: gcr.io - IMAGE_NAME: tasks-app - INSTANCE_NAME: tasks-mysql - -jobs: - test: - runs-on: ubuntu-latest - if: ${{ !inputs.force_deploy }} - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install dependencies - run: | - cd app - pip install -r requirements.txt - - - name: Run tests - run: | - cd app - python -m pytest tests/ || echo "No tests found, continuing..." - - build-and-push: - needs: test - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') - environment: - name: Production - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Set up Cloud SDK - uses: google-github-actions/setup-gcloud@v1 - - - name: Configure Docker to use gcloud as a credential helper - run: gcloud auth configure-docker - - - name: Build and push to GCR - run: | - cd app - docker build -t $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA . - docker tag $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA $REGISTRY/$PROJECT_ID/$IMAGE_NAME:prod-latest - docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:$GITHUB_SHA - docker push $REGISTRY/$PROJECT_ID/$IMAGE_NAME:prod-latest - - - name: Build and push to GitHub Container Registry - run: | - cd app - docker build -t ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA . - docker tag ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA ghcr.io/${{ github.repository }}/$IMAGE_NAME:prod-latest - echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin - docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:$GITHUB_SHA - docker push ghcr.io/${{ github.repository }}/$IMAGE_NAME:prod-latest - - deploy-prod: - needs: build-and-push - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') - environment: - name: Production - url: https://tasks-app-prod.example.com - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Set up Cloud SDK - uses: google-github-actions/setup-gcloud@v1 - - - name: Configure kubectl - run: | - gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $PROJECT_ID - - - name: Install Helm - uses: azure/setup-helm@v3 - with: - version: '3.12.0' - - - name: Get database password from Secret Manager - run: | - DB_PASSWORD=$(gcloud secrets versions access latest --secret="${INSTANCE_NAME}-app-db-password" --project=$PROJECT_ID) - echo "DB_PASSWORD=$DB_PASSWORD" >> $GITHUB_ENV - - - name: Deploy to GKE with Helm - run: | - helm upgrade --install tasks-app-prod ./helm/tasks-app \ - --namespace tasks-prod \ - --create-namespace \ - --values ./helm/tasks-app/values-prod.yaml \ - --set image.repository=$REGISTRY/$PROJECT_ID/$IMAGE_NAME \ - --set image.tag=prod-latest \ - --set secrets.dbPassword=$DB_PASSWORD \ - --wait --timeout=5m - - - name: Verify deployment - run: | - kubectl get pods -n tasks-prod - kubectl get services -n tasks-prod - kubectl get ingress -n tasks-prod \ No newline at end of file diff --git a/.github/workflows/terraform-prod.yml b/.github/workflows/terraform-prod.yml deleted file mode 100644 index 95aff32..0000000 --- a/.github/workflows/terraform-prod.yml +++ /dev/null @@ -1,167 +0,0 @@ -name: Terraform (Production) - -on: - push: - branches: [main] - paths-ignore: - - 'bootstrap-wif/**' - -concurrency: - group: terraform-production-${{ github.ref }} - cancel-in-progress: false - -env: - TF_IN_AUTOMATION: true - TF_INPUT: false - -jobs: - fmt_validate: - name: Format & Validate - runs-on: ubuntu-latest - environment: - name: Production - permissions: - contents: read - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.8.5 - terraform_wrapper: true - - - name: Cache Terraform plugins - uses: actions/cache@v4 - with: - path: | - ~/.terraform.d/plugin-cache - ./.terraform - key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} - restore-keys: | - ${{ runner.os }}-terraform- - - - name: Terraform Init (backend prd) - run: | - cd terraform - terraform init \ - -backend-config=../configs/prd.config - - - name: Terraform Format Check - run: | - cd terraform - terraform fmt -check -recursive - - - name: Terraform Validate - run: | - cd terraform - terraform validate - - - plan: - name: Plan - runs-on: ubuntu-latest - needs: [fmt_validate] - environment: - name: Production - permissions: - contents: read - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.8.5 - terraform_wrapper: true - - - name: Cache Terraform plugins - uses: actions/cache@v4 - with: - path: | - ~/.terraform.d/plugin-cache - ./.terraform - key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} - restore-keys: | - ${{ runner.os }}-terraform- - - - name: Terraform Init (backend prd) - run: | - cd terraform - terraform init \ - -backend-config=../configs/prd.config - - - name: Terraform Plan (env prd) - id: plan - run: | - cd terraform - terraform plan \ - -var-file=../environments/prd/terraform.tfvars \ - -input=false - - - apply: - name: Apply - runs-on: ubuntu-latest - needs: [fmt_validate, plan] - environment: - name: Production - permissions: - contents: read - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Auth to Google Cloud (WIF) - uses: google-github-actions/auth@v2 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.8.5 - terraform_wrapper: true - - - name: Cache Terraform plugins - uses: actions/cache@v4 - with: - path: | - ~/.terraform.d/plugin-cache - ./.terraform - key: ${{ runner.os }}-terraform-${{ hashFiles('**/.terraform.lock.hcl') }} - restore-keys: | - ${{ runner.os }}-terraform- - - - name: Terraform Init (backend prd) - run: | - cd terraform - terraform init \ - -backend-config=../configs/prd.config - - - name: Terraform Apply (env prd) - run: | - cd terraform - terraform apply \ - -auto-approve \ - -var-file=../environments/prd/terraform.tfvars \ - -input=false - From bdc3d21ed6c7c576c49179a4de42921ad1d8ab5c Mon Sep 17 00:00:00 2001 From: math974 Date: Fri, 24 Oct 2025 17:44:51 +0200 Subject: [PATCH 12/12] feat(CI/CD): remove github registry workflow --- .github/workflows/deploy-dev-unified.yml | 13 ------------- .github/workflows/deploy-prod-unified.yml | 12 ------------ 2 files changed, 25 deletions(-) diff --git a/.github/workflows/deploy-dev-unified.yml b/.github/workflows/deploy-dev-unified.yml index 96f9f8b..6a86c85 100644 --- a/.github/workflows/deploy-dev-unified.yml +++ b/.github/workflows/deploy-dev-unified.yml @@ -276,19 +276,6 @@ jobs: docker push $REGISTRY/$IMAGE_NAME:dev-latest echo "✅ Images poussées avec succès vers $REGISTRY" - - name: Build and push to GitHub Container Registry - run: | - cd app - # Convertir le nom du repository en minuscules pour Docker - REPO_NAME_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') - echo "Repository name (lowercase): $REPO_NAME_LOWER" - - docker build -t ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA . - docker tag ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:dev-latest - echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin - docker push ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA - docker push ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:dev-latest - # ===== PHASE 4: KUBERNETES DEPLOYMENT ===== #deploy-dev: # name: Deploy to GKE diff --git a/.github/workflows/deploy-prod-unified.yml b/.github/workflows/deploy-prod-unified.yml index 2c27410..060b250 100644 --- a/.github/workflows/deploy-prod-unified.yml +++ b/.github/workflows/deploy-prod-unified.yml @@ -283,18 +283,6 @@ jobs: docker push $REGISTRY/$IMAGE_NAME:prod-latest echo "✅ Images poussées avec succès vers $REGISTRY" - - name: Build and push to GitHub Container Registry - run: | - cd app - # Convertir le nom du repository en minuscules pour Docker - REPO_NAME_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') - echo "Repository name (lowercase): $REPO_NAME_LOWER" - - docker build -t ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA . - docker tag ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:prod-latest - echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin - docker push ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:$GITHUB_SHA - docker push ghcr.io/$REPO_NAME_LOWER/$IMAGE_NAME:prod-latest # ===== PHASE 4: KUBERNETES DEPLOYMENT ===== #deploy-prod: