diff --git a/docs/ci.md b/docs/ci.md new file mode 100644 index 00000000..08705a3d --- /dev/null +++ b/docs/ci.md @@ -0,0 +1,89 @@ +### CI/CD — Fonctionnement observé et architecture de déploiement + +Cette documentation décrit le pipeline CI/CD tel qu’il peut être déduit des artefacts présents dans le dépôt : `Dockerfile`, `package.json`, et les manifestes Kubernetes rendus dans `temp.yaml`. Aucune configuration de pipeline explicite n’est présente dans le dépôt (pas de `.gitea/`, `.github/workflows/`, `.gitlab-ci.yml`, `.drone.yml`). Le flux ci-dessous s’appuie donc sur ces éléments pour décrire le fonctionnement attendu. + +### Portée + +- **Build applicatif**: Next.js (Node 19-alpine) avec dépendance privée `le-coffre-resources` via SSH. +- **Image Docker**: construction multi-étapes, publication attendue vers un registre Scaleway Container Registry. +- **Déploiement Kubernetes**: namespace `lecoffre`, intégration Vault Agent pour l’injection d’ENV, `ExternalSecret` pour le secret de pull Docker, `Ingress` TLS via cert-manager, ressources de `Deployment`/`Service`. + +### Chaîne de build + +- **Dépendances** + - `package.json` indique Next.js 14, TypeScript 4.9, ESLint 8.36, etc. + - La dépendance privée `le-coffre-resources` est récupérée depuis `git.4nkweb.com` via SSH (`git+ssh`). + +- **Dockerfile** (multi-étapes, Node 19-alpine) + - Étape `deps`: installation des dépendances avec `npm install` en utilisant BuildKit et le forward d’agent SSH pour accéder au dépôt privé. + - Étape `development`: copie du code, exécution sous un utilisateur non-root, commande par défaut `npm run dev` (pour le développement local). Pour la prod, l’image utilisée en cluster exécute `npm run start` (cf. manifeste). + +- **Build Next.js** + - Script `build`: `NEXT_TELEMETRY_DISABLED=1 next build --no-lint` + - Script `start`: `next start` + - Le lint n’est pas bloquant au build (flag `--no-lint`). + +### Image, registre et version + +- **Image utilisée en cluster**: `rg.fr-par.scw.cloud/lecoffre/front:v0.1.9` (cf. `temp.yaml`). +- **Registre**: Scaleway Container Registry (région `fr-par`). +- **Tagging**: la version d’exemple observée est `v0.1.9`. L’origine du tag (automatique via CI, ou manuel) n’est pas dans le dépôt. + +### Déploiement Kubernetes (extrait de `temp.yaml`) + +- **Namespace**: `lecoffre` +- **ServiceAccount**: `lecoffre-front-sa` avec `Secret` token associé. +- **ExternalSecret**: création de `imagePullSecret` à partir de Vault via `external-secrets.io` en lisant `secret/data/lecoffre-front-stg/config/dockerpullsecret` (clé `.dockerconfigjson`). +- **Deployment**: `apps/v1` nommé `lecoffre-front` avec: + - `image`: `rg.fr-par.scw.cloud/lecoffre/front:v0.1.9` + - `imagePullPolicy`: `Always` + - `resources`: `requests` (cpu 200m, ram 1Gi), `limits` (ram 2Gi) + - **Vault Agent Injector**: annotations pour injecter des variables d’environnement depuis `secret/data/lecoffre-front-stg/config/envs` en exportant chaque paire `clé=valeur` dans `/vault/secrets/envs`. + - **Commande de démarrage**: `['sh','-c', '. /vault/secrets/envs && npm run start']` + +- **Service**: type ClusterIP exposant le port 80 vers le `targetPort` 3000 du conteneur Next.js. + +- **Ingress**: classe `nginx` avec TLS géré par `cert-manager` (ClusterIssuer `letsencrypt-prod`) pour `app.stg.lecoffre.smart-chain.fr`. + +### Flux CI/CD attendu (déduit) + +1. **Checkout + préparation** + - Récupération du code et configuration de l’agent SSH (accès à `git.4nkweb.com`). + +2. **Installation des dépendances** + - `npm install` avec BuildKit (`--mount=type=ssh`) pour la dépendance privée. + +3. **Build applicatif** + - `npm run build` (désactive la télémétrie et le lint bloquant). + +4. **Construction de l’image** + - `docker build` avec BuildKit et forward d’agent SSH. + - Taggage semver (ex. `v0.1.9`) et éventuellement `latest`/environnement (non constaté ici). + +5. **Push au registre** + - `docker push rg.fr-par.scw.cloud/lecoffre/front:`. + +6. **Déploiement Kubernetes** + - Application des manifestes (ou rendu Helm) dans le namespace `lecoffre`. + - Les secrets de pull sont fournis via `ExternalSecret` connecté à Vault. + - Au runtime, Vault Agent injecte les variables d’environnement nécessaires avant `npm run start`. + +### Sécurité et secrets + +- **Build**: le forward d’agent SSH évite d’écrire la clé privée dans l’image. +- **Runtime**: aucune variable sensible n’est stockée dans l’image; elles sont injectées à l’exécution par Vault. +- **Pull de l’image**: la config Docker (`.dockerconfigjson`) est fournie par `ExternalSecret` à partir de Vault. + +### Points à confirmer + +- Outil CI utilisé (Gitea Actions, Woodpecker/Drone, GitLab CI, autre) et fichiers de pipeline hébergés ailleurs. +- Règles de nommage des tags d’image et gouvernance de version (`vX.Y.Z`, tags d’environnement). +- Stratégie de déploiement (Helm chart source exact, commandes d’apply, gestion multi-env: dev/stg/prod). +- Politique de lint/test avant build (actuellement `--no-lint` au build Next.js). + +### Bonnes pratiques recommandées + +- Activer un job de lint et tests unitaires avant build d’image. +- Signer les images (Cosign) et activer des scans SCA/Container. +- Gérer explicitement les tags et le changelog en CI. +- Déployer via Helm chart versionné, avec valeurs par environnement (`values.{env}.yaml`). diff --git a/src/front/Api/Auth/IdNot/index.ts b/src/front/Api/Auth/IdNot/index.ts index a4c3ae7b..94191b27 100644 --- a/src/front/Api/Auth/IdNot/index.ts +++ b/src/front/Api/Auth/IdNot/index.ts @@ -41,10 +41,9 @@ export default class Auth extends BaseApiService { } public async idNotAuth(autorizationCode: string | string[]): Promise<{ idNotUser: any; authToken: string }> { - // const variables = FrontendVariables.getInstance(); + const variables = FrontendVariables.getInstance(); - // TODO: review - const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; + const baseBackUrl = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}:${variables.BACK_API_PORT}${variables.BACK_API_ROOT_URL}${variables.BACK_API_VERSION}`; const url = new URL(`${baseBackUrl}/api/v1/idnot/auth/${autorizationCode}`); try { @@ -56,7 +55,8 @@ export default class Auth extends BaseApiService { } public async getIdNotUser(): Promise<{ success: boolean; data: any }> { - const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; + const variables = FrontendVariables.getInstance(); + const baseBackUrl = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}:${variables.BACK_API_PORT}${variables.BACK_API_ROOT_URL}${variables.BACK_API_VERSION}`; const url = new URL(`${baseBackUrl}/api/v1/idnot/user`); try { @@ -69,7 +69,8 @@ export default class Auth extends BaseApiService { } public async getIdNotOfficeForUser(userId: string): Promise { - const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; + const variables = FrontendVariables.getInstance(); + const baseBackUrl = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}:${variables.BACK_API_PORT}${variables.BACK_API_ROOT_URL}${variables.BACK_API_VERSION}`; const url = new URL(`${baseBackUrl}/api/v1/idnot/user/rattachements`); url.searchParams.set('idNot', userId); @@ -83,7 +84,8 @@ export default class Auth extends BaseApiService { } public async getIdNotUserForOffice(officeId: string): Promise { - const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; + const variables = FrontendVariables.getInstance(); + const baseBackUrl = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}:${variables.BACK_API_PORT}${variables.BACK_API_ROOT_URL}${variables.BACK_API_VERSION}`; const url = new URL(`${baseBackUrl}/api/v1/idnot/office/rattachements`); url.searchParams.set('idNot', officeId); @@ -96,7 +98,8 @@ export default class Auth extends BaseApiService { } public async getUserProcessByIdNot(pairingId: string): Promise<{ success: boolean; data: { processId: string, processData: { [key: string]: any } } }> { - const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; + const variables = FrontendVariables.getInstance(); + const baseBackUrl = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}:${variables.BACK_API_PORT}${variables.BACK_API_ROOT_URL}${variables.BACK_API_VERSION}`; const url = new URL(`${baseBackUrl}/api/v1/process/user`); url.searchParams.set('pairingId', pairingId); @@ -109,7 +112,8 @@ export default class Auth extends BaseApiService { } public async getOfficeProcessByIdNot(): Promise<{ success: boolean; data: { processId: string, processData: { [key: string]: any } } }> { - const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; + const variables = FrontendVariables.getInstance(); + const baseBackUrl = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}:${variables.BACK_API_PORT}${variables.BACK_API_ROOT_URL}${variables.BACK_API_VERSION}`; const url = new URL(`${baseBackUrl}/api/v1/process/office`); try { diff --git a/src/front/Api/LeCoffreApi/Notary/Customers/Customers.ts b/src/front/Api/LeCoffreApi/Notary/Customers/Customers.ts index a36c547b..54ae9ecd 100644 --- a/src/front/Api/LeCoffreApi/Notary/Customers/Customers.ts +++ b/src/front/Api/LeCoffreApi/Notary/Customers/Customers.ts @@ -2,6 +2,7 @@ import { Contact, Customer } from "le-coffre-resources/dist/Notary"; import BaseNotary from "../BaseNotary"; import { ECivility } from "le-coffre-resources/dist/Customer/Contact"; +import { FrontendVariables } from "@Front/Config/VariablesFront"; // TODO Type get query params -> Where + inclue + orderby export interface IGetCustomersparams { @@ -89,8 +90,8 @@ export default class Customers extends BaseNotary { } public async sendReminder(office: any, customer: any): Promise { - // TODO: review - const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; + const variables = FrontendVariables.getInstance(); + const baseBackUrl = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}:${variables.BACK_API_PORT}${variables.BACK_API_ROOT_URL}${variables.BACK_API_VERSION}`; const url = new URL(`${baseBackUrl}/api/send_reminder`); diff --git a/tests/ci.md b/tests/ci.md new file mode 100644 index 00000000..9fb9aa6c --- /dev/null +++ b/tests/ci.md @@ -0,0 +1,31 @@ +### Plan de tests CI/CD + +Ce document liste les scénarios de test pour valider la chaîne CI/CD décrite dans `docs/ci.md`. + +### Pré-requis + +- Accès au registre Scaleway avec droits de push/pull. +- Accès au cluster Kubernetes `lecoffre` et à Vault (lecture des chemins référencés). +- BuildKit activé et agent SSH opérationnel pour l’accès `git.4nkweb.com`. + +### Tests de build + +- Vérifier l’installation des dépendances avec accès SSH aux ressources privées. +- Exécuter `npm run build` et confirmer la génération sans erreurs. + +### Tests d’image Docker + +- Construire l’image avec le forward SSH. +- Valider la taille, les couches, l’utilisateur non-root, et l’exécution `npm run start`. +- Pousser l’image taguée (ex. `vX.Y.Z`) sur `rg.fr-par.scw.cloud/lecoffre/front` et vérifier la présence. + +### Tests Kubernetes + +- Appliquer les manifests/Helm sur un environnement de test. +- Valider la création de l’`ExternalSecret` et du `imagePullSecret`. +- Vérifier que le `Deployment` démarre, que Vault injecte les variables, et que le `Service` et `Ingress` sont fonctionnels. + +### Observabilité et rollback + +- Vérifier les logs d’injection Vault et de l’application. +- Tester un rollback d’image (retag vers version précédente) et vérifier la restauration.