From 6360fed5c628acf69395061ee3722d21e42cad12 Mon Sep 17 00:00:00 2001 From: florimondmanca Date: Tue, 27 Feb 2024 11:30:54 +0100 Subject: [PATCH] Update --- .github/workflows/eudonet_paris_import.yml | 16 ++-- Makefile | 2 +- docs/tools/db.md | 4 +- docs/tools/eudonet_paris.md | 53 +++++++++---- tools/download_addok_bundle.sh | 6 +- tools/scalingodbtunnel | 89 ++++++++++++++++------ 6 files changed, 118 insertions(+), 52 deletions(-) diff --git a/.github/workflows/eudonet_paris_import.yml b/.github/workflows/eudonet_paris_import.yml index cd3b3e914..0728a0d07 100644 --- a/.github/workflows/eudonet_paris_import.yml +++ b/.github/workflows/eudonet_paris_import.yml @@ -2,7 +2,7 @@ name: Eudonet Paris Import # on: # schedule: -# - cron: '0 17 * * 1' # Tous les lundis à 17h00 +# - cron: '30 16 * * 1' # Voir https://crontab.guru/ : tous les lundis à 16h30 on: push: @@ -46,7 +46,7 @@ jobs: id: addok-bundle-cache with: path: docker/addok/addok-data - key: ${{ runner.os }}-addok-bundle-${{ secrets.EUDONET_PARIS_KDRIVE_FILE_ID }} + key: ${{ runner.os }}-addok-bundle-${{ secrets.EUDONET_PARIS_IMPORT_KDRIVE_FILE_ID }} - name: Download and unzip Addok bundle if: steps.addok-bundle-cache.outputs.cache-hit != 'true' @@ -56,8 +56,8 @@ jobs: unzip -d tmp/addok-archive tmp/addok-archive.zip unzip -d docker/addok/addok-data tmp/addok-archive/addok-dialog-bundle.zip env: - EUDONET_PARIS_KDRIVE_TOKEN: ${{ secrets.EUDONET_PARIS_KDRIVE_TOKEN }} - EUDONET_PARIS_KDRIVE_FILE_ID: ${{ secrets.EUDONET_PARIS_KDRIVE_FILE_ID }} + KDRIVE_TOKEN: ${{ secrets.EUDONET_PARIS_IMPORT_KDRIVE_TOKEN }} + KDRIVE_FILE_ID: ${{ vars.EUDONET_PARIS_IMPORT_KDRIVE_FILE_ID }} - name: Start Addok run: | @@ -66,14 +66,16 @@ jobs: - name: Init environment variables run: | - echo "DATABASE_URL=${{ secrets.EUDONET_PARIS_IMPORT_DATABASE_URL_PR }}" >> .env.local + echo "DATABASE_URL=${{ secrets.EUDONET_PARIS_IMPORT_DATABASE_URL }}" >> .env.local # Deal with JSON quotes - printf "APP_EUDONET_PARIS_CREDENTIALS='%s'\n" '${{ secrets.APP_EUDONET_PARIS_CREDENTIALS }}' >> .env.local - echo "APP_EUDONET_PARIS_ORG_ID=${{ secrets.APP_EUDONET_PARIS_ORG_ID_PR }}" >> .env.local + printf "APP_EUDONET_PARIS_CREDENTIALS='%s'\n" '${{ secrets.EUDONET_PARIS_IMPORT_CREDENTIALS }}' >> .env.local + echo "APP_EUDONET_PARIS_ORG_ID=${{ vars.EUDONET_PARIS_IMPORT_ORG_ID }}" >> .env.local echo "API_ADRESSE_BASE_URL=http://localhost:7878" >> .env.local - name: Run import run: make eudonet_paris_import_ci BIN_PHP="php" BIN_CONSOLE="php bin/console" BIN_COMPOSER="composer" + env: + EUDONET_PARIS_IMPORT_APP: ${{ vars.EUDONET_PARIS_IMPORT_APP }} - name: Get log file path id: logfile diff --git a/Makefile b/Makefile index d3b190fd7..861de08aa 100644 --- a/Makefile +++ b/Makefile @@ -284,5 +284,5 @@ scalingo-postdeploy: eudonet_paris_import_ci: make composer CMD="install -n --prefer-dist" scalingo login --ssh --ssh-identity ~/.ssh/id_rsa - scalingo --app dialog-staging-pr634 db-tunnel -p 10000 DATABASE_URL & ./tools/wait-for-it.sh 127.0.0.1:10000 + ./tools/scalingodbtunnel {EUDONET_PARIS_IMPORT_APP} --host-url --port 10000 & ./tools/wait-for-it.sh 127.0.0.1:10000 make console CMD="app:eudonet_paris:import" diff --git a/docs/tools/db.md b/docs/tools/db.md index 8fcc3bf6a..54d425895 100644 --- a/docs/tools/db.md +++ b/docs/tools/db.md @@ -57,14 +57,14 @@ Vous pouvez utiliser en local une base de données hébergée sur Scalingo (stag Lancez la commande suivante : ``` -./tools/scalingodbtunnel APP +./tools/scalingodbtunnel --app APP ``` Remplacez `APP` par l'application Scalingo cible. Cette commande démarre un tunnel SSH vers la base de données, et le relaie à Docker pour que le conteneur PHP/Doctrine puisse y accéder. -La commande affiche ensuite une ligne `DATABASE_URL=...`. **Reportez cette ligne** dans `.env.local` pour utiliser la base de données distante. Accédez à http://localhost:8000 comme d'habitude. +La commande affiche ensuite la `DATABASE_URL`. **Reportez cette valeur** dans `.env.local` pour utiliser la base de données distante. Accédez à http://localhost:8000 comme d'habitude. Laissez la commande tourner pour garder le tunnel ouvert. diff --git a/docs/tools/eudonet_paris.md b/docs/tools/eudonet_paris.md index 568ec9df4..963858c2a 100644 --- a/docs/tools/eudonet_paris.md +++ b/docs/tools/eudonet_paris.md @@ -65,13 +65,29 @@ Notes : ## Déploiement périodique automatique -Les données Eudonet Paris sont automatiquement intégrées en production tous les lundis à 17h00. +Les données Eudonet Paris sont automatiquement intégrées en production tous les lundis à 16h30. -Cette automatisation est réalisée au moyen de GitHub Actions (voir [`eudonet_paris_import.yml`](../../workflows/eudonet_paris_import.yml)). +Cette automatisation est réalisée au moyen de la GitHub Action [`eudonet_paris_import.yml`](../../workflows/eudonet_paris_import.yml). -### Accès SSH de GitHub Actions à la base de données sur Scalingo +La configuration passe par diverses variables d'environnement résumées ci-dessous : -Cette GitHub Action a besoin d'un accès SSH à la base de données hébergée chez Scalingo. +| Variable d'environnement | Configuration | Description | +|---|---|---| +| `EUDONET_PARIS_IMPORT_APP` | [Variable](https://docs.github.com/fr/actions/learn-github-actions/variables) au sens GitHub Actions | L'application Scalingo cible (par exemple `dialog` pour la production) | +| `EUDONET_PARIS_IMPORT_CREDENTIALS` | [Secret](https://docs.github.com/fr/actions/security-guides/using-secrets-in-github-actions) au sens GitHub Actions | Les identifiants d'accès à l'API Eudonet Paris | +| `EUDONET_PARIS_IMPORT_DATABASE_URL` | Secret | L'URL d'accès à la base de données par la CI (voir ci-dessous) | +| `EUDONET_PARIS_IMPORT_KDRIVE_TOKEN` | Secret | Clé d'API pour Infomaniak kDrive (téléchargement des données Addok par la CI) | +| `EUDONET_PARIS_IMPORT_KDRIVE_FILE_ID`| Variable | Identifiant du fichier Addok sur kDrive | +| `EUDONET_PARIS_IMPORT_ORG_ID` | Variable | Le UUID de l'organisation "Ville de Paris" dans l'environnement défini apr `EUDONET_PARIS_IMPORT_APP` | +| `GH_SCALINGO_SSH_PRIVATE_KEY` | Secret | Clé SSH privée permettant l'accès à Scalingo par la CI | + +### Configuration de l'organisation cible + +L'organisation cible de l'import est configurée via la variable `EUDONET_PARIS_IMPORT_ORG_ID` sur GitHub Actions. + +### Accès SSH de GitHub Actions à Scalingo + +La GitHub Action d'import a besoin d'un accès SSH à Scalingo pour accéder à la base de données de façon sécurisée. Pour cela des clés SSH ont été générées comme suit : @@ -83,25 +99,32 @@ La clé publique `~/.ssh/id_dialog_gh_scalingo.pub` ainsi générée a été enr > 💡 Pour renouveler les clés, ou en cas de perte, de nouvelles clés peuvent être régénérées en utilisant la méthode ci-dessus, puis rattachées au compte de toute personne ayant un accès "Collaborator" sur l'app Scalingo `dialog`. -La clé privée a été ajoutée comme secret `$GH_SCALINGO_SSH_PRIVATE_KEY` au dépôt GitHub et est utilisée par la GitHub Action. +La clé privée a été ajoutée comme secret `GH_SCALINGO_SSH_PRIVATE_KEY` au dépôt GitHub et est utilisée par la GitHub Action. + +### Accès de GitHub Actions à la base de données sur Scalingo L'accès à la base de données lors de l'import se fait via un [tunnel chiffré Scalingo](https://doc.scalingo.com/platform/databases/access#encrypted-tunnel). -* L'URL de base de données résultant a été ajouté comme secret `$EUDONET_PARIS_IMPORT_DATABASE_URL`. -* La valeur de ce secret doit être la `DATABASE_URL` de production où l'on remplace le `host:port` par `127.0.0.1:10000` afin de pointer sur le DB tunnel Scalingo (le port `10000` est hardcodé dans la GitHub Action). +Le secret `EUDONET_PARIS_IMPORT_DATABASE_URL` doit contenir la `DATABASE_URL` de production où `host:port` est remplacé par `127.0.0.1:10000`. -### Données Addok +Si besoin de la reconfigurer, pour obtenir automatiquement cette URL, exécutez : -L'intégration Eudonet Paris a besoin de faire tourner l'[instance Addok personnalisée](./addok.md) en local. +```bash +./tools/scalingodbtunnel dialog --url-only --host-url +``` + +> Cette commande nécessite le CLI Scalingo, voir [Utiliser une DB Scalingo en local](./db.md#utiliser-une-db-scalingo-en-local). -Il faut donc que la GitHub Action télécharge le fichier ZIP contenant les données (1.6 Go environ) hébergé sur le kDrive de Fairness. +Sinon il vous faut récupérer la `DATABASE_URL` dans l'interface web Scalingo. + +### Données Addok -Cela est fait par le script `tools/download_addok_bundle.sh`. Pour cela une clé d'API Infomaniak a été créée par @florimondmanca et enregistrée dans le secret `EUDONET_PARIS_KDRIVE_TOKEN`. +L'intégration Eudonet Paris a besoin de faire tourner l'[instance Addok personnalisée](./addok.md) sur la CI, en parallèle de l'import. -L'identifiant du fichier sur kDrive est stocké dans le secret `EUDONET_PARIS_KDRIVE_FILE_ID`. +Il faut donc que la GitHub Action télécharge le fichier ZIP contenant les données (1.6 Go environ) hébergé sur le kDrive de Fairness. (Le fichier est mis en cache après le premier téléchargement.) -#### Mise à jour des données Addok +Cela est fait par le script `tools/download_addok_bundle.sh`. Pour cela une clé d'API Infomaniak avec le scope `drive` a été créée par @florimondmanca et enregistrée dans le secret `EUDONET_PARIS_IMPORT_KDRIVE_TOKEN`. -Si un nouveau bundle Addok est stocké sur le kDrive, récupérer le FileID (visible dans l'URL de partage du fichier) et mettre à jour le secret `EUDONET_PARIS_KDRIVE_FILE_ID`. +L'identifiant du fichier sur kDrive est stocké dans la variable `EUDONET_PARIS_IMPORT_KDRIVE_FILE_ID`. -Le ZIP est mis en cache après le premier téléchargement. +**Important** : si un nouveau bundle Addok est stocké sur le kDrive, ou si l'URL du fichier change pour toute autre raison, il faut mettre à jour la variable `EUDONET_PARIS_IMPORT_KDRIVE_FILE_ID` avec le nouveau FileID (visible dans l'URL de partage du fichier). diff --git a/tools/download_addok_bundle.sh b/tools/download_addok_bundle.sh index 931a81ee4..5797a47b0 100755 --- a/tools/download_addok_bundle.sh +++ b/tools/download_addok_bundle.sh @@ -6,14 +6,14 @@ DRIVE_ID=184671 ARCHIVE_ID=$( curl -L \ -X POST \ - -H "Authorization: Bearer ${EUDONET_PARIS_KDRIVE_TOKEN}" \ + -H "Authorization: Bearer ${KDRIVE_TOKEN}" \ -H "Content-Type: application/json" \ - -d "{\"file_ids\": [\"${EUDONET_PARIS_KDRIVE_FILE_ID}\"]}" \ + -d "{\"file_ids\": [\"${KDRIVE_FILE_ID}\"]}" \ "https://api.infomaniak.com/3/drive/${DRIVE_ID}/files/archives" \ | jq --raw-output .data.uuid ) curl -L \ - -H "Authorization: Bearer ${EUDONET_PARIS_KDRIVE_TOKEN}" \ + -H "Authorization: Bearer ${KDRIVE_TOKEN}" \ "https://api.infomaniak.com/2/drive/${DRIVE_ID}/files/archives/${ARCHIVE_ID}" \ > $1 diff --git a/tools/scalingodbtunnel b/tools/scalingodbtunnel index 33a34f4c3..0aaa7d1f1 100755 --- a/tools/scalingodbtunnel +++ b/tools/scalingodbtunnel @@ -3,7 +3,8 @@ import argparse import shlex import subprocess import sys -from contextlib import contextmanager +from contextlib import contextmanager, nullcontext +import threading from urllib.parse import urlparse, urlunparse @@ -38,7 +39,12 @@ def _popen_terminate_on_exit(*args, **kwargs): proc.terminate() -def main(app, port): +def main( + app: str, + port: int, + host_url: bool = False, + url_only: bool = False, +): # Ensure Scalingo CLI is authenticated result = subprocess.run(["scalingo", "whoami"], capture_output=True) if result.returncode: @@ -51,23 +57,30 @@ def main(app, port): return 1 print(result.stdout.decode()) - # 1) Forward Scalingo -> host. - - # https://doc.scalingo.com/platform/databases/access#encrypted-tunnel - db_tunnel_command = [ - "scalingo", - "--app", - app, - "db-tunnel", - "-p", - str(port), - "DATABASE_URL", - ] - - with _popen_terminate_on_exit( - db_tunnel_command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL - ) as db_tunnel_proc: - # 2) Forward host -> Docker. + if url_only: + # Yield an object with .wait() method that will return immediately. + ready = threading.Event() + ready.set() + db_tunnel_ctx = nullcontext(ready) + else: + # Forward Scalingo DB to host. + # https://doc.scalingo.com/platform/databases/access#encrypted-tunnel + db_tunnel_command = [ + "scalingo", + "--app", + app, + "db-tunnel", + "-p", + str(port), + "DATABASE_URL", + ] + + db_tunnel_ctx = _popen_terminate_on_exit( + db_tunnel_command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) + + with db_tunnel_ctx as db_tunnel_proc: + # Forward host port to Docker. # This allows PHP/Doctrine container to access the Scalingo database. username = _get_output(["whoami"]) @@ -100,11 +113,14 @@ def main(app, port): _get_output(["scalingo", "--app", app, "env-get", "DATABASE_URL"]) ) + # DB tunnel is always available on localhost, so we can show either. + displayed_origin = f"127.0.0.1:{port}" if host_url else docker_tunnel_origin + db_tunnel_database_url = database_url._replace( - netloc=f"{database_url.username}:{database_url.password}@{docker_tunnel_origin}" + netloc=f"{database_url.username}:{database_url.password}@{displayed_origin}" ) - print(f"DATABASE_URL={urlunparse(db_tunnel_database_url)}") + print(urlunparse(db_tunnel_database_url)) try: db_tunnel_proc.wait() @@ -116,13 +132,38 @@ def main(app, port): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("app", help="Name of the Scalingo application") + parser.add_argument( + "app", + nargs="?", + help="Name of the Scalingo application (default: dialog-staging)", + ) parser.add_argument( "--port", type=int, default=10000, help="Port of the SSH tunnel (default: 10000)", ) - args = parser.parse_args() + parser.add_argument( + "--host-url", + action="store_true", + help="Show URL of the tunnel on the host, not Docker", + ) + parser.add_argument( + "--url-only", + action="store_true", + help="Show local URL only, do not establish tunnel", + ) - sys.exit(main(args.app, args.port)) + args = parser.parse_args() + + if args.app is None: + args.app = "dialog-staging" + + sys.exit( + main( + args.app, + args.port, + host_url=args.host_url, + url_only=args.url_only, + ) + )