Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
florimondmanca committed Feb 27, 2024
1 parent 4be2738 commit 6360fed
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 52 deletions.
16 changes: 9 additions & 7 deletions .github/workflows/eudonet_paris_import.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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'
Expand All @@ -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: |
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 2 additions & 2 deletions docs/tools/db.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
53 changes: 38 additions & 15 deletions docs/tools/eudonet_paris.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 :

Expand All @@ -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).
6 changes: 3 additions & 3 deletions tools/download_addok_bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
89 changes: 65 additions & 24 deletions tools/scalingodbtunnel
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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:
Expand All @@ -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"])
Expand Down Expand Up @@ -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()
Expand All @@ -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,
)
)

0 comments on commit 6360fed

Please sign in to comment.