Cette journée va faire l’objet d’une évaluation : vous devrez pousser des commits au fur et à mesure de votre avancée, sur un dépôt que vous aurez forké.
Réviser les notions vues depuis le premier cours :
Intégrer différents types de tests dans un pipeline GitLab CI
Aborder un exemple de pipeline plus complexe, au travers d’un “monorepo” frontend + backend
Un dépôt vous est fourni, avec du code backend, frontend, une partie des outils configurés.
Vous allez écrire un pipeline GitLab CI presque complet, intégrant le linting, différents types de tests, etc.
Quelques précisions :
En résumé, Je n’attends pas de vous que vous sachiez absolument tout faire, mais :
- Que vous essayiez,
- Que vous posiez des questions si vous n’y arrivez pas.
On vous donne un dépôt avec un backend
et un
frontend
: https://gitlab.com/bhubert/ipi-cicd-todoapp
Il s’agit d’une application de “todo-list”. Pas très original certes, mais adapté pour réviser les notions de tests…
❗️❗️N’ALLEZ PAS PLUS LOIN (pour l’instant)❗️❗️
Voici à quoi ressemblera le pipeline dans son état final :
L’ordre des stages (étapes) diffère un peu de ce qu’on avait pu voir.
Les étapes de build ne sont plus découpées en deux parties (compilation TypeScript dans une étape, puis build d’une image Docker dans la suivante) : on build directement les images Docker.
La raison à cela est que l’étape de tests end-to-end (test_e2e) nécessite des images Docker.
Dans l’interface de GitLab, le pipeline ressemblera à ceci (ici en cours d’exécution) :
Notez le fait qu’on ait coché “Show dependencies” pour montrer les dépendances entre les jobs.
- Cette vue d’ensemble est accessible, depuis la barre latérale, dans le menu Build > Pipelines puis en cliquant sur l’id d’un pipeline.
- Un autre outil qui pourra vous servir est Build > Pipeline editor. Il vous permettra, en collant le code YAML de votre pipeline, de vérifier qu’il ne comporte pas d’erreur : il est en effet frustrant qu’un pipeline échoue à cause d’une erreur de syntaxe.
Le plan détaillé sera fourni étape par étape.
install
→ Installation des dépendances du
projetlint_backend
→ Exécution d’ESLint dans la
partie backend du dépôtlint_frontend
→ Exécution d’ESLint dans la
partie frontend du dépôttest_backend
→ Exécution des tests backend
(tests d’intégration Express)test_frontend
→ Exécution des tests frontend
(tests de composants React + tests unitaires)build_backend_image
→ Construction de l’image
Docker du backendbuild_frontend_image
→ Construction de
l’image Docker du frontendtest_e2e
→ Exécution des tests end-to-end,
utilisant les images Dockerpush_images
→ Push des images Docker vers un
registry (par ex. Docker Hub)Un “squelette” incomplet de pipeline vous est fourni dans le dépôt, et reproduit ci-dessous.
Il va s’agir de remplir les différentes étapes.
# Définit une image Docker à utiliser pour les jobs,
# si une image n'est pas spécifiée pour un job.
# Pas obligatoire !! On peut définir une image par stage
# default:
# image: node:lts-alpine
# Définit les étapes de pipeline.
# Chaque étape est exécutée dans l'ordre défini.
stages:
- install
- lint_backend
- lint_frontend
# - test_backend
# - test_frontend
# - build_backend_image
# - build_frontend_image
# - test_e2e
# - push_images
# ICI => cache (pour les node_modules)
install:
stage: install
script:
- echo "Installing dependencies with npm..."
- sleep 5
lint_backend:
stage: lint_backend
script:
- echo "Linting backend..."
- sleep 5
needs:
- install
lint_frontend:
stage: lint_frontend
script:
- echo "Linting frontend..."
- sleep 5
needs:
- install
# TODO => ajouter
# - test_backend
# - test_frontend
# - build_backend_image
# - build_frontend_image
# Désactivé pour le moment
# LAISSÉ car il démontre par exemple l'utilisation
# de dépendances multiples, et de services
# test_e2e:
# image: docker:latest
# services:
# - docker:dind
# stage: test_e2e
# script:
# - echo "Running end-to-end tests..."
# - sleep 5
# needs:
# - build_backend_image
# - build_frontend_image
# Désactivé pour le moment
# push_images:
# stage: push_images
# script:
# - echo "Pushing Docker images to registry..."
# - cat toto.txt
# - sleep 5
# needs:
# - test_e2e
Cette étape ne concerne pas vraiment le pipeline CI !
Dans plusieurs dépôts que je vous ai fournis précédemment,
il fallait installer les dépendances séparément dans les
dossiers backend
et frontend
.
Dans ce dépôt, vous n’avez à le faire qu’une seule fois,
à la racine du dépôt. C’est dû à la présence
de la propriété workspaces
dans le
package.json
de la racine. Cela permet de
centraliser tous les node_modules
à la racine du
dépôt.
Une fois placé dans votre dossier
ipi-cicd-todoapp
, vous pouvez lancer
npm install
.
Ouvrez deux fenêtres/onglets de terminal, une sous
backend
, une sous frontend
, et
lancez dans chaque dossier npm run dev
. Cela
lance le script dev
du package.json
.
(si besoin, voir rappels sur
npm dans les annexes).
Le frontend est accessible à l’URL http://localhost:5173.
Si vous le visitez en l’état, vous devriez obtenir :
Message from server: Hello World!
→ cela signifie
que le frontend communique bien avec le backend.Error fetching tasks: Request failed with status code 500
.Si vous examinez la console d’où vous avez lancé le backend, vous en aurez l’explication : l’app a besoin de se connecter à une base de données MySQL
Nous allons tout de suite voir comment adresser cela.
Trois options sont possibles :
L’option Docker est de loin la meilleure, les autres ne doivent être envisagées que si Docker ne marche pas chez vous.
Dans un autre terminal, vous pouvez d’abord pull la dernière version de l’image MySQL :
docker pull mysql:latest
Puis lancez-la comme ceci :
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root_password -e MYSQL_DATABASE=app_todolist_test -e MYSQL_USER=app_todolist_test -e MYSQL_PASSWORD=app_todolist_test mysql:latest
Quelques explications sur les arguments et de
docker run
:
-d
→ daemonize, lancer en tâche
de fond (pour éviter de bloquer la console)
-p 3306:3306
→ établit une correspondance
entre un port sur l’hôte (avant le :
) et un port
sur le conteneur (MySQL écoute par défaut sur 3306). Avec ce
“mapping”, ce sera comme si vous utilisiez un MySQL
“standard”.
--name mysql
→ nommer le conteneur
mysql
- cela sera pratique pour s’y référer, par
ex. avec docker stop
, docker exec
,
etc.
-e VARIABLE=VALEUR
→ passer une variable
d’environnement au conteneur. La doc de l’image
officielle MySQL en mentionne plusieurs, ici nous
utilisons :
MYSQL_ROOT_PASSWORD
→ mot de passe admin de
MySQLMYSQL_DATABASE
, MYSQL_USER
,
MYSQL_PASSWORD
→ respectivement nom d’une base de
données que MySQL va créer, username et password d’un
utilisateur qui aura les droits d’accès à cette base de
donnéesmysql:latest
→ image à partir de laquelle
démarrer le conteneur
Après le lancement, vous pouvez lancer
docker logs mysql -f
pour suivre la procédure de
démarrage, puis interrompre (Ctrl-C) quand vous voyez que la
BDD est prête :
2024-03-28T19:26:03.838165Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.3.0' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
Concerne MySQL installé sur votre machine Windows, macOS ou Linux.
À ne faire de préférence que si vous avez déjà installé - je préfère ne pas avoir à faire du dépannage d’installation 😬.
Lancez soit la console mysql
dans un terminal,
ou MySQL Shell, ou tout autre outil vous permettant d’envoyer
des requêtes à votre BDD.
Vous pouvez coller dans cette console le contenu du fichier
backend/sql/create-db-test.sql
.
Cela créera un utilisateur avec les mêmes paramètres que pour l’option Docker.
Me demander : j’ai créé un certain nombre de comptes sur une VM hébergée chez moi, à usage temporaire, le temps de cette journée.
Les BDD sont déjà créées avec des noms d’utilisateurs et mots de passe uniques, que je ne peux évidemment pas les publier ici !
Une fois votre BDD en route, il vous faut :
backend
,sample.env
en tant que
.env
DB_NAME
,
DB_USER
, DB_PASS
; laisser les
variables DB_HOST
et DB_PORT
vides,
cela concerne ceux à qui j’aurai donné un accès à une BDD
externe.Sinon mettez directement ça dans .env
:
DB_HOST=localhost
DB_NAME=app_todolist_test
DB_USER=app_todolist_test
DB_PASS=app_todolist_test
Exécutez les migrations :
npx db-migrate up -e test
Relancez le backend :
npm run dev
Cette fois-ci, le frontend devrait pouvoir communiquer avec le backend, et le gros message d’erreur en rouge devrait avoir disparu !
Prenez un peu de temps à interagir avec l’application pour voir comment elle fonctionne.
install
du pipelineVous pouvez, ici comme à chaque étape, supprimer les
sleep 5
, qui font office de “remplissage”.
Il va vous falloir modifier la stage install
du pipeline .gitlab-ci.yml
, de façon à installer
les dépendances du projet.
Il y a juste une commande à ajouter dans la partie
script
du pipeline !
Par contre, il y a d’autres modifications à apporter.
D’une part, il faut choisir quelle image va servir à lancer le conteneur qui exécute la stage. Ce choix peut être fait :
image: nom-de-l-image:tag
,image
.install
, comme pour les
4 suivantes, je vous recommande fortement d’utiliser
l’image node:lts-alpine
(Alpine = image Linux
légère, LTS = long term support).D’autre part, il arrive que des stages aient besoin de
fichiers produits par des stages précédentes. Le résultat de
la stage présente est la création d’un dossier
node_modules
. Les 4 stages suivantes auront
besoin de les récupérer.
Vous pouvez référer au support de cours de la 2ème journée pour vous inspirer.
❗️ Avant chaque git add/commit/push votre fichier
.gitlab-ci.yml
, je vous conseille de copier-coller son contenu dans l’éditeur de pipeline, que vous pouvez trouver, depuis la home page de votre dépôt, sous Build > Pipeline Editor dans la barre latérale.Quand vous collez, GitLab “mouline” un peu, puis vous indique si le pipeline est valide, ou s’il comporte des erreurs.
Par contre, pas la peine de sauvegarder le fichier dans l’interface de GitLab : on s’en sert juste pour vérifier, mais on fait la modification localement et on pousse.
lint_backend
Nous avions vu - rapidement - ESLint lors de la première journée. Nous allons revoir son installation et sa configuration, pour le backend.
❗️ Placez-vous dans le dossier
backend
.
Lancez cette commande, qui démarre l’assistant de configuration d’ESLint, en ligne de commande :
npm init @eslint/config
L’assistant va vous poser plusieurs questions, et il est important de bien choisir les réponses !
La dernière option est la meilleure, exception faite de mini-projets à la durée de vie très limitée. Nous détaillerons cela plus loin.
To check syntax only
To check syntax and find problems
❯ To check syntax, find problems, and enforce code style
Garder la première option.
Traditionnellement, les projets Node.js utilisaient CommonJS,
mais ici, nous avons configuré babel, qui permet d’utiliser la
syntaxe import/export
.
Celle-ci commence à être prise en charge nativement par Node.js dans les dernières versions, avec quelques restrictions.
❯ JavaScript modules (import/export)
CommonJS (require/exports)
None of these
Dernière option car nous parlons ici de code backend !!
React
Vue.js
❯ None of these
Valider directement No : pour simplifier, nous sommes restés sur du code JavaScript.
Attention ici : appuyez sur espace pour désélectionner Browser, descendez et rappuyez sur espace pour sélectionner Node.
Browser
✔ Node
Valider la 1ère option.
❯ Use a popular style guide
Answer questions about your style
Que signifie cette question ?
On a le choix entre :
Valider la 1ère option : Airbnb.
❯ Airbnb: https://github.com/airbnb/javascript
Standard: https://github.com/standard/standard
Google: https://github.com/google/eslint-config-google
XO: https://github.com/xojs/eslint-config-xo
La style guide Airbnb est stricte, mais permet de détecter bon nombre d’erreurs subtiles. Elle a également de bons (à mon avis) choix par défaut :
Valider la 1ère option : JavaScript - même si ici, je dirais “peu importe” !
❯ JavaScript
YAML
JSON
Valider le choix par défaut Yes.
Checking peerDependencies of eslint-config-airbnb-base@latest
Local ESLint installation not found.
The config that you've selected requires the following dependencies:
eslint-config-airbnb-base@latest eslint@^7.32.0 || ^8.2.0 eslint-plugin-import@^2.25.2
? Would you like to install them now? › No / Yes
À nouveau le choix par défaut car le dépôt
a été configuré avec npm
.
❯ npm
yarn
pnpm
C’est terminé pour l’assistant de configuration !
Il reste quelques réglages à effectuer, mais vous pouvez
remarquer que dans votre IDE, un fichier
.eslintrc.js
est apparu à la racine du
backend.
Faites git add .eslintrc.js package.json
puis
commitez en indiquant dans votre message “configuration
initiale d’ESLint” (en anglais ou français, peu importe). Ne
poussez pas encore !
D’abord, créez un fichier .eslintignore
sous
backend
, avec ce contenu :
dist
migrations
Cela va ignorer les builds générés dans
dist
par la commande npm run build
(qui appelle Babel). On va ignorer également les
migrations
, produites par l’outil
db-migrate
avec une ancienne syntaxe
JavaScript.
Maintenant, modifiez .eslintrc.js
pour :
node: true
, ajouter
jest: true
, pour qu’ESLint ne remonte pas
d’erreur quand il rencontre les describe
,
it
, beforeEach
, etc. de Jestextends: 'airbnb-base'
par
extends: ['airbnb-base', 'prettier']
; cela
permettra à Prettier et ESLint de “cohabiter pacifiquement”.
Sinon, quand on formate son code avec Prettier, ESLint peut se
plaindre, car certaines règles de Prettier peuvent contredire
celles de la style guide choisie précédemment (ici
Airbnb). Ici, on met 'prettier'
en dernier, pour
que les règles de Prettier prennent le pas sur certaines de la
style guide.Au final, votre config doit ressembler à ça :
.exports = {
moduleenv: {
es2021: true,
node: true,
jest: true,
,
}extends: ["airbnb-base", "prettier"],
overrides: [
{env: {
node: true,
,
}files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
,
},
},
]parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
,
}rules: {},
; }
L’ajout qu’on a fait à extends
implique
d’avoir un paquet correspondant installé. Lancez :
npm i -D eslint-config-prettier
Après cette modification, ESLint va utiliser les règles des
modules eslint-config-airbnb-base
et
eslint-config-prettier
.
Faites git add .eslintrc.js .eslintignore
puis
commitez. Ne poussez pas encore !
Lancer manuellement ESLint se fait avec
npx eslint --ext .js .
. Pour simplifier cela,
ajoutez un script lint
à
package.json
:
"scripts": {
...
"lint": "eslint --ext .js .",
...
}
Testez avec npm run lint
pour voir si ESLint
vous remonte des erreurs !
Ne vous inquiétez pas des lignes avec
warning
en jaune. La convention de codage Airbnb indique qu’il vaut mieux éviter lesconsole.log
. Des modules “loggers” tels quedebug
ouwinston
sont plus appropriés pour afficher des informations dans la console. Mais pour l’instant, on fera sans !
Modifiez l’étape lint_backend
du pipeline pour
linter le code backend.
Attention, dans la partie script
de cette
stage :
backend
À nouveau, vérifiez votre pipeline dans GitLab CI. Puis commitez, et cette fois, poussez !
C’est la fin de cette étape. À noter : il est possible de rendre automatique le linting, avant même de commiter, avec les outils Husky et Lint-Staged. On le fera si on a le temps. Si vous êtes en avance, cherchez comment faire (avec la subtilité : on utilise un “npm workspace”, ce qui aura probablement une incidence).
lint_frontend
Cela va être beaucoup plus rapide que dans l’étape précédente, car ESLint est déjà pré-configuré par l’outil Vite, avec lequel on a initialisé l’application frontend.
Un script lint
est même déjà fourni dans
package.json
!
Il ne vous reste plus qu’à modifier le pipeline, à le vérifier, à le commiter et pousser.
test_backend
Cette étape est dépendante de l’étape
lint_backend
, ce que vous pouvez matérialiser avec la propriéténeeds
.
On ne va pas ici faire de configuration : cela a déjà été fait pour vous.
On va en revanche se focaliser sur les tests, et appliquer les notions de tests unitaires et tests d’intégration vues lors de la 1ère journée consacrée aux tests.
Plus de détails vous sont donnés ci-dessous, mais il vous est proposé d’écrire :
1 test unitaire pour la fonction
castDoneToBoolean
définie dans
backend/src/helpers/cast-done-to-boolean.js
au moins 1 test d’intégration (mais le plus est le mieux), parmi ceux-ci :
DELETE /api/items/:id
(le plus facile).POST /api/items
,PUT /api/items/:id
Même si vous vous sentez confiant,
commencez par vous tenir au test unitaire, et à un
seul test d’intégration ; vous pourrez passer à la
section suivante (Exécution des tests dans la stage
test_backend
), et revenir à l’écriture de tests
plus tard.
Pour exécuter les tests, lancez :
npm test
: lance tous les tests, une
seule foisnpx jest --watchAll
: lance tous les
tests, et reste en attente de modifications pour les relancer
! À ne pas utiliser dans une CI car ça bloque le pipeline
!castDoneToBoolean
Il s’agit de tester que la fonction
castDoneToBoolean
convertit l’attribut
done
d’une tâche de la base de données en
booléen.
La fonction est documentée dans le fichier
backend/src/helpers/cast-done-to-boolean.js
.
Le test est à écrire sous
backend/test/unit/cast-done-to-boolean.test.js
.
Vous pouvez vous inspirer, pour la structure du test et sa relation avec la fonction à tester, d’une fonction qu’on a mise dans la base de code pour faire une démonstration :
backend/src/helpers/add-full-name.js
backend/test/unit/add-full-name.test.js
Si on passe à la fonction castDoneToBoolean
l’objet suivant :
{
"id": 5,
"name": "Écrire un pipeline CI",
"done": 0,
"createdAt": "2024-03-29T04:50:39.340Z",
"updatedAt": "2024-03-29T04:50:39.340Z"
}
On s’attend à ce qu’elle renvoie :
{
"id": 5,
"name": "Écrire un pipeline CI",
"done": false,
"createdAt": "2024-03-29T04:50:39.340Z",
"updatedAt": "2024-03-29T04:50:39.340Z"
}
Tous les tests d’intégration sont à
ajouter à
test/integration/item-endpoints.test.js
DELETE /api/items/:id
- cas succèsPUT /api/items/:id
pour
voir comment on crée une tâcheDELETE
sans données204
(l’opération a réussi mais aucune donnée n’est renvoyée)res.body
soit un objet
vide.POST /api/items
- cas succèsPUT
POST
avec un object
contenant name
.201
avec
un objet de forme similaire à celui renvoyé par la route
PUT
.PUT /api/items/:id
PUT
sans données400
avec
un objet contenant un message d’erreur.Si, après l’exploration des étapes suivantes, vous
souhaitez aller plus loin, vous pouvez écrire un test pour un
endpoint DELETE /api/items/all
visant à supprimer
toutes les tâches de la BDD, puis écrire l’implémentation
correspondante dans app.js
.
test_backend
Pour cette stage, utiliser l’image
node:lts-alpine
Il va maintenant s’agir d’exécuter les tests dans la stage
test_backend
.
C’est moins aisé qu’il n’y paraît ! Un simple
npm test
ne va pas suffire…
Comme les tests sont en majorité des tests d’intégration, il faut nous disposer d’une vraie base de données MySQL.
Ici intervient la notion de services. Vous pouvez consulter cette section de la documentation de GitLab : Use CI/CD > Services > MySQL service.
Les deux premiers blocs de code de la doc peuvent vous inspirer :
D’abord, à l’intérieur de votre stage
test_backend
, ajoutez une propriété
services
.
Votre stage ressemble alors à ceci :
test_backend:
services:
- mysql:latest
Pour la durée de vie de cette stage, on va lancer un serveur MySQL, basé sur l’image Docker officielle. Il va falloir configurer ce serveur avec des variables d’environnement. Si vous avez utilisé Docker pour lancer MySQL sur votre machine, c’est exactement la même chose.
Ajoutez un bloc variables
sous
services
en y définissant les variables :
variables:
MYSQL_ROOT_PASSWORD: VotreMotDePasseRoot
MYSQL_DATABASE: nom_de_la_bdd
MYSQL_USER: nom_utilisateur
MYSQL_PASSWORD: MotDePasseUtilisateur
Les valeurs que vous mettez importent peu : les considérations de sécurité sont ici moindres, puisqu’on est en environnement de test, sur des BDD éphémères.
Vous pouvez ici “hardcoder” les valeurs que vous voulez. Utiliser les variables d’environnement via l’interface de GitLab (Settings > CI/CD > Variables) est une autre option, mais semble plus lourde à mettre en place.
Il faut également ajouter des variables pour
db-migrate
et pour l’app Node dans ce
bloc variables
: les DB_*
telles que
vous les aviez dans votre backend/.env
ou
backend/sample.env
. Vous devrez utiliser ces
valeurs :
DB_HOST
→ mysql
(le service est
un conteneur avec le même nom que l’image)DB_NAME
→ même valeur que
MYSQL_DATABASE
DB_USER
→ même valeur que
MYSQL_USER
DB_PASS
→ même valeur que
MYSQL_PASSWORD
❗️ Il faut également exécuter les migrations avant de lancer les tests…
Donc, dans le script
de la stage, il faut
lancer npx db-migrate up -e test
, avant
de lancer npm test
.
test_frontend
Cette étape est dépendante de l’étape
lint_frontend
, ce que vous pouvez matérialiser avec la propriéténeeds
.
À nouveau, on ne va pas ici faire de configuration.
On va revenir sur les tests de composants - mais pas de panique :
L’app frontend comporte beaucoup de composants.
On a fourni des tests pour votre inspiration :
TodoItem
, dans le fichier
frontend/src/components/__tests__/TodoItem.test.jsx
AppTitle
, dans le fichier
frontend/src/components/__tests__/AppTitle.test.jsx
Vous êtes invités à en écrire deux (dans un premier temps) :
pour le composant TodoListError
- qui
reçoit simplement un message d’erreur et l’affiche en rouge
(c’est le composant qui indiquait “Network Error” avant la
configuration de l’app backend)
pour le composant Footer
:
message
- chaîne de caractères)error
, qui elle-même est un
objet comprenant une propriété
message
)Si et seulement si vous avez suffisamment avancé sur le reste, vous pouvez étoffer la suite de tests :
AddTodo
: cas plus
complexe car met en oeuvre des interactions utilisateurs
simulées, ainsi que le fait le test de
TodoItem
TodoItem
, changer le formatage de la
propriété createdAt
, pour l’afficher à un format
plus lisible, par exemple : 11/07/2024, 11:32
.
Vous pouvez pour cela écrire une fonction utilitaire dans un
fichier séparé, et tester cette fonction.test_frontend
Cette stage est plus simple que son pendant backend : il n’y a qu’à lancer les tests avec la commande habituelle !
build_backend_image
Ici, contrairement aux étapes précédentes, il va falloir utiliser l’image
docker:latest
.
Il va également falloir ajouter un block
services
avec docker:dind
.
La stage va ressembler à ceci (rajouter également
needs
pour qu’elle s’exécute après
test_backend
).
build_backend_image:
stage: build_backend_image
image: docker:latest
services:
- docker:dind
script:
- echo "Build image Docker backend"
Maintenant, que va-t-on mettre dans script
?
De façon évidente, une commande de type
docker build
.
En fait, cette stage est relativement courte :
docker build
,La difficulté réside dans l’écriture du Dockerfile, d’autant qu’il va être un peu différent de ceux qu’on a pu écrire jusqu’ici.
Dans les cours précédents, on avait réparti le build sur deux stages :
node
et compilant
des sources TypeScript avec tsc
, pour obtenir un
dossier dist
contenant des fichiers
.js
.dist
: on faisait un
COPY dist dist
.Cette fois, même si on n’utilise pas TypeScript, on doit
transpiler du code JavaScript moderne - avec la
syntaxe import/export
ES6 - en code JavaScript
exécutable directement par node
.
On va faire cela au travers du script dev
, qui
appelle babel
(outil utilisé également par les
systèmes de build pour React, etc.).
Mais cela va être fait lors du build de l’image Docker.
Pour résumer, le Dockerfile
doit :
se baser sur une image node
→
node:lts-alpine
copier les fichiers :
package.json
babel.config.json
→ pour l’outil
babel
,database.json
→ pour l’outil de migration de
bases de données db-migrate
,src
→ code source de l’app,migrations
→ migrations exécutées par
db-migrate
pour construire la BDDlancer npm run build
enfin, exécuter l’application
Traditionnellement, le lancement d’une application Node en
production se fait via npm start
, qui invoque
node
avec, comme argument, le point d’entrée de
l’application.
Si vous regardez le package.json
, vous pouvez
voir :
build
:
babel -d dist src
va transpiler les
.js
du dossier src
et stocker les
.js
résultants dans dist
.start
:
node dist/index.js
va exécuter le point d’entrée
de l’application, dist/index.js
.Normalement, vous devriez donc avoir une CMD
qui conclut votre Dockerfile
comme ceci :
CMD ["npm", "start"]
Ou, ce qui revient au même :
CMD ["node", "dist/index.js"]
Mais ce n’est pas si simple, car il faut - comme dans la
stage test_backend
avant de lancer les tests -
exécuter les migrations avant de lancer l’app.
Pour ce faire, il y a plusieurs options :
Un exemple pour chaque option :
npm
:{
"scripts": {
"hello-goodbye": "echo Hello && echo Goodbye"
}
}
On pourrait lancer hello-goodbye
à la fin d’un
Dockerfile comme ceci :
CMD npm run hello-goodbye
OU
CMD ["npm", "run", "hello-goodbye"]
Un module comme npm-run-all
permet d’exécuter
plusieurs scripts en parallèle ou en séquentiel - par défaut,
en séquentiel.
{
"scripts": {
"hello": "echo Hello",
"goodbye": "echo Goodbye",
"hello-goodbye": "npm-run-all hello goodbye"
},
"dependencies": {
"npm-run-all": "4.1.5"
}
}
Dans le Dockerfile : même chose que dans l’exemple précédent
build_frontend_image
Dockerfile front
# Stage 1 - Build React app into static files
FROM node:lts-alpine AS builder
WORKDIR /app
COPY package.json vite.config.js index.html ./
RUN npm install
COPY src ./src
RUN npm run build
# Stage 2 - Copy into nginx and serve static files
FROM nginx:stable-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
# Contenu de ce fichier ci-dessous
COPY docker/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Config nginx à stocker sous
frontend/docker/nginx.conf
, et à copier sous
/etc/nginx/nginx.conf
dans le
Dockerfile
# Ref: https://cli.vuejs.org/guide/deployment.html#docker-nginx
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
# Proxy to API backend
location /api {
proxy_pass http://backend:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Serve the React app build
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
# docker-compose.yml
# Docker Compose for full-stack Express+React+MySQL
# Backend image is ipi-recap-back and exposing 5000
# It needs vars DB_HOST, DB_USER, DB_PASS, DB_NAME
# Frontend image is ipi-recap-front and exposing 80 (nginx proxying to backend:5000)
# We'll need 3 containers here: backend, frontend and db
---
version: '3.7'
services:
mysql:
image: mysql:8
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASS}
ports:
- "3306:3306"
networks:
- mynetwork
backend:
image: ipi-recap-back
container_name: backend
environment:
NODE_ENV: ${NODE_ENV}
DB_HOST: mysql
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
DB_NAME: ${DB_NAME}
ports:
- "5000:5000"
networks:
- mynetwork
depends_on:
- mysql
restart: unless-stopped
frontend:
image: ipi-recap-front
container_name: recapfront
ports:
- "8000:80"
networks:
- mynetwork
restart: unless-stopped
networks:
mynetwork:
driver: bridge
Vous pouvez utiliser les mêmes commandes en local que d’habitude pour lancer les tests.
npm run wdio
Explorez des idées de cas de test (modification d’une tâche, suppression, etc).
Demandez moi, si vous vous ennuyez…
Vous pouvez explorer :
npm
Cette section donne des rappels sur npm
,
l’outil de gestion de paquets fourni avec Node.js.
npm
fournit un grand nombre de commandes
(npm --help
pour en avoir un aperçu). Parmi
celles qu’on utilise souvent :
init
permet d’initialiser le fichier
package.json
pour un nouveau projet,
ou, suivi d’un nom de paquet, de configurer
l’installation d’une bibliothèque (exemples :
@eslint/config
, wdio
).
install
package.json
(sous dependencies
et
devDependencies
) : ils sont téléchargés depuis le
registre NPM, décompressés et stockés sous
node_modules
.uninstall
est son inverse.
En outre, npm
permet
d’exécuter des scripts
qu’on peut exécuter dans
un répertoire contenant un fichier package.json
.
Prenons l’exemple de ce package.json
minimaliste
:
{
"scripts": {
"echo": "echo This is a message",
"start": "node index.js"
}
}
npm run <nom script>
-
npm run echo
et npm run start
fonctionneront.start
, test
- on peut lancer plus
simplement npm start
ou
npm test
.