Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6301ffedd7 | ||
|
|
da5d2d838a | ||
|
|
8386e05f31 | ||
|
|
2a7e1c25fc | ||
|
|
5d955ca991 | ||
|
|
cefb07067d | ||
|
|
3cc6c67e0d | ||
|
|
93afc6031c | ||
|
|
99db807e3c | ||
|
|
47c4fd445b | ||
|
|
667592033b | ||
|
|
6785c70f6f | ||
|
|
692ec436a5 | ||
|
|
a3d32a6fb1 |
@@ -3,8 +3,6 @@
|
||||
# espace de travail : `.env`, certificats et volumes y survivent d'un déploiement à l'autre.
|
||||
# Piège : jamais de déclencheur `pull_request` ici. Sur un dépôt public, une PR de fork
|
||||
# exécuterait son code sur la machine de production (ADR 0009) - job deploy.
|
||||
# Pourquoi : `main` va en prod, `dev` en recette, et toute autre branche lancée à la main
|
||||
# (workflow_dispatch) va dans `dev`, la vitrine d'une branche de travail (ADR 0017).
|
||||
|
||||
name: Déploiement
|
||||
|
||||
@@ -16,10 +14,8 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Par environnement et non par branche : deux branches lancées dans `dev` écriraient sinon
|
||||
# dans le même dossier en même temps.
|
||||
concurrency:
|
||||
group: deploy-${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'dev' && 'rec' || 'dev' }}
|
||||
group: deploy-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
@@ -27,11 +23,11 @@ jobs:
|
||||
runs-on: [self-hosted, linux, eni-g3]
|
||||
timeout-minutes: 30
|
||||
environment:
|
||||
name: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'dev' && 'rec' || 'dev' }}
|
||||
url: ${{ github.ref_name == 'main' && 'https://enervision.local' || github.ref_name == 'dev' && 'https://rec.enervision.local:8443' || 'https://dev.enervision.local:9443' }}
|
||||
name: ${{ github.ref_name == 'main' && 'prod' || 'rec' }}
|
||||
url: ${{ github.ref_name == 'main' && 'https://enervision.local' || 'https://rec.enervision.local:8443' }}
|
||||
env:
|
||||
ENVIRONNEMENT: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'dev' && 'rec' || 'dev' }}
|
||||
PORT_HTTPS: ${{ github.ref_name == 'main' && '443' || github.ref_name == 'dev' && '8443' || '9443' }}
|
||||
ENVIRONNEMENT: ${{ github.ref_name == 'main' && 'prod' || 'rec' }}
|
||||
PORT_HTTPS: ${{ github.ref_name == 'main' && '443' || '8443' }}
|
||||
steps:
|
||||
- name: Aligner le dossier de l'environnement sur la branche poussée
|
||||
run: |
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM docker
|
||||
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||
RUN docker buildx version
|
||||
|
||||
FROM python:3.14-slim AS builder
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.11.26 /uv /uvx /bin/
|
||||
@@ -8,10 +13,8 @@ ENV UV_COMPILE_BYTECODE=1 \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
uv sync --locked --no-install-project --no-dev --no-build
|
||||
COPY uv.lock pyproject.toml /app/
|
||||
RUN uv sync --locked --no-install-project --no-dev
|
||||
|
||||
# Le projet lui-meme n'est pas installe (pas de second `uv sync`) : il tourne depuis /app, le
|
||||
# repertoire de travail, et rien ne lit ses metadonnees. L'installer imposerait de le construire
|
||||
@@ -19,6 +22,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
# l'installation des dependances n'execute aucun script de build (regle Sonar docker:S8541).
|
||||
COPY . /app
|
||||
|
||||
RUN uv sync --locked --no-dev
|
||||
|
||||
FROM python:3.14-slim AS runtime
|
||||
|
||||
|
||||
@@ -16,9 +16,6 @@ EN_TETES: Final[dict[str, str]] = {
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "DENY",
|
||||
"Referrer-Policy": "no-referrer",
|
||||
# same-origin : aucun client ne charge l'API en no-cors depuis une autre origine
|
||||
# (proxy.conf.json en dev, reverse proxy nginx ensuite, cf. docs/architecture/20-backend.md).
|
||||
"Cross-Origin-Resource-Policy": "same-origin",
|
||||
}
|
||||
|
||||
PREFIXE_AUTHENTIFICATION: Final = "/auth"
|
||||
|
||||
@@ -23,9 +23,8 @@ async def interroge(
|
||||
("x-content-type-options", "nosniff"),
|
||||
("x-frame-options", "DENY"),
|
||||
("referrer-policy", "no-referrer"),
|
||||
("cross-origin-resource-policy", "same-origin"),
|
||||
],
|
||||
ids=["nosniff", "anti_iframe", "referrer", "corp"],
|
||||
ids=["nosniff", "anti_iframe", "referrer"],
|
||||
)
|
||||
async def test_every_response_carries_the_security_headers(
|
||||
client: AsyncClient, entete: str, valeur: str
|
||||
|
||||
@@ -26,20 +26,28 @@ RUN npm run build
|
||||
FROM nginx:1.31-alpine AS runner
|
||||
|
||||
# Copie de la configuration de nginx
|
||||
COPY --chown=root:root --chmod=755 nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --chown=root:root nginx.conf /etc/nginx/nginx.conf
|
||||
RUN chmod 755 /etc/nginx/nginx.conf
|
||||
|
||||
# Copy the static build output from the build stage to Nginx's default HTML serving directory
|
||||
COPY --chown=root:root --chmod=755 --from=builder /app/dist/*/browser /usr/share/nginx/html
|
||||
COPY --chown=root:root --from=builder /app/dist/frontend/browser /usr/share/nginx/html
|
||||
RUN chmod 755 /usr/share/nginx/html
|
||||
|
||||
|
||||
# Create necessary directories with proper permissions for nginx
|
||||
RUN mkdir -p /var/log/nginx /var/cache/nginx && \
|
||||
chown -R nginx:nginx /var/log/nginx /var/cache/nginx /usr/share/nginx/html
|
||||
chown -R nginx:nginx /var/log/nginx /var/cache/nginx /usr/share/nginx/html &&\
|
||||
chmod 755 /var/log/nginx /var/log
|
||||
|
||||
# Permissions appropriées
|
||||
RUN find /usr/share/nginx/html -type f -exec chmod 644 {} \; && \
|
||||
find /usr/share/nginx/html -type d -exec chmod 755 {} \; && \
|
||||
chown -R nginx:nginx /var/log/nginx /var/cache/nginx
|
||||
|
||||
# Use a non-root user for security best practices
|
||||
USER nginx
|
||||
|
||||
# Frontend : port 3000
|
||||
# Backend : port 8000
|
||||
EXPOSE 3000
|
||||
|
||||
# Start Nginx directly with custom config
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
error_log /dev/null warn; # ← Envoyer les logs vers /dev/null
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
events {
|
||||
@@ -9,15 +9,14 @@ events {
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
access_log /dev/null; # ← Idem pour access logs
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
|
||||
server {
|
||||
listen 3000;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
@@ -29,4 +28,4 @@ http {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"build": "node node_modules/@angular/cli/bin/ng.js build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"test:ci": "ng test --watch=false"
|
||||
"test:ci": "ng test --watch=false"
|
||||
},
|
||||
"private": true,
|
||||
"packageManager": "npm@11.19.0",
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# 0017 - Un troisième environnement, `dev`, déployé à la demande depuis n'importe quelle branche
|
||||
|
||||
- Statut : accepté
|
||||
- Date : 2026-09-23
|
||||
|
||||
## Contexte
|
||||
|
||||
L'[ADR 0009](0009-deux-environnements-compose-sur-la-vm-eni.md) a posé deux environnements sur
|
||||
la VM ENI : la recette suit `dev`, la production suit `main`. Les environnements GitHub en
|
||||
comptent trois, `dev`, `rec` et `prod`, et le troisième ne déployait rien.
|
||||
|
||||
Il manque un endroit où montrer une branche de travail avant son merge : la recette ne doit
|
||||
porter que ce qui est intégré à `dev`, sinon elle cesse d'être une recette. Un
|
||||
`workflow_dispatch` sur une branche de travail envoyait d'ailleurs cette branche dans la
|
||||
recette, puisque tout ce qui n'était pas `main` y partait.
|
||||
|
||||
La VM est passée à 32 Go : une troisième TimescaleDB, réglée à 2 Go comme les deux autres,
|
||||
tient sans peine.
|
||||
|
||||
## Décision
|
||||
|
||||
**Un troisième projet Compose, `enervision-dev`, dans `/srv/enervision/dev`**, bâti exactement
|
||||
comme les deux autres : son clone, son `.env`, son certificat, préparés par
|
||||
`scripts/provision-host.sh`.
|
||||
|
||||
**Déployé à la demande, jamais sur un push.** `deploy.yml` envoie `main` en prod, `dev` en
|
||||
recette, et toute autre branche lancée depuis l'onglet Actions dans `dev`. Seul un membre ayant
|
||||
le droit d'écriture sur le dépôt peut lancer un workflow.
|
||||
|
||||
**Ports décalés d'un cran de plus** : HTTPS `9443`, et sur `127.0.0.1` la redirection HTTP
|
||||
`8083`, PostgreSQL `5435`, Mailpit `8027`, Airflow `8084`. Nom d'hôte `dev.enervision.local`,
|
||||
pour la même raison de cookie que la recette.
|
||||
|
||||
**Le groupe de concurrence suit l'environnement**, et non plus la branche : deux branches lancées
|
||||
coup sur coup écriraient sinon dans le même dossier en même temps.
|
||||
|
||||
## Alternatives écartées
|
||||
|
||||
- **`dev` suit la branche `dev` à chaque push, la recette devient manuelle** : la recette
|
||||
offrirait une version figée au jury, mais la doc CI/CD, l'ADR 0009 et l'habitude de l'équipe
|
||||
basculeraient à deux jours du rendu.
|
||||
- **Un environnement par branche de travail** : un projet Compose et une TimescaleDB par
|
||||
branche, sans mécanisme de nettoyage. La machine ne le porterait pas longtemps.
|
||||
- **Garder `dev` sur les postes seulement** : rien à montrer d'une branche non mergée sans
|
||||
passer par la recette.
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Une branche de travail créée avant ce changement porte l'ancien `deploy.yml` : lancée à la
|
||||
main, elle part encore dans la recette. Limiter l'environnement GitHub `rec` à la branche
|
||||
`dev` ferme ce chemin, réglage que seul un administrateur du dépôt peut poser.
|
||||
- `dev` ne garde aucune donnée d'une branche à l'autre au-delà de ce que ses migrations
|
||||
acceptent : une branche dont les migrations divergent de `dev` peut laisser la base dans un
|
||||
état que la suivante refuse. Recréer le volume, `docker compose down -v`, est alors le remède.
|
||||
- Trois environnements construisent leurs images séparément : l'écart de l'ADR 0009, un même
|
||||
commit construit deux fois, reste ouvert jusqu'au passage à GHCR.
|
||||
@@ -227,27 +227,26 @@ Deux conséquences se propagent jusqu'à l'application, et elles ne se devinent
|
||||
- `APP_TRUST_PROXY_HEADERS` passe à vrai en même temps, sinon la limitation de débit par IP
|
||||
compte sur l'IP du proxy et devient globale.
|
||||
|
||||
### Trois environnements sur la même machine
|
||||
### Deux environnements sur la même machine
|
||||
|
||||
Statut : `En cours`. Décision et motifs dans
|
||||
l'[ADR 0009](../adr/0009-deux-environnements-compose-sur-la-vm-eni.md), étendue à un troisième
|
||||
environnement par l'[ADR 0017](../adr/0017-environnement-dev-a-la-demande.md).
|
||||
La VM `eadl-2025-nantes-g3` porte le développement, la recette et la production, chacun dans son
|
||||
clone du dépôt, son `.env` et son projet Compose. Le nom de projet préfixe volumes, réseau et
|
||||
conteneurs : rien n'est partagé. `scripts/provision-host.sh` prépare les trois dossiers, génère
|
||||
les secrets et les certificats, et ne démarre rien.
|
||||
Statut : `En cours`, la machine n'étant pas encore provisionnée. Décision et motifs dans
|
||||
l'[ADR 0009](../adr/0009-deux-environnements-compose-sur-la-vm-eni.md).
|
||||
La VM `eadl-2025-nantes-g3` portera la recette et la production, chacune dans son clone du dépôt,
|
||||
son `.env` et son projet Compose. Le nom de projet préfixe volumes, réseau et conteneurs : rien
|
||||
n'est partagé. `scripts/provision-host.sh` prépare les deux dossiers, génère les secrets et les
|
||||
certificats, et ne démarre rien.
|
||||
|
||||
| | Développement | Recette | Production |
|
||||
|---|---|---|---|
|
||||
| Branche, environnement GitHub | toute branche lancée à la main, `dev` | `dev`, `rec` | `main`, `prod` |
|
||||
| Dossier, projet Compose | `/srv/enervision/dev`, `enervision-dev` | `/srv/enervision/rec`, `enervision-rec` | `/srv/enervision/prod`, `enervision-prod` |
|
||||
| URL | `https://dev.enervision.local:9443` | `https://rec.enervision.local:8443` | `https://enervision.local` |
|
||||
| Proxy HTTP, HTTPS | `127.0.0.1:8083`, `9443` | `127.0.0.1:8081`, `8443` | `80`, `443` |
|
||||
| PostgreSQL, Mailpit, Airflow, sur `127.0.0.1` | `5435`, `8027`, `8084` | `5434`, `8026`, `8082` | `5433`, `8025`, `8080` |
|
||||
| | Recette | Production |
|
||||
|---|---|---|
|
||||
| Branche, environnement GitHub | `dev`, `rec` | `main`, `prod` |
|
||||
| Dossier, projet Compose | `/srv/enervision/rec`, `enervision-rec` | `/srv/enervision/prod`, `enervision-prod` |
|
||||
| URL | `https://rec.enervision.local:8443` | `https://enervision.local` |
|
||||
| Proxy HTTP, HTTPS | `127.0.0.1:8081`, `8443` | `80`, `443` |
|
||||
| PostgreSQL, Mailpit, Airflow, sur `127.0.0.1` | `5434`, `8026`, `8082` | `5433`, `8025`, `8080` |
|
||||
|
||||
Les trois noms d'hôte visent la même IP, à déclarer dans le `/etc/hosts` des postes. Deux noms
|
||||
Les deux noms d'hôte visent la même IP, à déclarer dans le `/etc/hosts` des postes. Deux noms
|
||||
distincts sont nécessaires : le cookie `__Secure-ev_refresh` est posé par hôte, pas par port.
|
||||
La redirection HTTP de la recette et du développement est ramenée sur la boucle locale parce que la configuration
|
||||
La redirection HTTP de la recette est ramenée sur la boucle locale parce que la configuration
|
||||
Nginx renvoie vers `https://$host` sans port, c'est-à-dire vers la production.
|
||||
|
||||
Le déploiement est décrit dans [50-cicd.md](50-cicd.md) : un runner GitHub Actions installé sur
|
||||
@@ -268,7 +267,7 @@ sequenceDiagram
|
||||
|
||||
TF->>VM: SSH, get.docker.com puis docker compose version
|
||||
TF->>VM: copie et exécute scripts/provision-host.sh
|
||||
VM->>VM: trois clones, trois .env, trois certificats
|
||||
VM->>VM: deux clones, deux .env, deux certificats
|
||||
TF->>VM: installe actions-runner, config.sh, svc.sh
|
||||
VM->>GH: le runner s'enregistre avec le label eni-g3
|
||||
```
|
||||
|
||||
@@ -423,13 +423,8 @@ Le reste, par ordre de surface :
|
||||
écriture des journaux. C'est la troisième ligne de défense : la première est de ne rien passer
|
||||
de secret au logger, la deuxième de ne jamais mettre un jeton dans une URL.
|
||||
- En-têtes posés par l'application : `X-Content-Type-Options`, `X-Frame-Options`,
|
||||
`Referrer-Policy`, `Cross-Origin-Resource-Policy: same-origin`, plus `Cache-Control: no-store`
|
||||
sur `/auth/*`. Le CORP est fixé à `same-origin` parce qu'aucun client légitime ne charge l'API
|
||||
en `no-cors` (image, script, média) depuis une autre origine : le frontend l'appelle en relatif
|
||||
(`/api/v1`), sur sa propre origine, via `proxy.conf.json` en dev et le reverse proxy nginx
|
||||
(`infra/proxy/conf.d/enervision.conf`) en recette et en production. Les appels `HttpClient`, en
|
||||
mode `cors`, n'y sont de toute façon pas soumis. HSTS et CSP appartiennent au terminateur TLS, que
|
||||
l'application ne connaît pas : le reverse proxy les pose
|
||||
`Referrer-Policy`, plus `Cache-Control: no-store` sur `/auth/*`. HSTS et CSP appartiennent au
|
||||
terminateur TLS, que l'application ne connaît pas : le reverse proxy les pose
|
||||
([ADR 0007](../adr/0007-terminaison-tls-et-reverse-proxy-nginx.md)).
|
||||
- Le conteneur tourne en utilisateur non-root, avec un `HEALTHCHECK` sur `/api/v1/health/live`.
|
||||
- TLS, limitation de débit au frontal et journal d'accès sont portés par le reverse proxy.
|
||||
|
||||
@@ -131,14 +131,13 @@ entrant n'est ouvert.
|
||||
|---|---|---|---|
|
||||
| `push` sur `dev` | `rec` | `/srv/enervision/rec` | aucune : la recette suit `dev` |
|
||||
| `push` sur `main` | `prod` | `/srv/enervision/prod` | approbation d'un relecteur dans l'environnement `prod`, branche `main` seule autorisée |
|
||||
| `workflow_dispatch` sur toute autre branche | `dev` | `/srv/enervision/dev` | droit d'écriture sur le dépôt, seul à pouvoir lancer un workflow ([ADR 0017](../adr/0017-environnement-dev-a-la-demande.md)) |
|
||||
|
||||
Le job aligne le clone sur la branche (`fetch`, `checkout`, `reset --hard`), lance
|
||||
`make stack-up`, qui reconstruit les images, redémarre les conteneurs puis applique les
|
||||
migrations Alembic dans le conteneur backend, et attend jusqu'à trois minutes que
|
||||
`/api/v1/health/ready` réponde derrière le proxy. Cette sonde ne vérifie que la connexion à la
|
||||
base et la présence de TimescaleDB : sans la migration, le déploiement serait vert sur une base
|
||||
sans schéma, et c'est pourquoi `make stack-up` la porte. Un groupe de concurrence par environnement,
|
||||
sans schéma, et c'est pourquoi `make stack-up` la porte. Un groupe de concurrence par branche,
|
||||
sans annulation, empêche deux déploiements simultanés du même environnement.
|
||||
|
||||
Le job ne fait pas de `actions/checkout` dans son espace de travail, et c'est voulu : le dossier
|
||||
@@ -158,7 +157,7 @@ passé à `scripts/provision-host.sh` fixe ce propriétaire.
|
||||
|
||||
La machine se prépare avec `scripts/provision-host.sh`, qui vérifie Docker et Compose 2.24.4 ou
|
||||
plus, clone les deux branches, génère les secrets de chaque `.env` et les certificats
|
||||
auto-signés, et ne démarre rien. Le détail des trois environnements, ports et noms d'hôte, est
|
||||
auto-signés, et ne démarre rien. Le détail des deux environnements, ports et noms d'hôte, est
|
||||
dans [10-infra.md](10-infra.md).
|
||||
|
||||
## Ce qui bloque un merge
|
||||
|
||||
@@ -39,7 +39,7 @@ lecture seule ; plusieurs lignes resteront à compléter une fois les endpoints
|
||||
| Cinq gardes de configuration qui refusent le démarrage plutôt que de dégrader silencieusement | `app/core/config.py` | A05 |
|
||||
| Documentation interactive fermée hors développement, `/metrics` derrière un jeton, sonde qui ne publie plus de version | `app/main.py`, `app/api/security.py` | A05 |
|
||||
| Scan dynamique OWASP ZAP de l'API authentifiée (compte `lecteur` jetable), non bloquant, configuration par défaut du backend uniquement (ni TLS ni en-têtes du reverse proxy) | `.github/workflows/dast.yml`, `scripts/dast-token.sh` | A05, API8 Security Misconfiguration |
|
||||
| En-têtes `nosniff`, `DENY`, `no-referrer`, `Cross-Origin-Resource-Policy: same-origin`, et `no-store` sur les routes d'authentification | `app/api/middleware.py` | A05 |
|
||||
| En-têtes `nosniff`, `DENY`, `no-referrer`, et `no-store` sur les routes d'authentification | `app/api/middleware.py` | A05 |
|
||||
| Refus de rétrograder ou désactiver le dernier administrateur actif | `app/services/user.py` | A04 Insecure Design |
|
||||
| Amorçage du premier administrateur hors dépôt, mot de passe jamais dans `argv` ni dans Git | `app/cli.py` | A02, A05 |
|
||||
| Réponse de l'API Mock bornée avant écriture : timeout, plafond de sites et de mesures, bornes physiques par grandeur, recopie des seuls champs attendus | `app/etl/mock_api_import.py` | API10 Unsafe Consumption of APIs |
|
||||
|
||||
+5
-7
@@ -8,9 +8,8 @@ Rien ici ne construit d'image ni ne lance de conteneur.
|
||||
- `k3s` : installe un cluster k3s single-node sur une machine distante via SSH
|
||||
(script officiel `get.k3s.io`) et rapatrie le kubeconfig en local.
|
||||
- `terraform/environments/<racine>` : une racine par machine provisionnee.
|
||||
- `vm-eni` : la VM `eadl-2025-nantes-g3`, qui porte les environnements `dev`, `rec` et `prod`
|
||||
([ADR 0009](../docs/adr/0009-deux-environnements-compose-sur-la-vm-eni.md),
|
||||
[ADR 0017](../docs/adr/0017-environnement-dev-a-la-demande.md)). Installe Docker,
|
||||
- `vm-eni` : la VM `eadl-2025-nantes-g3`, qui porte les environnements `rec` et `prod`
|
||||
([ADR 0009](../docs/adr/0009-deux-environnements-compose-sur-la-vm-eni.md)). Installe Docker,
|
||||
execute `scripts/provision-host.sh`, enregistre le runner GitHub Actions.
|
||||
- `k3s-cible` : le cluster k3s, cible a terme de `docs/architecture/10-infra.md`. Jamais
|
||||
applique.
|
||||
@@ -33,10 +32,9 @@ terraform apply
|
||||
Parametres du depot, Actions, Runners, New self-hosted runner. Seul un administrateur du depot
|
||||
peut le creer.
|
||||
|
||||
Apres l'apply, la machine porte `/srv/enervision/dev`, `/srv/enervision/rec` et
|
||||
`/srv/enervision/prod`, chacun avec son `.env` et son certificat. Le premier demarrage reste
|
||||
manuel, `make stack-up` dans chaque dossier ; les suivants sont joues par le runner a chaque push
|
||||
sur `dev` et sur `main`, et a chaque lancement manuel d'une autre branche pour `dev`.
|
||||
Apres l'apply, la machine porte `/srv/enervision/rec` et `/srv/enervision/prod`, chacun avec son
|
||||
`.env` et son certificat. Le premier demarrage reste manuel, `make stack-up` dans chaque dossier ;
|
||||
les suivants sont joues par le runner a chaque push sur `dev` et sur `main`.
|
||||
|
||||
Retirer le runner se fait a la main, depuis les parametres du depot : `terraform destroy` ne le
|
||||
desinscrit pas.
|
||||
|
||||
+44
-4
@@ -1,11 +1,31 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/null" {
|
||||
version = "3.3.2"
|
||||
constraints = "~> 3.2"
|
||||
provider "registry.terraform.io/hashicorp/archive" {
|
||||
version = "2.8.1"
|
||||
constraints = "~> 2.4"
|
||||
hashes = [
|
||||
"h1:IQ1qrkht1sC1nibUR+AJ3ulryyhVDHfCHZhoJi0sg2Y=",
|
||||
"h1:eehhIUcuegkswQDKArYBAhVV9wQmRVMhyYGaD7kHIj0=",
|
||||
"zh:03de290604114a89fcd45c2e5bc7787d5a1ebfc5f964fb5989306bea7a4c79ec",
|
||||
"zh:0a7d69dc9fbbc48960bc2f04588c8fb1bd92c78a8f306566b7fb17fc4a4f2058",
|
||||
"zh:4df1f3981379c35f1757da957470f7f7724496b57219da3485177cf1647bdf59",
|
||||
"zh:50f0e72ba53bfe6e11b03b7fc899e1f72536354381d302a743a274ae2a3f45f4",
|
||||
"zh:5c4e15a04c98e2a8cafb1cd9632b48ad318051e8462d105b1153006277985c35",
|
||||
"zh:66069e604bcf5c4af0278e15997d9e6bd755c54fb3801d78885838b889729c5f",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:979765db3f42601870ab377104ba70befb029547b278337ec4ada980e3582de6",
|
||||
"zh:b165254da774f49945a73fbccc3ef1b63d70ea00a98fa9e14716665ba80ecaad",
|
||||
"zh:c0bb2697b525da9fec4511f569ed2bd2b42f25d5c1f9fa00f8f3645b50cfc2e9",
|
||||
"zh:c48f6695d12df0d0fa5231f7c1b8d518a4feae91a733fdd35a5804af3a303831",
|
||||
"zh:d589954c93f075180c9f4e2ce91780d5edcb56dfd0d3cda8ba08e13c10b52249",
|
||||
"zh:f5792ed06da65d0daf7ca3711f5399ff78c7cb4400afe53fbce9a926fbde4477",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/null" {
|
||||
version = "3.3.2"
|
||||
hashes = [
|
||||
"h1:7rn0+p+fbrHfJxNVgTnlKp6C3yu0jENMXffz9Xb9XjQ=",
|
||||
"zh:10ec43b8b7b18d5639238c7fb9e111f6a4b038523dd66c7a426bf27b25fa4c08",
|
||||
"zh:60beb9cc2ad5b871c710860cee75b42850cc6acd43db0d77cb5e00fda7288b55",
|
||||
"zh:62538582d0a4a2f10ad8a8d9a6c3cd3f05af6c6d91c6641ffc78d4f0e8e69b27",
|
||||
@@ -21,3 +41,23 @@ provider "registry.terraform.io/hashicorp/null" {
|
||||
"zh:faa01928c25d2a6ecd9c7eb8b88134cb08de55a6b11ca6c703ac0092845344ba",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/kreuzwerker/docker" {
|
||||
version = "4.5.0"
|
||||
constraints = "~> 4.5.0"
|
||||
hashes = [
|
||||
"h1:eXPMxNcZjz5gC7Nc1oEPJldpI6B5btbvldjLOgJQNbI=",
|
||||
"zh:0ee4e9121632158a9ae1049bdd53c71fd9e06357de915dd193df2c1c55ae8814",
|
||||
"zh:12c6991012b5a548a406e1960c323484f95a9538c2522af15ec82f47fdb832d1",
|
||||
"zh:19c86ae59ed1e062e8402a49ea842f957fcda2f9689e06ecbd9480cc9bd1e41a",
|
||||
"zh:3ac8bc9805acd20c40c0ed474c9f4605aa73b61cf52809e1131e335fcbfe8e49",
|
||||
"zh:5a0e70d143759712fdda109f024a41d59d4758b5615378aa21ee6b959f65a416",
|
||||
"zh:5d3599285f71cc53c88a5802fa6e2f8ba87b9deea03abe5fe8bb2639e9d95654",
|
||||
"zh:a42f573a2526cddc9b8a93637a2c5478bc6e903ff29556ab8b2f1467681d5bde",
|
||||
"zh:c8de50d902cc8457e565a5d959f1e3e49064d31c2f14f7adf5f3ec632cfe3695",
|
||||
"zh:d40543d51eb4e902125d2c5a8a0f425a60edcec6c1dc07c4a89d92335ec05541",
|
||||
"zh:dd3105018ffddc8114083dcf84798c32fdecca7ef708eceaee49eb4b306fc061",
|
||||
"zh:e51e0199700698b962085683af4551d1f982dce37f3b6950da062c65e0145505",
|
||||
"zh:ffe415d3d3deffffdbd3a7173e3a9f90b74601cb6d8acafec1ab701a5b91dbff",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
locals {
|
||||
backend_dir = "../../../../apps/backend"
|
||||
backend_image_name = "enervision-back"
|
||||
backend_image_tag = "latest"
|
||||
backend_container_name = "enervision-backend"
|
||||
}
|
||||
|
||||
resource "docker_image" "backend" {
|
||||
name = "${local.backend_image_name}:${local.backend_image_tag}"
|
||||
|
||||
build {
|
||||
context = local.backend_dir
|
||||
dockerfile = "Dockerfile"
|
||||
}
|
||||
|
||||
triggers = {
|
||||
build_hash = sha256(join("", [
|
||||
for file in fileset(local.backend_dir, "**") :
|
||||
filesha256("${local.backend_dir}/${file}")
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_container" "backend" {
|
||||
image = docker_image.backend.image_id
|
||||
name = local.backend_container_name
|
||||
|
||||
ports {
|
||||
internal = 8000
|
||||
external = var.backend_port
|
||||
}
|
||||
|
||||
volumes {
|
||||
host_path = "/var/log/enervision"
|
||||
container_path = "/var/log/enervision"
|
||||
}
|
||||
|
||||
depends_on = [docker_image.backend]
|
||||
}
|
||||
|
||||
resource "null_resource" "deploy_backend_to_server" {
|
||||
depends_on = [docker_image.backend]
|
||||
|
||||
triggers = {
|
||||
image_id = docker_image.backend.image_id
|
||||
}
|
||||
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = var.ssh_host
|
||||
port = var.ssh_port
|
||||
user = var.ssh_user
|
||||
password = var.ssh_password
|
||||
private_key = try(file(pathexpand(var.ssh_private_key_path)), null)
|
||||
timeout = "5m"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
source = "${local.backend_dir}/Dockerfile"
|
||||
destination = "/tmp/Dockerfile"
|
||||
}
|
||||
provisioner "file" {
|
||||
source = "${local.backend_dir}/"
|
||||
destination = "/tmp/backend/"
|
||||
}
|
||||
|
||||
provisioner "remote-exec" {
|
||||
inline = [
|
||||
"cd /tmp/backend",
|
||||
"sudo docker stop ${local.backend_container_name} 2>/dev/null || true",
|
||||
"sudo docker rm ${local.backend_container_name} 2>/dev/null || true",
|
||||
"sudo docker build -t ${local.backend_image_name}:${local.backend_image_tag} .",
|
||||
"sudo docker run -d --name ${local.backend_container_name} -p ${var.backend_port}:8000 --restart always -v /var/log/enervision:/var/log/enervision ${local.backend_image_name}:${local.backend_image_tag}"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
locals {
|
||||
frontend_environments = {
|
||||
dev = {
|
||||
source_dir = "${path.root}/../../../../apps/frontend/dist/frontend/browser"
|
||||
domain = "dev.enervision"
|
||||
}
|
||||
}
|
||||
|
||||
frontend_dir = "../../../../apps/frontend"
|
||||
image_name = "enervision-front"
|
||||
image_tag = "latest"
|
||||
container_name = "enervision-frontend"
|
||||
|
||||
selected_frontend = local.frontend_environments[var.deployment_environment]
|
||||
}
|
||||
|
||||
provider "docker" {
|
||||
host = var.docker_host
|
||||
}
|
||||
|
||||
# Build le front
|
||||
resource "null_resource" "build_frontend" {
|
||||
triggers = {
|
||||
frontend_files = sha256(join("", [
|
||||
for file in fileset(local.frontend_dir, "src/**,package.json") :
|
||||
filesha256("${local.frontend_dir}/${file}")
|
||||
]))
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "cd ${local.frontend_dir} && npm ci && chmod -R +x node_modules/@angular/cli/bin && chmod -R +x node_modules/.bin && npm exec ng build"
|
||||
}
|
||||
}
|
||||
|
||||
# Build l'image docker du front
|
||||
resource "docker_image" "frontend" {
|
||||
name = "${local.image_name}:${local.image_tag}"
|
||||
|
||||
build {
|
||||
context = local.frontend_dir
|
||||
dockerfile = "Dockerfile"
|
||||
}
|
||||
|
||||
triggers = {
|
||||
build_hash = sha256(join("", [
|
||||
for file in fileset(local.frontend_dir, "**") :
|
||||
filesha256("${local.frontend_dir}/${file}")
|
||||
]))
|
||||
}
|
||||
|
||||
depends_on = [null_resource.build_frontend]
|
||||
}
|
||||
|
||||
# Création du container Docker
|
||||
resource "docker_container" "frontend" {
|
||||
image = docker_image.frontend.image_id
|
||||
name = local.container_name
|
||||
|
||||
ports {
|
||||
internal = 3000
|
||||
external = var.frontend_port
|
||||
}
|
||||
|
||||
# Volume pour les logs
|
||||
volumes {
|
||||
host_path = "/var/log/enervision"
|
||||
container_path = "/var/log/nginx"
|
||||
}
|
||||
|
||||
depends_on = [docker_image.frontend]
|
||||
}
|
||||
|
||||
resource "null_resource" "create_network" {
|
||||
provisioner "remote-exec" {
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = var.ssh_host
|
||||
port = var.ssh_port
|
||||
user = var.ssh_user
|
||||
password = var.ssh_password
|
||||
private_key = try(file(pathexpand(var.ssh_private_key_path)), null)
|
||||
timeout = "5m"
|
||||
}
|
||||
|
||||
inline = [
|
||||
"sudo docker network create enervision_default 2>/dev/null || true"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# Déploiement du container sur le serveur via SSH
|
||||
resource "null_resource" "deploy_to_server" {
|
||||
depends_on = [docker_image.frontend]
|
||||
|
||||
triggers = {
|
||||
image_id = docker_image.frontend.image_id
|
||||
}
|
||||
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = var.ssh_host
|
||||
port = var.ssh_port
|
||||
user = var.ssh_user
|
||||
password = var.ssh_password
|
||||
private_key = try(file(pathexpand(var.ssh_private_key_path)), null)
|
||||
timeout = "5m"
|
||||
}
|
||||
|
||||
# Copie le Dockerfile et nginx.conf
|
||||
provisioner "file" {
|
||||
source = "${local.frontend_dir}/Dockerfile"
|
||||
destination = "/tmp/Dockerfile"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
source = "${local.frontend_dir}/nginx.conf"
|
||||
destination = "/tmp/nginx.conf"
|
||||
}
|
||||
|
||||
# Copie les sources pour le build
|
||||
provisioner "file" {
|
||||
source = "${local.frontend_dir}/"
|
||||
destination = "/tmp/frontend/"
|
||||
}
|
||||
|
||||
# Build et lance le container sur le serveur
|
||||
provisioner "remote-exec" {
|
||||
inline = [
|
||||
"cd /tmp/frontend",
|
||||
"sudo docker stop ${local.container_name} 2>/dev/null || true",
|
||||
"sudo docker rm ${local.container_name} 2>/dev/null || true",
|
||||
"sudo docker build -t ${local.image_name}:${local.image_tag} .",
|
||||
"sudo docker run -d \\",
|
||||
" --name ${local.container_name} \\",
|
||||
" --network enervision_default \\",
|
||||
" --network-alias frontend \\",
|
||||
" -p ${var.frontend_port}:3000 \\",
|
||||
" --restart always \\",
|
||||
" -v /var/log/enervision:/var/log/nginx \\",
|
||||
" ${local.image_name}:${local.image_tag}"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,24 @@ module "k3s" {
|
||||
ssh_port = var.ssh_port
|
||||
ssh_user = var.ssh_user
|
||||
ssh_private_key_path = var.ssh_private_key_path
|
||||
ssh_password = var.ssh_password
|
||||
k3s_version = var.k3s_version
|
||||
k3s_disable_components = var.k3s_disable_components
|
||||
kubeconfig_output_path = var.kubeconfig_output_path
|
||||
}
|
||||
|
||||
provider "vault" {
|
||||
# Adresse du serveur Vault (dev)
|
||||
address = "https://10.101.200.37:8200"
|
||||
|
||||
# Auth par userpass
|
||||
auth_login {
|
||||
path = "auth/userpass/login/${var.vault_username}"
|
||||
parameters = {
|
||||
login = var.vault_username
|
||||
password = var.vault_password
|
||||
}
|
||||
}
|
||||
# Skip verify pour dev uniquement (pas en prod!)
|
||||
# skip_client_verification = true
|
||||
}
|
||||
@@ -21,6 +21,35 @@ variable "ssh_private_key_path" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ssh_password" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "remote_path" {
|
||||
type = string
|
||||
description = "Chemin distant sur le serveur pour le deploiement."
|
||||
default = "/var/www/enervision"
|
||||
}
|
||||
|
||||
variable "docker_host" {
|
||||
type = string
|
||||
default = "npipe:////.//pipe//docker_engine"
|
||||
description = "Host Docker (local ou distant)"
|
||||
}
|
||||
|
||||
variable "frontend_port" {
|
||||
type = number
|
||||
default = 3000
|
||||
description = "Port externe du frontend"
|
||||
}
|
||||
|
||||
variable "backend_port" {
|
||||
type = number
|
||||
default = 8000
|
||||
description = "Port externe du backend"
|
||||
}
|
||||
|
||||
variable "k3s_version" {
|
||||
type = string
|
||||
description = "Version k3s a epingler pour un deploiement reproductible (ex: v1.31.5+k3s1). Voir https://github.com/k3s-io/k3s/releases."
|
||||
@@ -37,3 +66,25 @@ variable "kubeconfig_output_path" {
|
||||
description = "Chemin local ou ecrire le kubeconfig recupere apres installation."
|
||||
default = "./kubeconfig"
|
||||
}
|
||||
|
||||
variable "deployment_environment" {
|
||||
type = string
|
||||
default = "dev"
|
||||
|
||||
validation {
|
||||
condition = contains(["dev", "rec", "prod"], var.deployment_environment)
|
||||
error_message = "L'environnement doit être dev, rec ou prod."
|
||||
}
|
||||
}
|
||||
|
||||
variable "vault_username" {
|
||||
description = "Username Vault"
|
||||
type = string
|
||||
sensitive = false
|
||||
}
|
||||
|
||||
variable "vault_password" {
|
||||
description = "Password Vault"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
locals {
|
||||
frontend_environments = {
|
||||
prod = {
|
||||
source_dir = "${path.root}/../../../apps/frontend/dist/frontend/browser"
|
||||
domain = "enervision"
|
||||
}
|
||||
}
|
||||
|
||||
frontend_dir = "../../../../apps/frontend"
|
||||
image_name = "enervision-front"
|
||||
image_tag = "latest"
|
||||
container_name = "enervision-frontend"
|
||||
|
||||
selected_frontend = local.frontend_environments[var.deployment_environment]
|
||||
}
|
||||
|
||||
provider "docker" {
|
||||
host = var.docker_host
|
||||
}
|
||||
|
||||
# Build le front
|
||||
resource "null_resource" "build_frontend" {
|
||||
triggers = {
|
||||
frontend_files = sha256(join("", [
|
||||
for file in fileset(local.frontend_dir, "src/**,package.json") :
|
||||
filesha256("${local.frontend_dir}/${file}")
|
||||
]))
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = "cd ${local.frontend_dir} && npm ci && chmod -R +x node_modules/@angular/cli/bin && chmod -R +x node_modules/.bin && npm exec ng build"
|
||||
}
|
||||
}
|
||||
|
||||
# Build l'image docker du front
|
||||
resource "docker_image" "frontend" {
|
||||
name = "${local.image_name}:${local.image_tag}"
|
||||
|
||||
build {
|
||||
context = local.frontend_dir
|
||||
dockerfile = "Dockerfile"
|
||||
}
|
||||
|
||||
triggers = {
|
||||
build_hash = sha256(join("", [
|
||||
for file in fileset(local.frontend_dir, "**") :
|
||||
filesha256("${local.frontend_dir}/${file}")
|
||||
]))
|
||||
}
|
||||
|
||||
depends_on = [null_resource.build_frontend]
|
||||
}
|
||||
|
||||
# Création du container Docker
|
||||
resource "docker_container" "frontend" {
|
||||
image = docker_image.frontend.image_id
|
||||
name = local.container_name
|
||||
|
||||
ports {
|
||||
internal = 3000
|
||||
external = var.frontend_port
|
||||
}
|
||||
|
||||
# Volume pour les logs
|
||||
volumes {
|
||||
host_path = "/var/log/enervision"
|
||||
container_path = "/var/log/nginx"
|
||||
}
|
||||
|
||||
depends_on = [docker_image.frontend]
|
||||
}
|
||||
|
||||
# Déploiement du container sur le serveur via SSH
|
||||
resource "null_resource" "deploy_to_server" {
|
||||
depends_on = [docker_image.frontend]
|
||||
|
||||
triggers = {
|
||||
image_id = docker_image.frontend.image_id
|
||||
}
|
||||
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = var.ssh_host
|
||||
port = var.ssh_port
|
||||
user = var.ssh_user
|
||||
password = var.ssh_password
|
||||
private_key = try(file(pathexpand(var.ssh_private_key_path)), null)
|
||||
timeout = "5m"
|
||||
}
|
||||
|
||||
# Copie le Dockerfile et nginx.conf
|
||||
provisioner "file" {
|
||||
source = "${local.frontend_dir}/Dockerfile"
|
||||
destination = "/tmp/Dockerfile"
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
source = "${local.frontend_dir}/nginx.conf"
|
||||
destination = "/tmp/nginx.conf"
|
||||
}
|
||||
|
||||
# Copie les sources pour le build
|
||||
provisioner "file" {
|
||||
source = "${local.frontend_dir}/"
|
||||
destination = "/tmp/frontend/"
|
||||
}
|
||||
|
||||
# Build et lance le container sur le serveur
|
||||
provisioner "remote-exec" {
|
||||
inline = [
|
||||
"cd /tmp/frontend",
|
||||
"sudo docker stop ${local.container_name} 2>/dev/null || true",
|
||||
"sudo docker rm ${local.container_name} 2>/dev/null || true",
|
||||
"sudo docker build -t ${local.image_name}:${local.image_tag} .",
|
||||
"sudo docker run -d \\",
|
||||
" --name ${local.container_name} \\",
|
||||
" --network enervision_default \\",
|
||||
" --network-alias frontend \\",
|
||||
" -p ${var.frontend_port}:3000 \\",
|
||||
" --restart always \\",
|
||||
" -v /var/log/enervision:/var/log/nginx \\",
|
||||
" ${local.image_name}:${local.image_tag}"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module "k3s" {
|
||||
source = "../../modules/k3s"
|
||||
|
||||
ssh_host = var.ssh_host
|
||||
ssh_port = var.ssh_port
|
||||
ssh_user = var.ssh_user
|
||||
ssh_private_key_path = var.ssh_private_key_path
|
||||
k3s_version = var.k3s_version
|
||||
k3s_disable_components = var.k3s_disable_components
|
||||
kubeconfig_output_path = var.kubeconfig_output_path
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
output "kubeconfig_path" {
|
||||
description = "Chemin local du kubeconfig recupere apres installation."
|
||||
value = module.k3s.kubeconfig_path
|
||||
}
|
||||
|
||||
output "node_host" {
|
||||
description = "Adresse du serveur sur lequel k3s est installe."
|
||||
value = module.k3s.node_host
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
variable "ssh_host" {
|
||||
type = string
|
||||
description = "Adresse IP ou nom d'hote du serveur on-premise de l'ecole."
|
||||
}
|
||||
|
||||
variable "ssh_port" {
|
||||
type = number
|
||||
description = "Port SSH du serveur."
|
||||
default = 22
|
||||
}
|
||||
|
||||
variable "ssh_user" {
|
||||
type = string
|
||||
description = "Utilisateur SSH utilise pour l'installation."
|
||||
default = "root"
|
||||
}
|
||||
|
||||
variable "ssh_private_key_path" {
|
||||
type = string
|
||||
description = "Chemin local vers la cle privee SSH."
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "k3s_version" {
|
||||
type = string
|
||||
description = "Version k3s a epingler pour un deploiement reproductible (ex: v1.31.5+k3s1). Voir https://github.com/k3s-io/k3s/releases."
|
||||
}
|
||||
|
||||
variable "k3s_disable_components" {
|
||||
type = list(string)
|
||||
description = "Composants embarques k3s a desactiver."
|
||||
default = ["traefik"]
|
||||
}
|
||||
|
||||
variable "kubeconfig_output_path" {
|
||||
type = string
|
||||
description = "Chemin local ou ecrire le kubeconfig recupere apres installation."
|
||||
default = "./kubeconfig"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
terraform {
|
||||
required_version = ">= 1.7"
|
||||
|
||||
required_providers {
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.2"
|
||||
}
|
||||
}
|
||||
|
||||
backend "local" {
|
||||
path = "terraform.tfstate"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
locals {
|
||||
frontend_environments = {
|
||||
rec = {
|
||||
source_dir = "${path.root}/../../../apps/frontend/dist/frontend-rec/browser"
|
||||
domain = "rec.enervision"
|
||||
}
|
||||
}
|
||||
|
||||
selected_frontend = local.frontend_environments[var.deployment_environment]
|
||||
}
|
||||
|
||||
resource "null_resource" "frontend" {
|
||||
depends_on = [module.k3s]
|
||||
|
||||
triggers = {
|
||||
environment = var.deployment_environment
|
||||
|
||||
build_hash = sha256(join("", [
|
||||
for file in fileset("${path.root}/../../../apps/frontend/dist/frontend/browser", "**") :
|
||||
filesha256("${path.root}/../../../apps/frontend/dist/frontend/browser/${file}")
|
||||
]))
|
||||
}
|
||||
|
||||
// Variables pour la connexion SSH
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = var.ssh_host
|
||||
port = var.ssh_port
|
||||
user = var.ssh_user
|
||||
private_key = file(pathexpand(var.ssh_private_key_path))
|
||||
}
|
||||
|
||||
// Lancement de script en SSH avec remote-exec
|
||||
// Installation de Nginx et initialisation du répertoire du frontend
|
||||
provisioner "remote-exec" {
|
||||
inline = [
|
||||
"sudo apt-get update",
|
||||
"sudo apt-get install -y nginx",
|
||||
"sudo mkdir -p /var/www/enervision",
|
||||
"sudo rm -rf /var/www/enervision/*"
|
||||
]
|
||||
}
|
||||
|
||||
// Copie des fichiers vers le serveur
|
||||
provisioner "file" {
|
||||
source = "${path.root}/../../../apps/frontend/dist/frontend/browser/"
|
||||
destination = "/tmp/enervision-frontend"
|
||||
}
|
||||
|
||||
// Déplacement des fichiers
|
||||
provisioner "remote-exec" {
|
||||
inline = [
|
||||
"sudo cp -r /tmp/enervision-frontend/* /var/www/enervision/",
|
||||
"sudo chown -R www-data:www-data /var/www/enervision"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module "k3s" {
|
||||
source = "../../modules/k3s"
|
||||
|
||||
ssh_host = var.ssh_host
|
||||
ssh_port = var.ssh_port
|
||||
ssh_user = var.ssh_user
|
||||
ssh_private_key_path = var.ssh_private_key_path
|
||||
k3s_version = var.k3s_version
|
||||
k3s_disable_components = var.k3s_disable_components
|
||||
kubeconfig_output_path = var.kubeconfig_output_path
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
output "kubeconfig_path" {
|
||||
description = "Chemin local du kubeconfig recupere apres installation."
|
||||
value = module.k3s.kubeconfig_path
|
||||
}
|
||||
|
||||
output "node_host" {
|
||||
description = "Adresse du serveur sur lequel k3s est installe."
|
||||
value = module.k3s.node_host
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
variable "ssh_host" {
|
||||
type = string
|
||||
description = "Adresse IP ou nom d'hote du serveur on-premise de l'ecole."
|
||||
}
|
||||
|
||||
variable "ssh_port" {
|
||||
type = number
|
||||
description = "Port SSH du serveur."
|
||||
default = 22
|
||||
}
|
||||
|
||||
variable "ssh_user" {
|
||||
type = string
|
||||
description = "Utilisateur SSH utilise pour l'installation."
|
||||
default = "root"
|
||||
}
|
||||
|
||||
variable "ssh_private_key_path" {
|
||||
type = string
|
||||
description = "Chemin local vers la cle privee SSH."
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "k3s_version" {
|
||||
type = string
|
||||
description = "Version k3s a epingler pour un deploiement reproductible (ex: v1.31.5+k3s1). Voir https://github.com/k3s-io/k3s/releases."
|
||||
}
|
||||
|
||||
variable "k3s_disable_components" {
|
||||
type = list(string)
|
||||
description = "Composants embarques k3s a desactiver."
|
||||
default = ["traefik"]
|
||||
}
|
||||
|
||||
variable "kubeconfig_output_path" {
|
||||
type = string
|
||||
description = "Chemin local ou ecrire le kubeconfig recupere apres installation."
|
||||
default = "./kubeconfig"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
terraform {
|
||||
required_version = ">= 1.7"
|
||||
|
||||
required_providers {
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.2"
|
||||
}
|
||||
}
|
||||
|
||||
backend "local" {
|
||||
path = "terraform.tfstate"
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
# Contrainte : pas de provisioner `destroy` sur le runner. Il imposerait une connexion ne lisant
|
||||
# que `self`, donc le chemin de la cle SSH dans le state, et `svc.sh uninstall` ne desinscrit pas
|
||||
# le runner cote GitHub : le retrait reste manuel, depuis les parametres du depot.
|
||||
# Ref : ADR 0009 et 0017 pour les trois environnements, `scripts/provision-host.sh` pour leur contenu.
|
||||
# Ref : ADR 0009 pour les deux environnements, `scripts/provision-host.sh` pour leur contenu.
|
||||
|
||||
locals {
|
||||
sudo = var.ssh_user == "root" ? "" : "sudo "
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
variable "ssh_host" {
|
||||
type = string
|
||||
description = "Adresse de la VM ENI qui porte les trois environnements (ADR 0009, ADR 0017)."
|
||||
description = "Adresse de la VM ENI qui porte les deux environnements (ADR 0009)."
|
||||
}
|
||||
|
||||
variable "ssh_port" {
|
||||
|
||||
@@ -2,13 +2,15 @@ terraform {
|
||||
required_version = ">= 1.7"
|
||||
|
||||
required_providers {
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.2"
|
||||
archive = {
|
||||
source = "hashicorp/archive"
|
||||
version = "~> 2.4"
|
||||
}
|
||||
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
version = "~> 4.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
backend "local" {
|
||||
path = "terraform.tfstate"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ locals {
|
||||
# l'adresse, le port, l'utilisateur et le chemin de la cle : jamais la cle ni un mot de passe.
|
||||
resource "null_resource" "k3s_install" {
|
||||
triggers = {
|
||||
ssh_host = var.ssh_host
|
||||
ssh_port = tostring(var.ssh_port)
|
||||
ssh_user = var.ssh_user
|
||||
ssh_password = var.ssh_password
|
||||
k3s_version = var.k3s_version
|
||||
disable_components = join(",", var.k3s_disable_components)
|
||||
sudo_prefix = local.sudo_prefix
|
||||
ssh_host = var.ssh_host
|
||||
ssh_port = tostring(var.ssh_port)
|
||||
ssh_user = var.ssh_user
|
||||
@@ -20,11 +27,11 @@ resource "null_resource" "k3s_install" {
|
||||
}
|
||||
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = self.triggers.ssh_host
|
||||
port = tonumber(self.triggers.ssh_port)
|
||||
user = self.triggers.ssh_user
|
||||
private_key = file(pathexpand(self.triggers.ssh_key_path))
|
||||
type = "ssh"
|
||||
host = self.triggers.ssh_host
|
||||
port = self.triggers.ssh_port
|
||||
user = self.triggers.ssh_user
|
||||
password = self.triggers.ssh_password
|
||||
}
|
||||
|
||||
provisioner "remote-exec" {
|
||||
@@ -53,10 +60,13 @@ resource "null_resource" "fetch_kubeconfig" {
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
interpreter = ["bash", "-c"]
|
||||
command = <<-EOT
|
||||
ssh -i "${var.ssh_private_key_path}" -p ${var.ssh_port} -o StrictHostKeyChecking=accept-new ${var.ssh_user}@${var.ssh_host} '${local.kubeconfig_cmd}' \
|
||||
| sed 's/127.0.0.1/${var.ssh_host}/' > "${var.kubeconfig_output_path}"
|
||||
interpreter = ["PowerShell", "-NoProfile", "-Command"]
|
||||
|
||||
command = <<-EOT
|
||||
$content = ssh -i "${pathexpand(var.ssh_private_key_path)}" -p ${var.ssh_port} -o StrictHostKeyChecking=accept-new ${var.ssh_user}@${var.ssh_host} "cat /etc/rancher/k3s/k3s.yaml"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
$content -replace "127.0.0.1", "${var.ssh_host}" |
|
||||
Set-Content -Path "${var.kubeconfig_output_path}" -Encoding UTF8
|
||||
EOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ variable "ssh_private_key_path" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ssh_password" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "k3s_version" {
|
||||
type = string
|
||||
description = "Version k3s a epingler pour un deploiement reproductible (ex: v1.31.5+k3s1). Voir https://github.com/k3s-io/k3s/releases."
|
||||
|
||||
@@ -2,9 +2,9 @@ terraform {
|
||||
required_version = ">= 1.7"
|
||||
|
||||
required_providers {
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.2"
|
||||
provider = {
|
||||
source = "hashicorp/archive"
|
||||
version = "~> 2.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pourquoi : la machine porte trois environnements, chacun un clone du dépôt, un `.env` et un
|
||||
# projet Compose (ADR 0009, ADR 0017). Ce script prépare la machine et les trois dossiers sans
|
||||
# rien démarrer : construction des images et démarrage restent à l'opérateur, puis au runner.
|
||||
# Piège : lancé en root, git refuse un clone déjà chowné au runner (propriété douteuse). D'où
|
||||
# `safe.directory` passé en ligne de commande, seule portée où git l'accepte - preparer().
|
||||
# Pourquoi : la machine porte deux environnements, chacun un clone du dépôt, un `.env` et un
|
||||
# projet Compose (ADR 0009). Ce script prépare la machine et les deux dossiers sans rien
|
||||
# démarrer : construction des images et démarrage restent à l'opérateur, puis au runner GitHub.
|
||||
# Rejouable : un dossier déjà cloné est réaligné sur sa branche, un `.env` existant n'est jamais
|
||||
# réécrit, un certificat présent n'est jamais régénéré.
|
||||
|
||||
@@ -41,10 +39,9 @@ preparer() {
|
||||
local dossier="$RACINE/$env"
|
||||
|
||||
if [[ -d "$dossier/.git" ]]; then
|
||||
local git=(git -c "safe.directory=$dossier" -C "$dossier")
|
||||
"${git[@]}" fetch --quiet origin "$branche"
|
||||
"${git[@]}" checkout --quiet "$branche"
|
||||
"${git[@]}" reset --quiet --hard "origin/$branche"
|
||||
git -C "$dossier" fetch --quiet origin "$branche"
|
||||
git -C "$dossier" checkout --quiet "$branche"
|
||||
git -C "$dossier" reset --quiet --hard "origin/$branche"
|
||||
else
|
||||
git clone --quiet --branch "$branche" "$DEPOT" "$dossier"
|
||||
fi
|
||||
@@ -96,7 +93,6 @@ mkdir -p "$RACINE"
|
||||
# env branche hôte origine https http pg mailpit airflow
|
||||
preparer prod main enervision.local https://enervision.local 443 80 5433 8025 8080
|
||||
preparer rec dev rec.enervision.local https://rec.enervision.local:8443 8443 127.0.0.1:8081 5434 8026 8082
|
||||
preparer dev dev dev.enervision.local https://dev.enervision.local:9443 9443 127.0.0.1:8083 5435 8027 8084
|
||||
|
||||
if [[ -n "$PROPRIETAIRE" && "$(id -u)" -eq 0 ]]; then
|
||||
chown -R "$PROPRIETAIRE" "$RACINE"
|
||||
@@ -108,12 +104,10 @@ Démarrage, dans chaque dossier : make stack-up, qui applique aussi les migratio
|
||||
Premier administrateur, stack démarrée, dans chaque dossier :
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec backend \\
|
||||
python -m app.cli create-admin --email <adresse>
|
||||
Le runner GitHub Actions (label eni-g3) rejouera le déploiement à chaque push sur dev et main,
|
||||
et déploiera dans dev toute autre branche lancée à la main depuis l'onglet Actions.
|
||||
Le runner GitHub Actions (label eni-g3) rejouera le déploiement à chaque push sur dev et main.
|
||||
L'installer sous le propriétaire de $RACINE, sinon git refuse ces dépôts et le .env en 600 lui
|
||||
échappe : relancer au besoin ce script avec PROPRIETAIRE=<utilisateur du runner>.
|
||||
Données historiques : git ne porte pas data/raw, déposer les fichiers dans chaque dossier avant
|
||||
de déclencher le DAG historical_import.
|
||||
Depuis un poste : ajouter « $ADRESSE enervision.local rec.enervision.local dev.enervision.local »
|
||||
à /etc/hosts.
|
||||
Depuis un poste : ajouter « $ADRESSE enervision.local rec.enervision.local » à /etc/hosts.
|
||||
FIN
|
||||
|
||||
Reference in New Issue
Block a user