TP2 – Git avancé 2/2 GitLab CI/CD, Tests et releases automatisés, Intégration et déploiement continu
Dans ce second (et dernier) TP portant sur Git, nous allons étudier les outils de CI/CD (continuous integration/continuous delivery) proposés par GitLab. Globalement, il s’agit d’automatiser différentes tâches qui seraient fastidieuses (et chronophages) à faire “à la main”. Par exemple : tester l’intégration de différents modules de code, construire et mettre à disposition l’exécutable de l’application, déployer un site web en production, etc.
La plupart de ces outils sont aussi disponibles sur les autres plateformes comme GitHub, Bitbucket, etc (avec leurs propres spécificités).
Découverte de GitLab CI/CD et des pipelines
GitLab propose un outil appelé GitLab CI/CD (pour continuous integration/continuous delivery), disponible dans chaque dépôt. Cet outil permet notamment de détecter quand un événement survient sur un dépôt (par exemple, un push sur une branche spécifique…) et de déclencher automatiquement des scripts (appelés jobs) sur différents conteneurs dockers (machines virtuelles) contenant le code du projet. Tous ces jobs sont regroupés dans diverses étapes (appelées stages) et s’exécutent de manière ordonnée ou en parallèle à travers un pipeline.
Les jobs vont donc vous permettre de charger votre projet sur un système Linux virtuel pré-configuré de votre choix (par exemple, un conteneur qui contient java et Maven) et de réaliser des actions dessus (compilation, tests, publication d’un exécutable, et bien d’autres)…
Nous le verrons par la suite, il est possible de conditionner le déclenchement des jobs. Quand un événement survient sur le dépôt (push, création de tag, etc) le pipeline exécute tous les jobs concernés. On peut suivre la progression des tâches en direct et, en cas d’échec (par exemple, un test ne passe pas, le programme ne compile pas…) GitLab prévient en ligne qu’il y a eu des erreurs, avec les détails (il est possible de consulter les logs de la machine virtuelle exécutant chaque job).
Avec ce système, GitLab pourrait détecter automatiquement s’il y a des problèmes. Par exemple, lorsque deux branches sont fusionnées avec succès, mais que le code ne compile plus ou ne passe pas les tests.
Au-delà de ça, les jobs peuvent aussi servir à automatiser certaines tâches comme le déploiement d’un site web (ou autre), la mise en ligne de la documentation, etc.
Le fichier .gitlab-ci.yml
Le pipeline (et les différents jobs) d’un dépôt est défini à travers d’un fichier .gitlab-ci.yml
(au format YAML
) qui se place à la racine du dépôt.
Voici l’allure générale d’un tel fichier et des options courantes qu’on peut utiliser :
stages:
- stage1
- stage2
job1:
stage: stage1
image: nom_image_docker
script:
- ...
- ...
artifacts:
paths:
- ...
dependencies:
- ...
rules:
- if: ...
job2:
stage: stage1
image: nom_image_docker
...
job3:
stage: stage2
image: nom_image_docker
...
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Comme vous pouvez le constater, le fichier de configuration du pipeline commence par définir des stages. Les différents stages s’exécutent dans l’ordre, les uns après les autres. Un job est lié à un stage. Tous les jobs d’un stage s’exécutent en parallèle. Dans l’exemple ci-dessus, il y a deux stages et trois jobs. Les deux premiers jobs sont liés au stage1
et s’exécutent donc en parallèle. Le troisième job appartient au stage2
et ne s’exécutera que quand stage1
sera terminé, donc quand job1
et job2
seront terminés. Si un job d’un stage échoue, le stage suivant ne sera pas réalisé.
Revenons maintenant sur la configuration d’un job
et intéressons-nous à ses options (certaines sont obligatoires, d’autres non) :
-
stage
(optionnel, mais très fortement recommandé) : définit dans quel stage s’exécute le job. Si on ne met rien, il est placé dans un stage par défaut. -
image
(quasiment obligatoire tout le temps) : spécifie quelle est l’image docker utilisée par ce job. Il existe énormément d’images, on doit cibler en fonction des besoins du job. Par exemple, si on doit tester un programmejava
(qui tourne sur la version 21), on peut utiliser l’imagemaven:3.9.5-eclipse-temurin-21
qui contient java 21 et Maven. On peut éventuellement spécifier une image par défaut qui sera utilisée par chaque job (dans des conteneurs différents), mais de manière générale, on spécifie par job. Si on ne précise rien, une image par défaut sera utilisée. -
script
(obligatoire) : c’est la partie la plus importante. Il s’agit d’une liste de commandes (comme si vous étiez dans un terminal) qui vont s’exécuter dans la machine virtuelle. Lorsque le job démarre et que le conteneur est initialisé, les sources du projet sont chargées et vous vous trouvez directement à sa racine. Vous pouvez donc directement interagir avec le projet. Si toutes les commandes s’exécutent sans problème, le job réussit, sinon il échoue. -
artifacts
(optionnel) : lorsqu’un job démarre, il est isolé dans un conteneur et contient seulement le code du projet. Tous les fichiers potentiellement créés par d’autres jobs ne sont donc pas accessibles par défaut. Cependant, il arrive parfois qu’un job ait besoin de fichiers créés lors d’un job/stage précédent pour s’exécuter. La directiveartifacts
permet de spécifier quels fichiers seront sauvegardés et transmis aux jobs d’un prochain stage. Attention toutefois, les jobs d’un même stage s’exécutent en parallèle et ne peuvent donc pas se transmettre de fichier de cette manière (par défaut, mais c’est éventuellement possible avec une configuration avancée). Ce sont donc seulement les jobs des stages suivants qui auront accès aux fichiers sauvegardés par les stages précédents. On peut également se servir de cette directive pour sauvegarder et exposer des fichiers importants lors de la publication de releases (nous y reviendrons plus tard). On pourrait donc traduire le terme artifacts par “fichiers exportés” (depuis un job). -
dependencies
(optionnel) : permet d’indiquer que ce job a besoin des artifacts (fichiers exportés) d’un certainjob
(d’un stage précédent). Cela n’est pas obligatoire de la préciser si le job correspondant intervient dans le stage qui suit celui qui a créé lesartifacts
(les artifacts seront transmis automatiquement). -
rules
(optionnel) : permet de spécifier des conditions pour que le job se déclenche. Par exemple,job3
ne se déclenche qu’en cas depush
sur la branche par défaut du dépôt (main
oumaster
par exemple). Si rien n’est spécifié (comme pourjob1
etjob2
) le job s’exécute lors de chaque événement (par exemple, en cas de push sur n’importe quelle branche, la création d’un tag, etc). On peut aller dans le détail et écrire des conditions complexes, et même des conditions pour spécifier quand le job ne s’exécute pas, etc. Dans l’exemple dejob3
on aurait aussi pu spécifier une branche spécifique (par exemple'$CI_COMMIT_BRANCH == development'
) ou bien$CI_COMMIT_BRANCH
pour n’autoriser le job à s’exécuter seulement si on a un push de commits (pas de création de tags ou autre). On notera qu’unmerge
d’une branche résultera en unpush
à un moment donné, dans la branche qui intègre les changements de la branche fusionnée.
Il existe beaucoup d’autres options que nous n’aborderons pas dans ce TP, mais que vous pouvez retrouver ici.
GitLab fournit également diverses variables prédéfinies accessibles dans tous les jobs. Ces variables permettent de récupérer et exploiter des informations sur l’événement ayant déclenché le pipeline (message de commit, nom du tag, nom de la branche, etc). Par exemple $CI_COMMIT_BRANCH
contient le nom de la branche sur laquelle est effectué le push, $CI_DEFAULT_BRANCH
contient le nom de la branche par défaut (main, master) ou autre, etc. Ces variables sont préfixées d’un $
. On peut retrouver la liste des variables prédéfinies sur cette page.
Exemple
Voici un exemple un peu plus concret :
stages:
- test
- deploy
test:
stage: test
image: php:8.4
script:
- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \
php composer-setup.php && \
php -r "unlink('composer-setup.php');" && \
mv composer.phar /usr/local/bin/composer
- composer install
- echo "Lancement des tests unitaires"
- php -d xdebug.mode=coverage ./vendor/bin/phpunit
deploy:
stage: deploy
image: alpine:latest
script:
- rm -rf .git
- rm .gitlab-ci.yml
- commande pour uploader les fichier vers un serveur (ftp par exemple...)
- connexion ssh au serveur pour exécuter divers commandes de déploiement...
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Ce pipeline contient deux stages qui contiennent chacun un job. Comme il n’y a qu’un job par stage, on préfère nommer chaque job comme le stage dans lequel il intervient (mais c’est n’est pas obligatoire, bien entendu).
Le premier job test
:
- S’exécute sur l’image
php:8.4
(machine virtuelle qui contient la version 8.4 de php). - S’exécute dans tous les cas (pas de conditions).
- Installe le logiciel
composer
puis installe les dépendances du projet. - Un message est affiché en console puis les tests unitaires sont exécutés.
Le deuxième job deploy
:
- S’exécute seulement si un push est réalisé sur la branche par défaut (principale) du dépôt.
- S’exécute après le stage
test
donc après le job du même nom. - Ne s’exécute pas si le job
test
échoue. - S’exécute sur l’image
alpine:latest
(machine virtuelle simple). - Supprime divers fichiers non utiles au déploiement
- Exécute une commande pour uploader les fichiers du projet vers un dépôt distant…
- Se connecte en ssh à un serveur pour exécuter diverses commandes supplémentaires de déploiement…
Premiers pas
Vous allez maintenant créer votre premier pipeline (très simple) sur le projet d’éditeur de texte du TP précédent. Normalement, vous aviez travaillé à deux sur le même projet à la fin de ce TP, donc techniquement, un des deux membres du binôme a un projet plus avancé. Ici, vous travaillerez seul sur votre propre projet, mais ce n’est pas grave s’il manque les dernières fonctionnalités des exercices réalisés en binôme (si c’est le cas et que cela vous gêne vraiment, vous pouvez faire un fork du dépôt de votre collègue…).
-
En local, ouvrez votre projet d’éditeur de texte puis placez-vous sur la branche
development
et assurez-vous d’être à jour avec le dépôt distant (git pull origin
). -
À la racine du dépôt, créez un fichier
.gitlab-ci.yml
. -
À l’aide de ce fichier, créez le pipeline qui exécute un stage nommé
hello
qui contient un seul job (du nom que vous voulez) qui s’exécute sur l’imagealpine:latest
et affiche un message (de votre choix) en console. Ce job doit seulement s’exécuter sur la branchedevelopment
. -
Créez un commit pour ajouter ce fichier puis poussez-le sur le dépôt distant.
-
Rendez-vous sur la page de votre projet sur GitLab https://gitlabinfo.iutmontp.univ-montp2.fr/qualite-de-developpement-semestre-3/editeur-de-texte/etu/votrelogin/editeur-de-texte puis, à gauche, dans le menu
Build
→Pipelines
. -
Dans ce nouveau menu, vous devriez voir votre pipeline en train de s’exécuter (soit en cours, soit terminée selon votre vitesse et les performances du serveur). Si le pipeline a échoué, c’est que vous vous êtes trompés à un endroit, il faudra alors corriger et recommencer (nouveau commit, nouveau push). Si tout est bon (en attente ou terminé), cliquez sur le status du pipeline pour ouvrir une nouvelle fenêtre.
-
Dans la nouvelle fenêtre, vous pouvez observer chaque job du pipeline (un seul dans votre cas). Cliquez sur votre job afin d’ouvrir les logs de la machine virtuelle, et vérifiez que votre message s’affiche bien à la fin de l’exécution.
-
En local, créez et déplacez-vous vers une nouvelle branche
test_rapide
depuis la branchedevelopment
. Ajoutez ensuite un fichier bidon (par exemple, un fichier texte vide) puis faites un commit et poussez la nouvelle branche. Rendez-vous dans le menu des pipelines et observez que (normalement) rien ne se produit. Si toutefois un pipeline s’exécute quand même, cela signifie que vous n’avez pas bien rédigé la condition de déclenchement sur le job de votre pipeline ! Si tel est le cas, revenez surdevelopment
, supprimeztest_rapide
, corrigez, poussez le fichier de configuration et recommencez. -
Revenez sur la branche
development
puis supprimez la branchetest_rapide
(en local et à distance).
Automatisation des tests sur GitLab
Vous allez maintenant améliorer votre pipeline afin de lui permettre de vérifier que votre programme compile bien puis pour qu’elle exécute les tests unitaires sur votre application. Mais tout d’abord… il vous faut des tests unitaires ! (comment, vous n’avez pas écrit de tests unitaires depuis le début ?!)
-
Si ce n’est pas déjà fait, placez-vous dans votre branche
development
. -
Si le dossier de tests
src/test/java
n’existe pas encore (car vous n’avez écrit aucun test), alors il faudra le créer. Depuis IntelliJ le plus simple est de générer une classe de test, ce qui va provoquer la création du répértoire de tests : ouvrir la classeDocument
→ clic droit sur la déclaration de la classepublic class Document
→ Generate → Test… . Après avoir donné le nom à votre classe de test, disonsDocumentTest
, le dossiersrc/test/java
la contenant va être généré. De plus,DocumentTest
va être placée dans le même paquetage que la classe testéeDocument
(d’après la convention standard). Si vous voulez créer le répertoire de tests avant d’y ajouter les classes de tests, alors c’est un peu plus lent : cliquer droit sur le répertoiresrc
→ New → Directory → Choisisseztest/java
→ Créez un paquetagefr.iut.editeur.document
danssrc/test/
→ à l’intérieur, créez la classeDocumentTest
.Par convention, la couleur verte indique qu’il s’agit du répertoire contenant le code source des tests.
-
Écrire quelques méthodes pour tester les méthodes de la classe
Document
. Si vous ne vous rappelez plus comment faire, jetez un œil à vos cours de Dev-Objets de l’an dernier ! Voici un squelette de cette classe :
package fr.iut.editeur.document;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class DocumentTest {
@Test
public void testMethode() {
assertXXX(...)
}
}
- Pour exécuter les tests, IntelliJ vous permet de le faire simplement, en cliquant sur le bouton de lancement, à côté de la déclaration de la classe.
Maintenant que vous avez des tests unitaires prêts et fonctionnels, nous allons faire en sorte que GitLab vérifie que votre programme compile bien puis exécute les tests après n’importe quelle mise à jour sur le dépôt.
Dans votre pipeline, vous allez diviser le travail en deux stages et deux jobs :
-
Un premier stage (associé à un job) qui teste la compilation.
-
Un deuxième stage (associé à un autre job, s’exécutant après le stage de compilation) qui lance les tests unitaires.
Pour cela, vous aurez besoin des ressources suivantes :
-
L’image
maven:3.9.5-eclipse-temurin-21
qui permet d’avoir un environnement avec java (21) et Maven (pour avoir la commandemvn
). -
La commande
mvn compile
(qui permet de compiler un projet) et la commandemvn test
(exécution de tests unitaires).
Cela peut sembler lourd de séparer cela en deux stages/jobs, mais cela nous permet de former un enchaînement logique bien divisé : si nous avions tout regroupé dans un seul job, en cas d’échec, il aurait été plus difficile de dire ce qui ne va pas (compilation, tests, les deux ?). Ici, on a l’information immédiatement grâce aux noms des stages/jobs. De plus, avec cette organisation, si le stage de compilation ne passe pas, le stage des tests unitaires n’est pas réalisé.
-
Dans votre fichier
.gitlab-ci.yml
, supprimez ce que vous aviez fait lors de l’exercice précédent puis définissez les deux stages et les deux jobs demandés. Pour rappel, ils doivent s’exécuter dans tous les cas, donc il n’y a pas de conditions à préciser. Le premier job teste la compilation, le deuxième lance les tests unitaires. -
Faites un
commit
et unpush
sur votre branchedevelopment
. SurGitLab
, rendez-vous sur votre dépôt et retournez dans l’onglet des pipelines. Vous devriez voir votre pipeline en cours d’exécution. Cliquez sur son statut pour ouvrir les détails puis inspectez chaquejob
. Normalement, tout devrait passer. -
Maintenant, faites en sorte de modifier légèrement votre code pour qu’il ne compile plus. Faites un commit et un push puis vérifiez que le premier job de votre pipeline échoue bien et que le second n’est pas exécuté. Remettez votre code en ordre.
-
Pour finir, modifiez légèrement la classe
Document
pour qu’un ou plusieurs tests ne passent plus. Attention, le programme doit compiler ! Faites un commit et un push puis vérifiez que le premier job de votre pipeline passe, mais pas le second. Enfin, remettez tout en ordre.
Remarque : Dans cet exemple, l’image utilisée pour les deux stages est la même. Ainsi, pour éviter la duplication dans le fichier .gitlab-ci.yml
, on aurait pu indiquer image: maven:3.9.5-eclipse-temurin-21
sur la première ligne de ce fichier, avant le mot-clé stages
. Cela signifie que c’est l’image par défaut pour tous les stages et donc on peut omettre la déclaration de l’image dans chacun des jobs.
Automatisation des releases
Releases et tags
GitLab permet de publier des releases de notre application, c’est-à-dire publier une version fonctionnelle du logiciel dans un espace de notre dépôt en ligne, avec les exécutables et les ressources nécessaires.
Sur Git il est possible d’associer un commit à un tag. Un tag est une étiquette nommée. Généralement, on associe une release
à un tag. Il est aussi possible de faire un checkout
directement sur un tag !
Par convention, les tags s’utilisent donc pour indiquer la version d’un programme sous la forme vX.Y.Z
. Ainsi, par exemple v0.0.1
, v1.0.0
, etc. X
désigne les mises à jour majeures (le logiciel change quasi-complétement), Y
les mises à jour intermédiaires (ajout de nouvelles fonctionnalités, par exemple après un sprint) et Z
les mises à jour mineures (fixes de bugs par exemple).
Pour créer un tag, on utilise simplement la commande :
git tag vX.Y.Z
Cela aura pour effet d’associer le dernier commit à ce tag. Pour indiquer au dépôt distant que le tag a été créé, il faut le pousser. Par exemple :
git push origin v0.0.1
Pour supprimer un tag en local, on utilise l’option -d
avec la commande tag
:
git tag -d vX.Y.Z
Et pour le supprimer sur le dépôt distant :
git push -d origin v0.0.1
Release automatique de l’application d’édition de texte
Nous allons faire en sorte que, dès qu’un tag est push sur le dépôt distant, le code de l’application soit build en un exécutable java .jar
et qu’une release contenant notre exécutable soit publiée. Ainsi, dès qu’une nouvelle version est prête, il suffit de créer un tag et GitLab se charge alors du reste.
Dans une release, il est possible d’inclure des liens vers des fichiers. Par défaut, le code source de l’application est mis à disposition. Dans notre cas, nous allons seulement ajouter un lien de téléchargement vers l’exécutable .jar
de l’application, mais dans l’absolu, il est tout à fait possible d’inclure d’autres fichiers.
Nous allons répartir le travail en deux stages/jobs :
- Premier job : construire l’exécutable de l’application et l’exporter pour le prochain stage :
- Le script lance la construction de l’exécutable grâce à une commande
maven
, tout en précisant la version de l’application (qu’on obtient grâce au nom du tag). - Il déplace ensuite l’exécutable produit dans le dossier courant.
- Enfin, on utilise le système d’artifacts pour exporter le fichier
jar
vers le prochain stage.
- Le script lance la construction de l’exécutable grâce à une commande
- Deuxième job : publier une release contenant l’exécutable :
- On utilise le système d’artifacts pour exporter encore une fois le fichier
jar
(qu’on a reçu du précédent stage/job) de manière définitive (pour qu’il ne soit pas supprimé à l’issue de l’exécution du pipeline). - On utilise la directive
release
(que nous allons bientôt voir) pour publier la release en y attachant le lien pointant sur l’artifact correspondant à notre exécutable.jar
.
- On utilise le système d’artifacts pour exporter encore une fois le fichier
Pour pouvoir mettre tout cela en place, nous allons avoir besoin de nous attarder sur différentes options qu’il est possible de définir dans les jobs
:
stages:
- exemple1
exemple1:
stage: exemple1
image: ...
variables:
NOM_VAR_1: valeur
NOM_VAR_2: bis_$NOM_VAR_1 #contient bis_valeur
script:
- commande1 $NOM_VAR_1
- commande2 $NOM_VAR_2
- etc...
artifacts:
paths:
- "chemin/fichier1.xxx"
- "chemin/fichier2.xxx"
- "chemin/dossier1/"
expire_in: ...
rules:
- if: '$CI_COMMIT_TAG'
Expliquons tout cela :
-
La section
variables
permet de définir des variables accessibles dans le job. La variable est ensuite utilisable sous le nom$NOM_VAR_1
(et on peut l’utiliser n’importe où dans le job). Cela nous sera utile par la suite pour stocker le nom de la version (sans lev
) ou bien le nom complet de l’exécutablejar
(que nous allons utiliser plusieurs fois). -
Dans la section
paths
deartifacts
, on précise les chemins (relatifs ou absolu) des fichiers ou dossiers que l’on souhaite exporter (à noter que l’ajout d’un dossier est récursif). Enfin, on peut préciser une durée de vie de ces fichiers avec la sectionexpires_in
. Si on ne précise pas d’unité de temps, cela sera en secondes, sinon on peut spécifier par exemplesecs
,mins
,hrs
(abréviations de secondes minutes et heures, mais il y a plein d’autres formats acceptés). Par exemple, on peut spécifier10 mins
. On peut également précisernever
pour garder ces fichiers exportés pour toujours. -
Enfin, la condition
if: '$CI_COMMIT_TAG'
permet de faire en sorte que le job ne s’exécuter que si un tag est poussé sur le dépôt distant.
Nous allons procéder par étape et vous allez d’abord commencer par implémenter le job de build
sans la release. Nous pourrons ainsi voir si l’exécutable est bien produit et exporté. Voici ce dont vous aurez besoin :
-
Pour ce job (et ce stage) nous allons utiliser encore une fois l’image
maven:3.9.5-eclipse-temurin-21
. -
La variable prédéfinie
$CI_COMMIT_TAG
permet d’obtenir le nom du tag poussé sur le dépôt. - Il est judicieux de définir deux variables pour ce job :
- Une variable contenant la version correspondant au tag sans le
v
. Pour cela, on peut utiliser la syntaxe${CI_COMMIT_TAG#v}
pour enlever lev
. - Une variable contenant le nom de l’exécutable :
EditeurDeTexte-$VERSION.jar
(où$VERSION
correspondant au nom de la variable contenant la version…). - Donc, par exemple, si le tag poussé est
v1.0.2
, on obtient les variables1.0.2
etEditeurDeTexte-1.0.2.jar
.
- Une variable contenant la version correspondant au tag sans le
-
On peut utiliser les variables partout dans le job (commande du
script
, dans lepath
desartifacts
, etc). -
Pour le script, on peut utiliser trois commandes :
-
mvn versions:set -DnewVersion=X.Y.Z -DgenerateBackupPoms=false
qui permet de définir la version de l’exécutablejar
produit (en remplaçantX.Y.Z
par cette version, bien entendu). -
mvn package
pour produire l’exécutable dans le dossiertarget/NomExecutable-X.Y.Z.jar
. -
mv target/NomExecutable-X.Y.Z.jar ./
pour déplacer le.jar
dans le dossier courant (plus facile pour la suite) en remplaçant par le nom adéquat, bien entendu.
-
- Concernant les
artifacts
, on exporte seulement le.jar
qui se trouve dans le dossier courant (donc, danspaths
, on peut simplement mettre le nom du jar vu qu’il se trouve dans le dossier courant). On peut alors mettre une expiration d’une heure (pour être large). Ce fichier ne sera pas conservé, il a pour but d’être utilisé par le job de release que nous développerons par la suite.
Allez, il est temps de mettre en application !
- Dans votre fichier
.gitlab-ci.yml
, ajoutez un nouveau stagebuild
à la suite et un job associé (avec le même nom) qui :- Produit le
jar
de l’application avec le bon libellé de version. - L’exporte comme
artifact
avec une durée de vie d’une heure. - S’exécute seulement quand un tag est poussé.
- Produit le
-
Faites un
commit
et unpush
sur votre branchedevelopment
. SurGitLab
, rendez-vous sur votre dépôt et retournez dans l’onglet des pipelines. Normalement, les deux premiers jobs doivent bien s’exécuter, mais pas le dernier (car se déclenche seulement quand on pousse untag
). -
Créez un tag nommé
v1.0.0
puis, poussez-le sur le dépôt distant. -
Sur la page de gestion des pipelines, observez le déroulement des jobs, et vérifiez que le job
build
s’exécute bien à la fin. À l’issue de l’exécution, rendez-vous dans le menu accessible via le panneau latéral gaucheBuild
→Artifacts
et vérifiez que votre fichier.jar
est bien accessible en déroulant les fichiers associés au jobbuild
. Vérifiez également que son nom est correct (EditeurDeTexte-1.0.0.jar
). - S’il y a une erreur quelque part, vous devrez bien penser à pousser vos modifications sur le dépôt distant puis à créer et pousser un nouveau tag pour re-tester (ou supprimer celui-ci en local et à distance puis le pousser de nouveau). La version en suffixe de l’exécutable
jar
doit correspondre au tag.
Tout fonctionne jusqu’ici ? Parfait ! Il ne nous reste donc plus qu’une dernière étape : faire en sorte que notre pipeline exporte de manière définitive l’exécutable, crée une release et y attache le lien de téléchargement du fichier.
Voyons comment faire cela :
stages:
- build
- release
#job qui build et exporte l'exécutable dans un artifact
build:
...
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
dependencies:
- build
artifacts:
paths:
- "chemin/fichier1.xxx"
- "chemin/fichier2.xxx"
expire_in: never
script:
- echo "Publication de la release X.Y.Z..."
release:
name: 'Release X.Y.Z'
description: "Version X.Y.Z de l'application"
tag_name: '$CI_COMMIT_TAG$'
ref: '$CI_COMMIT_TAG'
assets:
links:
- name: "fichier1.xxx"
url: "$CI_JOB_URL/artifacts/raw/fichier1.xxx"
- name: "fichier2.xxx"
url: "$CI_JOB_URL/artifacts/raw/fichier2.xxx"
rules:
- if: '$CI_COMMIT_TAG'
Attardons-nous sur tout cela :
-
Afin de publier une release, on utilise l’image
registry.gitlab.com/gitlab-org/release-cli:latest
pour faciliter les choses. -
La section
dependencies
indique lejob
dont on souhaite récupérer les artifacts. -
On réutilise encore la section
artifacts
pour exporter les fichiers qu’on souhaite mettre à disposition dans notre release. Cette-fois, on indiquenever
comme date d’expiration (car on souhaite conserver ces fichiers pour qu’ils puissent être téléchargés). -
On doit obligatoirement inclure une section
script
(même si on ne fait rien de particulier). Ici, on met simple message de log. -
On configure ensuite la
release
. On remplit les différentes sections obligatoiresname
,description
,tag_name
etref
. On peut préciser le numéro de version dans le nom et la description (même si ce n’est pas obligatoire). -
Enfin, on définit la section
links
qui nous permettra d’inclure les différents fichiers qu’on souhaite attacher à la release et le lien de téléchargement. Le lien$CI_JOB_URL/artifacts/raw/
permet de télécharger les artifacts exportés par cejob
(en spécifiant le nom du fichier souhaité à la fin du lien, bien entendu). -
Encore une fois, on exécute le job que si un tag est poussé (grâce à la section
rules
).
Votre but est donc de créer un stage et un job associé qui fait suite au job de build
, récupère ses artifacts (ici, on aura seulement le fichier EditeurDeTexte-X.Y.Z.jar
), l’exporte via les artifacts et publie la release. Voici quelques conseils pour votre prochaine mission :
-
Comme indiqué plus haut, vous devez utiliser l’image
registry.gitlab.com/gitlab-org/release-cli:latest
. -
Redéfinissez les mêmes variables que dans le job
build
(numéro de version sansv
et du nom de l’exécutable) dans ce nouveau job : vous en aurez besoin un peu partout (pourpaths
dansartifacts
, dans les différentes sous-sections derelease
, pour le nom du fichier et le lien de téléchargement, etc). -
N’oubliez pas d’inclure une section
script
avec une commande simple (qui fait un écho ou autre). -
N’oubliez pas d’inclure la section
dependencies
.
- Dans votre fichier
.gitlab-ci.yml
, ajoutez un nouveau stagerelease
à la suite et un job associé (avec le même nom) qui :- Exporte l’exécutable
jar
récupéré du jobbuild
commeartifact
avec une durée de vie infinie (pas d’expiration). - Crée une release avec un lien de téléchargement vers cet exécutable.
- S’exécute seulement quand un tag est poussé.
- Exporte l’exécutable
-
Faites un
commit
et unpush
sur votre branchedevelopment
. -
Créez un tag nommé
v1.1.0
(ou autre, tant que ça respecte le formatvX.Y.Z
) puis, poussez-le sur le dépôt distant. -
Sur la page de gestion des pipelines, observez le déroulement des jobs, et vérifiez que le job
release
s’exécute bien à la fin. -
Enfin, si tout s’est bien passé, rendez-vous dans le menu
Deploy
→Releases
au niveau du panneau latéral gauche. Vous devriez alors voir votre release! Vérifiez que l’exécutable.jar
peut bien être téléchargé. - En local, rendez-vous sur
master
et fusionnez la branchedevelopment
. Poussez les changements sur le dépôt distant (sur la branchemaster
donc).
Félicitations, vous avez automatisé le processus de release de votre application ! Dorénavant, dès qu’un tag est créé et poussé, une release sera créée et publié. On peut souligner le fait qu’il n’est pas nécessairement obligatoire de passer par le système d’artifact pour publier un fichier dans une release. On peut spécifier n’importe quel lien. On pourrait donc imaginer un job qui upload les fichiers désirés sur un serveur externe et on utiliserait alors ces liens dans la release.
Déploiement d’un site web PHP
Pour terminer, nous allons créer un nouveau projet de site web, qui ne contiendra qu’une page simple, et qui sera automatiquement déployé sur le serveur web de l’IUT après chaque push
sur la branche principale du dépôt. Cet exercice est intéressant, car il va nous permettre de voir comment utiliser des informations sensibles (nom d’utilisateur, mot de passe) de manière sécurisée au travers d’un pipeline.
Globalement, on peut déployer le site web avec un seul job simple :
stages:
- deploy
deploy:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache openssh lftp
- rm -rf .git
- rm .gitlab-ci.yml
- mkdir ~/.ssh
- ssh-keyscan -p numero_port_serveur adresse_serveur >> ~/.ssh/known_hosts
- lftp sftp://nom_utilisateur:$mot_de_passe@$adresse_serveur:numero_port_serveur -e "mirror -R -e ./ /chemin/destination ; quit"
Ce job va :
- Installer les logiciels
openssh
etlftp
dans le conteneur exécutant le job. - Supprimer les fichiers inutiles au déploiement (le dossier
.git
et le fichier.gitlab-ci.yml
…). - Créer un dossier
.ssh
(afin de stocker le fichierknown_hosts
). - Exécuter la commande
ssh-keyscan
qui permet de récupérer la clé publique du serveur de destination (désigné paradresse_serveur
) et de la stocker dansknown_hosts
. Sans ça, le programmelftp
ne pourra pas se connecter au serveur ftp et tournera dans le vide. - Enfin, la commande
lftp
permet de se connecter sur le serveur puis d’exécuter la commandemirror
sur celui-ci qui permet de copier les fichiers du dossier courant depuis le conteneur (désigné par./
, donc, les fichiers du projet) vers un chemin de destination dans le serveur ftp (le chemin peut être relatif ou absolu…). - On utilise le protocole
sftp
. - Les options de
mirror
utilisées ici sont :-R
: afin de copier les fichiers récursivement.-e
: supprime les fichiers distants qui ne sont pas présents en local.
- Dans une application réelle (en tout cas pour un site web complet), il faudrait sûrement aussi se connecter en
ssh
pour exécuter d’autres actions (par exemple, installer les dépendances, etc).
Peut-être que vous êtes choqué de voir mot_de_passe
, nom_utilisateur
et peut-être même adresse_serveur
écrits en clair dans ce fichier, et vous avez raison ! Tous les utilisateurs ayant accès au dépôt (même en lecture) peuvent lire le fichier de .gitlab-ci.yml. Il est donc totalement exclu d’y placer des informations sensibles ! Alors comment faire ?
GitLab permet d’associer des variables CI/CD à notre dépôt (que seuls les gestionnaires du dépôt peuvent éditer) et d’y faire référence dans nos jobs. Ainsi, à la lecture, selon le niveau de sécurité associé à la variable, personne ne pourra voir le contenu réel de ces données, mais lors de l’exécution, la bonne valeur sera utilisée. De plus, il est possible de configurer le degré de visibilité de ces variables pour faire en sorte qu’elles ne puissent pas être lues dans les logs ni dans l’interface de gestion après avoir été crées (on peut toutefois les supprimer).
Pour créer une variable CI/CD à partir de la page du dépôt, on se rend dans Settings
→ CI/CD
dans le panneau latéral gauche. Ensuite, il faut se rendre dans la section Variables
puis Project variables
. L’ensemble des variables disponibles sont listées dans la section CI/CD Variables
(vous en avez 0 pour le moment). Pour ajouter une nouvelle variable, il suffit alors d’appuyer sur le bouton Add variable
à droite.
Lorsqu’on crée une variable, on doit préciser :
- Sa visibilité : on choisit Masked and hidden pour un niveau de sécurité maximal.
- Sa clé (
key
) : le nom de la variable - Sa valeur (
value
). - On peut aussi cocher la case Protect variable si on souhaite que la variable ne soit utilisée que dans les jobs qui se déclenchent sur des branches protégées ou lorsqu’on pousse un tag (nous n’utiliserons pas cette option).
Ensuite, pour l’utiliser dans un job, on y fait référence en préfixant sa clé (key
) par un $
. Par exemple :
stages:
- exemple
exemple:
stage: exemple
image: alpine:latest
script:
- connection -u $USER_NAME -p $PASSWORD
Où USER_NAME
et PASSWORD
seraient deux variables CI/CD créées dans le dépôt (avec une visibilité “Masked and hidden”).
Bref, avec tout ça, vous êtes prêt à construire votre pipeline !
Concernant les informations pour se connecter au serveur FTP de l’IUT :
-
Adresse :
ftpinfo.iutmontp.univ-montp2.fr
-
Port :
22
-
username / password : vos identifiants de département.
-
Téléchargez le fichier index.php et placez-le dans un nouveau dossier de projet. Il s’agit d’une simple page web affichant “Hello world”.
-
Initialisez le dépôt Git ce projet en local, puis sur GitLab, créez un nouveau dépôt vierge (privé) dans votre namespace
qualite-de-developpement-semestre-3/etu/votrelogin
et associez-le à votre dépôt local. - Sur votre dépôt GitLab (de ce nouveau projet), créez quatre nouvelles variables CI/CD secrètes :
- FTP_HOST :
ftpinfo.iutmontp.univ-montp2.fr
(adresse du serveur ftp du département informatique de l’IUT) - FTP_PORT : 22 (on pourrait éventuellement ne pas mettre le port dans une variable…mais cela renforce la sécurité)
- FTP_USERNAME et FTP_PASSWORD : votre login et votre mot de passe du département informatique (les mêmes que vous utilisez pour vous connecter à GitLab normalement).
- FTP_HOST :
-
Créez un fichier
.gitlab-ci.yml
à la racine de votre projet. -
Configurez votre pipeline afin d’ajouter un stage (et un job associé)
deploy
qui se connecte au serveur FTP du département informatique de l’IUT puis publie les fichiers du projet dans le dossier./public_html/hello_world_site/
(sur le serveur distant). Inspirez-vous des commandes de script données précédemment. Bien sûr, il faudra remplacer les élémentsnom_utilisateur
,mot_de_passe
,adresse_serveur
, etnumero_port_serveur
par vos variables secrètes… -
Faites en sorte que ce job ne se déclenche que quand on fait un push sur la branche par défaut (il y a un exemple incluant cette condition au début du TP).
-
Faites un commit puis poussez le projet sur le dépôt distant. Suivez le déroulement de l’exécution du pipeline. Si tout se passe bien, alors, le site a été déployé et vous pouvez y accéder sur le serveur web de l’IUT : https://webinfo.iutmontp.univ-montp2.fr/~login/hello_world_site/ (en remplaçant
login
, bien entendu). -
Dans votre dépôt local, ajoutez la ligne de code suivante dans le fichier
index.php
:echo "<p>Nous sommes le <strong>{$date->format('j F Y')}</strong> et il est <strong>{$date->format('H:i')}</strong></p>";
- Poussez cette modification sur le dépôt distant, patientez et vérifiez que votre site a bien été mis à jour à l’issue de l’exécution du pipeline !
Vous pouvez maintenant déployer vos projets web sur leur serveur de destination avec un simple push sur une branche !
Conclusion
À travers ce dernier TP, vous avez donc appris à vous servir de certaines fonctionnalités plus poussées de la plateforme GitLab.
Comme vous l’avez constaté, les techniques de CI/CD
présentent beaucoup d’avantages dans le cadre du développement d’un projet. Dans ce TP, nous avons étudié seulement quelques exemples, mais il est possible de faire bien plus ! Vous êtes donc fortement invité à plus amplement explorer et utiliser ces outils afin d’automatiser certaines tâches de vos futurs projets, notamment dans le cadre de vos SAEs qui seront (pour la plupart) hébergées sur le serveur GitLab du département.