ICT-324

Pierre Ferrari

Références

Identification du module

Définition

Le terme DevOps est un mot-valise anglais formé à partir de “development” (développement) et “operations” (exploitation). Il désigne un ensemble de pratiques visant à automatiser et à industrialiser l’ensemble des processus de production des applications logicielles, depuis la phase de développement jusqu’à la phase de mise en production.

Historique de l’apparition du DevOps

Contexte historique

Au début des années 2000, l’industrie du logiciel connaissait une révolution rapide avec l’adoption généralisée de l’internet et l’évolution des technologies. Cependant, malgré les avancées technologiques, les entreprises étaient confrontées à des défis majeurs dans la livraison de logiciels.

Les défis traditionnels du développement et des opérations

  1. Silotage des équipes : Les équipes de développement (dev) et les équipes des opérations (ops) travaillaient souvent en silos séparés, avec des objectifs et des priorités différents. Les développeurs se concentraient sur la création de nouvelles fonctionnalités, tandis que les opérations se focalisaient sur la stabilité et la maintenance des systèmes.

  2. Longs cycles de développement : Les méthodologies traditionnelles de développement logiciel, comme le modèle en cascade (waterfall), impliquaient des cycles de développement longs. Les changements étaient rarement déployés en production, et chaque déploiement était une opération risquée et complexe.

  3. Problèmes de communication : Le manque de communication et de collaboration entre les équipes de développement et des opérations entraînait souvent des problèmes de déploiement, des erreurs de configuration, et des délais dans la résolution des problèmes.

  4. Environnements incohérents : Les environnements de développement, de test et de production étaient souvent différents, ce qui causait des problèmes de compatibilité et de performance lors du déploiement des applications.

Emergence de nouvelles pratiques

Au milieu des années 2000, plusieurs entreprises ont commencé à adopter des pratiques agiles pour améliorer la flexibilité et la réactivité du développement logiciel. Cependant, les défis liés à la livraison et au déploiement des logiciels persistaient. Il était nécessaire de trouver des moyens pour aligner les objectifs des équipes de développement et des opérations.

Naissance du mouvement DevOps

  1. Agile et Lean : Les méthodologies agiles ont mis l’accent sur des cycles de développement plus courts, des livraisons continues et une collaboration accrue. Les principes Lean, quant à eux, ont introduit des concepts de réduction des gaspillages et d’optimisation des processus.

  2. Culture de collaboration : Les entreprises ont réalisé l’importance de briser les silos organisationnels et de promouvoir une culture de collaboration et de communication entre les équipes. L’idée était de faire en sorte que les développeurs et les opérations travaillent ensemble tout au long du cycle de vie du développement logiciel.

  3. Automatisation et outillage : L’émergence d’outils d’automatisation et de gestion des configurations a permis d’automatiser de nombreuses tâches répétitives et d’améliorer la cohérence des environnements de développement et de production. Des outils comme Puppet, Chef, Ansible, et plus tard Docker et Kubernetes, ont joué un rôle crucial dans cette automatisation.

  4. Livraison continue (CD) : Les pratiques de livraison continue ont permis de déployer des changements de manière plus fréquente et plus fiable, en intégrant des tests automatisés et en facilitant le déploiement en production. Cela a permis de réduire les délais de mise en production et d’améliorer la qualité du logiciel.

Formalisation du terme “DevOps”

Le terme “DevOps” a été popularisé lors de la conférence “Velocity” en 2009, où John Allspaw et Paul Hammond de Flickr ont présenté un discours intitulé 10+ Deploys per Day: Dev and Ops Cooperation at Flickr. Leur présentation a illustré comment la collaboration entre les équipes de développement et des opérations permettait d’améliorer considérablement la fréquence et la fiabilité des déploiements.

Adoption et évolution

Depuis lors, DevOps est devenu un mouvement global adopté par de nombreuses entreprises à travers le monde. Il a évolué pour inclure des pratiques telles que l’infrastructure en tant que code (IaC), le monitoring en continu, et l’intégration de la sécurité dès le début du cycle de développement (DevSecOps).

Conclusion

DevOps est né de la nécessité de surmonter les défis traditionnels du développement et des opérations. En promouvant une culture de collaboration, en automatisant les processus et en adoptant des pratiques de livraison continue, DevOps a transformé la manière dont les logiciels sont développés, déployés et gérés. Aujourd’hui, il est un élément clé pour les entreprises cherchant à innover rapidement et à maintenir une haute qualité de service.

Source : ChatGPT

CI/CD

Ces deux thermes sont souvent associés à DevOps. Ils sont des pratiques clés du mouvement DevOps. C’est pourquoi il est important de les comprendre.

CI

La CI (Continuous Integration) est une pratique de développement logiciel qui consiste à intégrer fréquemment les modifications de code dans un dépôt partagé. Chaque modification est automatiquement testée et validée, ce qui permet de détecter et de corriger les erreurs plus rapidement.

CD

La CD (Continuous Delivery) est une pratique de développement logiciel qui consiste à automatiser le processus de déploiement des applications. Les modifications de code sont automatiquement construites, testées et déployées en production, ce qui permet de réduire les délais de mise en production et d’améliorer la qualité du logiciel.

Outils

Il existe de nombreux outils qui peuvent être utilisés pour mettre en œuvre les pratiques DevOps. Voici quelques-uns des outils les plus populaires :

Source

Vous ne devrez pas tous les connaître et je ne pense pas qu’il existe une personne qui les maîtrise tous. Nous pratiquerons quelqu’uns de ces outils et vous vous adapterez à ceux de votre futur entreprise.

2.1 Automatisation

Connaître des possibilités d’utilisation des outils d’automatisation dans l’environnement de développement (p. ex. linting, build, exécuter, tester, versionner, dépendances/paquets).

Linting

Le linting est une pratique de développement logiciel qui consiste à analyser le code source pour détecter les erreurs de syntaxe, les problèmes de style et les mauvaises pratiques de programmation. Les outils de linting permettent de garantir la cohérence du code, d’améliorer sa lisibilité et de réduire les erreurs.

ESLint

ESLint est un outil de linting pour JavaScript qui permet de détecter et de corriger les erreurs de syntaxe, les problèmes de style et les mauvaises pratiques de programmation. Il est largement utilisé dans l’industrie du logiciel pour améliorer la qualité du code JavaScript.

Un fichier de configuration standard de linting ressemble à ceci :

{
    "rules": {
        "no-unused-vars": "error",
        "no-undef": "error"
    }
}

Dans cet exemple, nous avons défini deux règles de linting : no-unused-vars et no-undef. La règle no-unused-vars signale une erreur si une variable est déclarée mais jamais utilisée, tandis que la règle no-undef signale une erreur si une variable est utilisée sans être déclarée.

Vous trouverez également cette notation :

{
    "rules": {
        "quotes": [ 2, "double" ],
        "no-unused-vars": 1,
        "no-undef": 0
    }
}

Les valeurs 0, 1, 2 correspondent respectivement à off, warn et error.

“off” or 0 - turn the rule off

“warn” or 1 - turn the rule on as a warning (doesn’t affect exit code)

“error” or 2 - turn the rule on as an error (exit code is 1 when triggered)

Bien entendu, il existe de nombreuses autres règles de linting que vous pouvez configurer en fonction de vos besoins. Vous trouverez des exemples de règles de linting de différentes entreprises sur github.

Exercice ESLint

Pré-requis : Node.js doit être installé sur votre machine.

Pour cet exercice, vous allez installer ESLint et créer un fichier de configuration de linting pour un projet JavaScript. Le travail sera réalisé dans l’outil de développement Visual Studio Code.

  1. Créez un nouveau projet JavaScript dans Visual Studio Code. Placez ce projet dans un dossier nommé jseslint.
  2. Créez un fichier index.js dans le dossier du projet et ajoutez-y le code suivant :
console.log('Demo ESLint')
  1. Installez l’extension ESLint dans Visual Studio Code en recherchant “ESLint” dans la barre de recherche des extensions.
  2. Ouvrez le terminal dans Visual Studio Code et installez ESLint en exécutant les commandes suivantes :
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (jseslint) 
version: (1.0.0) 
description: ESLint projet for learning
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: Pierre Ferrari
license: (ISC) 
About to write to /your/path/jseslint/package.json:

{
  "name": "jseslint",
  "version": "1.0.0",
  "description": "ESLint projet for learning",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Pierre Ferrari",
  "license": "ISC"
}


Is this OK? (yes) yes
$ npx eslint --init
You can also run this command directly using 'npm init @eslint/config@latest'.

> jseslint@1.0.0 npx
> create-config

 How would you like to use ESLint? · problems
 What type of modules does your project use? · esm
 Which framework does your project use? · none
 Does your project use TypeScript? · javascript
 Where does your code run? · browser
The config that you’ve selected requires the following dependencies:

eslint@9.x, globals, @eslint/js
 Would you like to install them now? · No / Yes
 Which package manager do you want to use? · npm
☕️Installing...

added 90 packages, and audited 91 packages in 1s

23 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Successfully created /your/path/jseslint/eslint.config.mjs file.
$ tree -L 1
.
├── eslint.config.mjs
├── index.js
├── node_modules
├── package.json
└── package-lock.json

2 directories, 4 files

Déclarez une variable non utilisée dans le fichier index.js et observez comment ESLint signale une erreur. Vous pouvez voir que la variable est soulignée en rouge et si vous placez votre curseur dessus, ESLint affiche un message d’erreur.

'i' is assigned a value but never used. eslint (no-unused-vars)

On peut également voir les erreurs dans la console de Visual Studio Code.

$ npx eslint index.js 

/your/path/jseslint/index.js
  1:5  error  'i' is assigned a value but never used  no-unused-vars

 1 problem (1 error, 0 warnings)

Dans le fichier de configuration package.json ajouter une commande pour lancer ESLint ainsi qu’une commande pour démarrer l’exécution du script index.js.

{
  "name": "jseslint",
  "version": "1.0.0",
  "description": "ESLint projet for learning",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "lint": "eslint \"**/*.js\" --ignore-pattern node_modules/"
  },
  "author": "Pierre Ferrari",
  "license": "ISC",
  "devDependencies": {...}
}

Puis modifier les fichiers :

eslint.config.mjs

import globals from "globals"
import pluginJs from "@eslint/js"


export default [
  { 
    languageOptions: { 
      globals: globals.browser 
    },
    rules: {
      quotes: [ "error", "double" ],
      "no-unused-vars": "error",
      "no-undef": "error",
      "no-implicit-globals": "error",
      "no-const-assign": "error",
      "no-var": "error",
      "prefer-const": "error",
      "array-bracket-spacing": [ "error", "always", { "arraysInArrays": false } ],
      eqeqeq: "error",
      semi: [ "error", "never" ],
      indent: [ "error", 2 ],
    }
  },
  pluginJs.configs.recommended,
]

et index.js sans changer quoi que ce soit dans un premier temps.

var i = 0;
let arr = [[ 1, 2 ], 2, [ 3, 4],4,[5, 6 ], 7, [8, 9], 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
console.log('Demo ESLint')

for(y = 0; y < 10; y++)
  {
  console.log(arr[y]);
}

if (i == 10) 
{
  console.log('i is 10')
}
else
{
  console.log('i is not 10')
}

Ensuite, démarrer le script avec la commande npm start et lancer ESLint avec la commande npm run lint.

Vous pouvez constater que le script fonctionne très bien mais qu’il ne respect pas les règles de linting.

Exercice 1

Corriger les erreurs détectées par ESLint pour les faire disparaître.

Exercice 2

Trouver et ajouter les règles de linting permettant de détecter les erreurs suivantes :

if (condition) {
    // code
} else {
    // code
}

Si vous reprenez le code d’exemple fournit plus haut sans faire de modification, vous devriez obtenir les erreurs suivantes :

$ npx eslint index.js
   1:1   error  Unexpected var, use let or const instead                                       no-var
   1:10  error  Extra semicolon                                                                semi
   2:1   error  This line has a length of 97. Maximum allowed is 80                            ????
   2:5   error  'arr' is never reassigned. Use 'const' instead                                 prefer-const
   2:31  error  A space is required before ']'                                                 array-bracket-spacing
   2:35  error  A space is required after '['                                                  array-bracket-spacing
   2:47  error  A space is required after '['                                                  array-bracket-spacing
   2:52  error  A space is required before ']'                                                 array-bracket-spacing
   2:97  error  A space is required before ']'                                                 array-bracket-spacing
   3:13  error  Strings must use doublequote                                                   quotes
   5:5   error  'y' is not defined                                                             no-undef
   5:12  error  'y' is not defined                                                             no-undef
   5:20  error  'y' is not defined                                                             no-undef
   6:1   error  Expected indentation of 0 tabs but found 2 spaces                              ????
   6:3   error  Opening curly brace does not appear on the same line as controlling statement  ????
   7:1   error  Expected indentation of 1 tab but found 2 spaces                               ????
   7:19  error  'y' is not defined                                                             no-undef
   7:22  error  Extra semicolon                                                                semi
  10:7   error  Expected '===' and instead saw '=='                                            eqeqeq
  11:1   error  Opening curly brace does not appear on the same line as controlling statement  ????
  12:1   error  Expected indentation of 1 tab but found 2 spaces                               ????
  12:15  error  Strings must use doublequote                                                   quotes
  13:1   error  Closing curly brace does not appear on the same line as the subsequent block   ????
  15:1   error  Opening curly brace does not appear on the same line as controlling statement  ????
  16:1   error  Expected indentation of 1 tab but found 2 spaces                               ????
  16:15  error  Strings must use doublequote                                                   quotes

Une fois que vous obtenez toutes les erreurs, et donc que votre fichier de configuration d’ESLint est correct, vous pouvez les corriger pour les faire disparaître.

Unit testing

Les tests unitaires sont une pratique de développement logiciel qui consiste à tester les composants individuels d’une application de manière isolée. Les tests unitaires permettent de vérifier le bon fonctionnement des fonctions, des classes et des modules, et de détecter les erreurs de manière précoce.

Cela suppose également que le code soit organisé de manière à ce que les composants soient facilement testables. C’est pourquoi il est important de suivre les principes de conception logicielle tels que le découplage, l’encapsulation et la modularité.

Chaque langage possède ses propres outils de test unitaire. Voici quelques-uns des outils les plus populaires pour les langages de programmation courants :

Vitest

Vitest est un outil de test unitaire pour JavaScript. Il est pensé pour être compatible avec l’outil Vite. Il est compatible avec le framework de test Jest très largement utilisé dans l’industrie.

Vitest possède des atouts par rapport à Jest :

L’avantage de Vitest réside dans sa console interactive. En effet, le développeur peut voir les résultats des tests en temps réel dans la console de son navigateur. Cela permet de détecter les erreurs plus rapidement et de faciliter le processus de débogage. Ainsi, au moment d’envoyer du code à la partie ops, il est possible de garantir que le code est fonctionnel.

Exercice Vitest

Pré-requis : Node.js doit être installé sur votre machine.

Pour cet exercice, vous allez installer Vitest et configurer un fichier de test unitaire pour un projet JavaScript. Le travail sera réalisé dans l’outil de développement Visual Studio Code.

L’ensemble des instructions pour réaliser cet exercice se trouve dans la vidéo DevTheory.

Regardez la vidéo jusqu’au bout avant de pratiquer car dans la vidéo, il fait un comparatif entre Jest et Vitest et donc, il va faire beaucoup de modification en cours de vidéo

Exercice Vitest en shell

Dans l’optique d’utiliser les tests unitaires dans un pipeline CI/CD, il est important de pouvoir lancer les tests en ligne de commande. On vous demande de réaliser toutes les étapes de mise en place d’un environnement de test permettant de tester un programme réalisé en Javascript.

Le programme sera basique. Il comportera un module mathématique qui contiendra trois fonctions :

math.js

export function add(a, b) {
  // Votre code ici
}

export function sub(a, b) {
  // Votre code ici
}

export function rotate(arr, n) {
  // Votre code ici
}

Les tests unitaires seront réalisés avec Vitest.

math.test.js

import { add, sub, rotate } from './math.js'

test('[add] expect the result of 1 + 2 to be 3', () => {
  expect(add(1, 2)).toBe(3)
})

// les autres tests
...

Les tests seront les suivants :

Le résultat attendu est le suivant

$ npm run vitest

> js-test@1.0.0 vitest
> vitest --globals


 DEV  v2.0.5 /your/path/js-test

  math.test.js (16)
    [add] expect the result of 1 + 2 to be 3
    [add] expect the result of -1 + -0 to be -1
    [add] expect the result of -1.0 + 1.0 to be 0
    [add] expect the result of 1 + 'a' to be an error
    [add] expect the result of 'a' + [1] to be an error
    [sub] expect the result of 1 - 2 to be -1
    [sub] expect the result of -1 - -0 to be -1
    [sub] expect the result of -1.0 - 1.0 to be -2
    [sub] expect the result of 1 + 'a' to be an error
    [sub] expect the result of v + [a] to be an error
    [rotate] expect the result of [1, 2, 3, 4] to be [2, 3, 4, 1]
    [rotate] expect the result of [a, 2, 3, 4] to be an error
    [rotate] expect the result of [1, 2, 3, 4, 5] to be [2, 3, 4, 5, 1]
    [rotate] expect the result of [1] to be [1]
    [rotate] expect the result of [] to be []
    [rotate] expect the result of [[1], 2, 3, 4, 5, 6, 7, 8, 9, 10] to be an error

 Test Files  1 passed (1)
      Tests  16 passed (16)
   Start at  13:52:56
   Duration  239ms (transform 22ms, setup 0ms, collect 17ms, tests 6ms, environment 0ms, prepare 67ms)

Documentation

La documentation est une partie essentielle du développement logiciel. Elle permet de décrire le fonctionnement d’une application, d’une bibliothèque ou d’un module, et de fournir des informations sur son utilisation, ses fonctionnalités et ses API.

Il existe de nombreux outils de documentation pour les différents langages de programmation. Voici quelques-uns des outils les plus populaires :

JSDoc

JSDoc est un outil de documentation pour JavaScript qui permet de générer une documentation à partir des commentaires du code source. Il est largement utilisé dans l’industrie du logiciel pour documenter les bibliothèques et les modules JavaScript.

Un exemple de commentaire JSDoc ressemble à ceci :

/**
 * Add two numbers.
 * @param {number} a - The first number to add.
 * @param {number} b - The second number to add.
 * @returns {number} The sum of the two numbers.
 */
function add(a, b) {
    return a + b;
}

Dans cet exemple, nous avons documenté la fonction add en spécifiant le type des paramètres et de la valeur de retour. Cela permet de générer une documentation claire et précise à partir du code source.

Pour générer la documentation à partir des commentaires JSDoc, il suffit d’exécuter la commande suivante :

$ npx jsdoc src/math.js

ou alors

$ node_modules/.bin/jsdoc src/math.js

Cette commande génère un répertoire out contenant les fichiers HTML de la documentation. Vous pouvez ouvrir le fichier index.html dans un navigateur pour visualiser la documentation.

Exercice JSDoc

Reprenez l’exercice précédent des tests unitaires et ajoutez des commentaires JSDoc au code source du module mathématique. Générez ensuite la documentation à partir des commentaires JSDoc.

La documentation doit contenir les informations suivantes pour chaque fonction :

Le résultat doit se trouver dans un répertoire docs à la racine du projet. La commande pour générer la documentation doit aller chercher la configuration, notamment du répertoire de sortie, dans le fichier jsdoc-conf.js.

Build

Le build est une étape du processus de développement logiciel qui consiste à compiler, à transformer et à regrouper les fichiers source en un exécutable ou en un package distribuable.

Chaque environnement de développement possède ses propres outils de build avec des finalités bien différentes. Si vous écrivez un programme en C, vous utiliserez make ou cmake pour obtenir un fichier binaire exécutable tel qu’un fichier ELF pour Linux ou PE pour Windows. Si vous écrivez un programme en Java, vous utiliserez maven ou gradle pour obtenir un fichier interprétable par la Java vm. Si vous écrivez un programme en Javascript, vous utiliserez webpack, vite, parcel ou rollup pour fournir un répertoire contenant toutes les dépendances nécessaires telles que les fichiers index.html, index.js et style.css.

À cette étape, il est important de définir les particularités de l’environnement de production. C’est une étape qui doit être réalisée en collaboration avec les équipes ops. En effet, les équipes ops doivent être en mesure de déployer l’application en production et comme elles sont responsables de l’infrastructure, elles doivent être impliquées dans le processus de build.

Prenons un exemple simple. Imaginons le développement d’une application web en JavaScript qui est en mesure de faire des conversions monétaires.

Le développeur travail avec les import / export de module Javascript et une api personnelle basée sur l’API fetch en mode asynchrone. Dans son environnement, tout ce passe à merveille. Aucun problème n’est détecté. Cependant, lorsqu’il envoie son code à l’équipe ops, il reçoit en retour un message qui l’informe que son application ne fonctionne pas.

vite v5.2.13 building for production...
5 modules transformed.
x Build failed in 97ms
error during build:
[vite:esbuild-transpile] Transform failed with 2 errors:
assets/index-!~{001}~.js:223:20: ERROR: Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14" + 2 overrides)

C’est normal car l’équipe ops utilise, pour ses tests, un environnement qui ne supporte pas l’utilisation des méthodes asynchrones directement dans le module de premier niveau.

La différence se résume à la simple ligne suivante de configuration dans le fichier vite.config.js :

export default ({
  build: {
    target: 'esnext',
  },
})

Malheureusement, le développeur n’a pas fournit ce fichier de configuration à l’équipe ops et il a ignoré ce fichier dans le dépôt Git pensant qu’il s’agissait d’un fichier de configuration local. Comme l’équipe ops utilise la version présente dans le dépôt Git, elle n’a pas pu déployer l’application.

Inévitablement, les deux équipes seront en conflit pour savoir qui a raison et qui a tort. C’est pourquoi il est important de définir les particularités de l’environnement de production dès le début du projet et de les documenter.

Exercice Build

Pour cet exercice nous utiliseront vitejs pour construire un site web simple basé sur du Javascript.

L’objectif de l’exercice est de réaliser le site web présenté ci-dessous.

Ce site permet de convertir des devises. Il se base sur l’API ExchangeRate. L’API fournit un service gratuit pour obtenir les taux de change entre différentes devises. Dans sa version gratuite, on ne peut pas directement faire la conversion d’une devise en une autre mais on peut passer par le Dollar US car l’API fournit le taux de change entre le Dollar US et les autres devises.

€ → CHF revient à faire € ⇢ $ ⇢ CHF.

Particularités

Grâce à l’outils vitejs vous pourrez réaliser la partie build du projet. Le build fournit un répertoire dist contenant tous les fichiers nécessaires pour le déploiement en production. On peut voir sur la structure ci-dessus que le build à déjà été réalisé.

Contrôle

Une fois votre build réalisé, vous devez vérifier que le site fonctionne correctement. Pour cela, vous devez :

  1. Placer le contenu de votre répertoire dist dans un serveur web local. Par exemple Laragon ou Wamp.
  2. Ouvrir le fichier index.html dans un navigateur via le protocole HTTP.
  3. Vérifier que les listes déroulantes sont bien construites.
  4. Vérifier que les drapeaux sont bien affichés.
  5. Vérifier que la conversion des devises fonctionne correctement.
  6. Vérifier dans la console du navigateur que les appels à l’API se font correctement.
  7. Vérifier qu’il n’y a pas d’erreurs dans la console du navigateur. Par exemple, une erreur de type 404 sur le favicon.ico signifie que le navigateur n’a pas pu trouver ce fichier.

Au final, l’arborescence de votre projet doit ressembler à ceci :

.
├── dist
   ├── assets
   │   ├── index-BKEa98Qj.js
   │   └── index-Col1SUQS.css
   ├── favicon.png
   └── index.html
├── css
│   └── style.css
├── index.html
├── main.js
├── .env
├── package.json
├── package-lock.json
├── public
│   └── favicon.png
├── README.md
├── src
│   ├── api.js
│   └── api.test.js
└── vite.config.js

Vous pouvez remarquer que le répertoire public contient le favicon du site. Si le fichier favicon se trouve dans le dossier public c’est parce que vitejs le copie automatiquement dans le répertoire dist lors du build.

Le dossier racine comporte également un fichier .env qui permet de définir des variables d’environnement utilisable par vitejs. Par exemple, si vous avez besoin d’une clé d’API pour l’API de conversion de devises, vous pouvez la définir dans ce fichier.

.env

VITE_API_KEY=b596b2.................ea8d68b
VITE_API_ENDPOINT=https://openexchangerates.org/api

api.js

const response = await fetch(`${import.meta.env.VITE_API_ENDPOINT}/latest.json?app_id=${import.meta.env.VITE_API_KEY}`)

Le fichier .env est ignoré par git et ne doit pas être versionné. Il doit être fourni par l’équipe ops lors du déploiement en production.

3.1 Gitflow

Connaître des procédures de développement en équipe dont le suivi est compréhensible et traçable (p. ex. liaison des commits et des récits utilisateur, PullRequest/revue par les pairs). Connaître des pratiques de gestion du code source basée fonctionnalités (p. ex. flux de travail GIT, versionnage sémantique, etc.).

Introduction à git

La partie Git du cours est entièrement basé sur le livre gratuit et officiel que l’on peut trouver à cette adresse : https://git-scm.com/book/fr/v2.

Cette partie s’articule autour de la lecture de ce livre (pas complètement 😓) et de la réalisation d’exercices pratiques. Voici comment il faut procéder :

Installez-vous confortablement sur votre chaise, téléchargez et ouvrez le livre officiel de Git.

Lisez les 3 premiers chapitres du livre. Au fur et à mesure de votre avancement, mettez en pratique les commandes qui sont présentées. Aucune aide externe ne vous est autorisée pour ces 3 chapitres. Vous devez vous débrouiller seul ou avec votre enseignant. Il ne vous est pas possible de demander à vos camarades de classe ni d’utiliser Internet!

Vous avez bien entendu le droit de créer des dépôts locaux et/ou distant sur Gitlab pour vous entraîner. Vous pourrez supprimer ces dépôts une fois que vous n’en aurez plus besoin.

L’enseignant qui possède déjà des bases dans ce domaine à utilisé ~6h pour réaliser cette tâche. Il s’agit de bien lire et de bien comprendre. Oui, parfois il faut lire plusieurs fois le même paragraphe pour réussir à comprendre 😟

Chapitre Démarrage rapide

Dans cette partie on vous explique les différents gestionnaires de versions, on vous parle de l’origine de Git et de ses prédécesseurs et on vous explique comment installer et configurer Git.

Comme il est indiqué dans ce chapitre:

Dans ce livre, nous utiliserons Git en ligne de commande. Tout d’abord, la ligne de commande est la seule interface qui permet de lancer toutes les commandes Git - la plupart des interfaces graphiques simplifient l’utilisation en ne couvrant qu’un sous-ensemble des fonctionnalités de Git. Si vous savez comment utiliser la version en ligne de commande, vous serez à même de comprendre comment fonctionne la version graphique, tandis que l’inverse n’est pas nécessairement vrai.

demo.cast

Votre objectif est d’installer Git sur votre machine si ce n’est pas déjà fait, et de passer en revue les différents niveaux de configuration, system, global et local.

Déjà que vous y êtes, configurer votre nom et votre adresse email dans la configuration globale.

$ git config --global --list
user.email=pierre.ferrari@rpn.ch
user.name=piferrari
credential.helper=/usr/share/doc/git/contrib/credential/libsecret/git-credential-libsecret
init.defaultbranch=main
pull.rebase=false
alias.last=log -1 HEAD

Les différentes valeurs de configuration sont expliquées sur la page de manuel de git-config que vous pouvez consulter avec la commande man git-config ou en ligne à cette adresse : https://git-scm.com/docs/git-config.

Chapitre Les bases de Git

Dans ce chapitre, vous allez apprendre les bases de Git. Vous allez apprendre à créer un dépôt, à y ajouter des fichiers, à les valider et à les pousser sur un serveur distant. Vous apprendrez également à ignorez des fichiers et à les renommer.

Commandes pratiquées dans ce chapitre :

$ git init
$ git add
$ git status
$ nano .gitignore
$ git clone
$ git commit
$ git status
$ git diff
$ git mv
$ git rm
$ git log
$ git reset
$ git checkout
$ git restore
$ git remote
$ git push
$ git pull
$ git fetch
$ git tag
$ git show
$ git last

Chapitre Les branches avec Git

Dans ce chapitre, vous allez apprendre à créer des branches, à les fusionner et à les supprimer. Vous allez également apprendre à gérer les conflits de fusion.

Supplément : Chapitre Commandes Git

Ce chapitre constitue une référence des commandes Git. Vous pouvez le consulter à tout moment pour retrouver une commande ou une option.

Exercices git

GitLab S2

Après avoir pratiqué les exercices précédents, vous devriez être en mesure de réaliser l’exercice ci-dessous. Ce pourrait être un exercice d’examen.

  1. Créez un dépôt Git locale sur votre machine dans un dossier de votre choix.
  2. Ajoutez-y un fichier votreNom contenant votre nom. Par exemple ferrarip contenant Pierre Ferrari.
  3. Validez ce changement dans votre dépôt avec un message de commit explicite. Vous trouverez les bonnes pratiques ici
  4. Créez un lien origin vers le dépôt distant exercice-base. ATTENTION, on ne vous demande pas de le cloner, mais de créer un lien vers ce dépôt.
  5. Rapatriez uniquement le contenu de la branch main du dépôt distant dans votre dépôt local. En l’occurrence, un fichier README.md.
  6. Mettez à jour le dépôt local pour le faire coïncider avec votre dépôt distant. C’est-à-dire, faite apparaître le fichier README.md dans votre dépôt local.
exbase.cast

binCraft decoder

Vous trouvez un dépôt binCraft à cette adresse.

Le dépôt original est différent de ce dépôt pour plusieurs raison mais une des raisons c’est que le dépôt original contient des erreurs dans le décodage des valeurs binaires alors que dans ce dépôt, les erreurs ont étés corrigées. Votre travail consiste à retrouver les erreurs pour être en mesure de proposer des corrections au mainteneur du dépôt original.

Bien entendu, il ne faut pas chercher à lire le code pour trouver les erreurs. Vous devez utiliser Git pour faire le travail.

Une fois les erreurs identifiées, préparez un patch par erreur identifiée comme si vous vouliez soumettre une demande de fusion (merge request) au mainteneur du dépôt original.

On ne souhaite fournir que les modifications du fichier binCraft_decoder.py puisque c’est le seul fichier identique présent sur le dépôt d’origine. On souhaite également prendre en compte toutes les modifications faites depuis le début, c’est-à-dire, depuis le commit initiale ayant le hash cf2cb6d8 jusqu’à l’état actuel HEAD

$ bincraft-decoder$ git diff --patch cf2cb6d8 HEAD binCraft_decoder.py > patch.txt
$ cat patch.txt
+++ b/binCraft_decoder.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 import struct
 import math
@@ -96,10 +96,10 @@ def binCraftReader(file,zstd_compressed=False):
        ac['lat'] = s32[2] / 1e6;
        ac['lon'] = s32[3] / 1e6;
 
-       ac['alt_baro'] = s16[8] * 25;
-       ac['alt_geom'] = s16[9] * 25;
-       ac['baro_rate'] = s16[10] * 8;
-       ac['geom_rate'] = s16[11] * 8;
+       ac['alt_baro'] = s16[10] * 25;
+       ac['alt_geom'] = s16[11] * 25;
+       ac['baro_rate'] = s16[8] * 8;
+       ac['geom_rate'] = s16[9] * 8;
 
        ac['nav_altitude_mcp'] = u16[12] * 4;
        ac['nav_altitude_fms'] = u16[13] * 4;
@@ -150,7 +150,7 @@ def binCraftReader(file,zstd_compressed=False):
        ac['nic_a'] = (u8[72] & 64) >> 6;
        ac['nic_c'] = (u8[72] & 128) >> 7;
 
-       ac['rssi'] = 10 * math.log10(u8[86]*u8[86]/65025 + 1.125e-5);
+       ac['rssi'] = 10 * math.log10(u8[86]*u8[86]/65025 + 1.125e-5) / math.log(10);
        ac['dbFlags'] = u8[87];
 
        ac['flight'] = genStr(u8,78,87)

Currency converter (le retour)

Reprenez l’exercice du currency converter que vous avez déjà réalisé précédemment dans la partie build. Durant la réalisation de cet exercice, on ne vous demandait pas de versionner votre code. Vous allez maintenant versionner votre code.

$ git log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(auto)%d%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all
* 1517d42 -  (HEAD -> main, tag: v2.0.0, origin/main, origin/HEAD) feat(api)!: changement d'api et adaptation des tests unitaires - piferrari
* 7e5262d -  (tag: v1.1.0) feat(app): ajout de la fonctionnalité permettant d'inverser la conversion, par exemple € → CHF par CHF → € en un clic - piferrari
* b9d7042 -  (tag: v1.0.0) refactor(readme): mise à jour du fichier readme - piferrari
* 5a369fa -  fix(js): suppression de l'event change inutile - piferrari
* c5eae45 -  fix(html): correction du titre de la page - piferrari
* 9e758fb -  refactor(vite): ajout du dossier public et utilisation de ce dossier pour favicon - piferrari
* 7a9ca98 -  feat(js): ajout du programme de conversion, suppression des tests et import css via vite - piferrari
* 79955f1 -  feat(html): ajout des formulaires html avec le css - piferrari
* 9ed7492 -  feat(api): ajout de l'api exchange ainsi que des tests unitaires - piferrari
* 5e85721 -  feat(vite): mise en place de l'environnement vite avec sa config - piferrari
* 3dd38d0 -  Initial commit - Ferrari Pierre (PDF)

Exercices supplémentaires

Vous pouvez également pratiquer les exercices suivants chez vous :

4. Utiliser un processus d’intégration automatisé

Utiliser un processus d’intégration automatisé est essentiel pour optimiser la qualité et la rapidité des cycles de développement logiciel. L’intégration automatisée consiste à déclencher automatiquement une série de tests et de validations à chaque modification du dépôt.

Ce processus permet de détecter rapidement les erreurs de compilation, les conflits de fusion, ou les bugs introduits par des modifications récentes, réduisant ainsi les délais entre la détection d’un problème et sa résolution. De plus, l’automatisation garantit que les tests sont exécutés de manière cohérente et exhaustive, augmentant ainsi la fiabilité du logiciel. Par exemple, dans un environnement DevOps, l’intégration automatisée peut inclure des tests unitaires, des tests d’intégration, des vérifications de la qualité du code, et même le déploiement sur des environnements de staging, permettant ainsi aux équipes de déceler les problèmes potentiels avant que le code n’atteigne la production. Cette approche non seulement accélère le cycle de développement, mais aussi améliore la qualité et la stabilité du produit final, tout en réduisant les risques de régressions et en favorisant une culture de développement itératif et collaboratif.

Principe de l’intégration continue

Il existe plusieurs manières de procéder pour faire de l’intégration continue. Chaque entreprise possède sa propre manière de faire même si elle se base sur des concepts communs. Ce que vous allez mettre en pratique est un exemple parmi d’autres.

Dans cet exemple, on voit un développeur qui va ajouter une fonctionnalité ou corriger un bug.

Il va créer une branche à partir de la branche main pour réaliser son travail. Il fera des commits réguliers pour sauvegarder son travail. Une fois qu’il pense être au bout de son travail, il va pousser sa branche sur le dépôt distant. Cette action va démarrer un pipeline CI/CD qui va tester le code, le compiler, le valider. Une fois que tous les tests mis en place seront passés (✔), le code sera proposé à une revue par un pair, un autre développeur ou un responsable.

Une fois la revue terminée, l’auteur va créer une demande de fusion (merge request) pour fusionner sa branche avec la branche main. Cette demande de fusion va déclencher un nouveau pipeline CI/CD qui va tester le code, le compiler, le valider et le déployer sur un environnement de staging.

Dans la phase de staging, un groupe de testeur utilisera la nouvelle version proposée pour vérifier que tout fonctionne correctement. Si tout est OK, le code validé sera déployé en production.

GitLab CI/CD

GitLab propose un outil d’intégration continue et de déploiement continu intégré directement dans son outil de gestion de versions. Cet outil s’appelle GitLab CI/CD.

GitLab CI/CD permet de définir des pipelines d’intégration continue et de déploiement continu directement dans le fichier de configuration du dépôt Git. Ces pipelines sont déclenchés automatiquement à chaque modification du dépôt, permettant ainsi de tester, de compiler et de déployer le code de manière automatisée.

Les pipelines GitLab CI/CD sont définis dans un fichier .gitlab-ci.yml à la racine du dépôt Git. Ce fichier contient les étapes du pipeline, les tâches à exécuter, les dépendances entre les tâches, et les conditions de déclenchement du pipeline.

Ce fichier est de type YAML. Il faut respecter la syntaxe YAML pour qu’il soit considéré comme valide. Gitlab propose un éditeur intégré au site web. Cet éditeur comporte un linter qui vous avertis des erreurs présentes lors de l’édition.

Voici un exemple de fichier .gitlab-ci.yml pour un pipeline simple :

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - npm install
    - npm run build

test:
  stage: test
  script:
    - npm test

deploy:
  stage: deploy
  script:
    - npm run deploy
  only:
    - main

Dans cet exemple, le pipeline est composé de trois étapes : build, test et deploy. Chaque étape contient une ou plusieurs tâches à exécuter, définies dans la section script. Les tâches sont exécutées dans l’ordre défini par les stages, et les dépendances entre les étapes sont gérées automatiquement par GitLab CI/CD.

Chaque stage s’exécute dans un conteneur système lxd séparé. Le conteneur correspond à une installation d’Ubuntu 20.04 vierge

Exercices basiques GitLab CI/CD

Exercice 1, le plus basique

Créez un dépôt sur le serveur GitLab de l’école. Nommez ce dépôt cicd-basique.

Dans le menu de gauche, cliquez sur Build puis sur Pipeline editor. Activez les pipelines GitLab CI/CD pour ce projet en créant le fichier .gitlab-ci.yml par un simple appui sur le bouton Configure pipeline. Vous vous trouvez alors dans l’éditeur de pipeline.

Commiter le fichier .gitlab-ci.yml en cliquant sur le bouton Commit changes. Rendez-vous ensuite dans l’onglet Pipelines pour voir le pipeline se déclencher.

Exercice 2, on vous prends par la main

Cet exercice est en réalité une vidéo réalisée par M. Droz lors d’un cours en ligne. Vous allez suivre pas à pas les instructions de la vidéo pour réaliser un pipeline GitLab CI/CD.

La vidéo se trouve à cette adresse : Ubicast S2.

Exercice 3, un peu plus complexe

Pour cet exercice, vous allez créer un pipeline GitLab CI/CD pour un projet JavaScript en NodeJS. Le pipeline doit inclure les étapes suivantes :

  1. Installer les dépendances du projet.
  2. Lancer les tests unitaires.
  3. Exécuter le code.
  4. Déployer le code sur un serveur de staging (simulation pour l’instant).

Le travail sera réalisé dans l’outil de développement Visual Studio Code.

  1. Créez un nouveau projet sur le serveur GitLab de l’école. Nommez ce projet js-cicd. Le projet est vide pour l’instant.
  2. Cloner ce projet localement dans un dossier nommé js-cicd.
  3. Initialisez un projet JavaScript dans le dossier js-cicd en exécutant la commande npm init.
  4. Installez les dépendances suivantes dans le projet :
$ npm install dayjs
  1. Créez un fichier index.js dans le dossier du projet et ajoutez-y le code suivant :
import dayjs from "dayjs"

const today = dayjs()
const year_end = dayjs('2024-12-31')

console.log(year_end.diff(today, 'day'))
  1. Modifier le fichier package.json pour ajouter le type module ainsi que les scripts start et test suivants :

"main": "index.js",
"type": "module",
"scripts": {
  "start": "node index.js",
  "test": "echo \"Error: no test specified\" && exit 1"
}
  1. Testez localement vos scripts en exécutant les commandes npm start et npm test.
$ npm start

> js-cicd@1.0.0 start
> node index.js

194
$ npm test

> js-cicd@1.0.0 test
> echo "Error: no test specified" && exit 1

Error: no test specified
  1. Créez un fichier .gitlab-ci.yml à la racine du projet et ajoutez le contenu suivant :
stages:
  - install
  - run
  - test
  - deploy

install:
  stage: install
  before_script:
    - placer ici le nécessaire pour installer les dépendances du projet
  script:
    - placer ici le code nécessaire pour ce stage

run:
  stage: run
  before_script:
    - placer ici le nécessaire pour installer les dépendances du projet
  script:
    - placer ici le code nécessaire pour ce stage

test:
  stage: test
  before_script:
    - placer ici le nécessaire pour installer les dépendances du projet
  script:
    - placer ici le code nécessaire pour ce stage

deploy:
  stage: deploy
  script:
    - echo "Deploying to staging server..."
  only:
    - main
  1. Commitez et poussez le projet sur le dépôt distant.
  2. Vérifiez que le pipeline est déclenché automatiquement et que les étapes du pipeline sont exécutées avec succès.

Erreurs :

Il y a des erreurs dans l’exécution du pipeline, c’est normal si vous n’avez rien changé au fichier YAML présent ci-dessous. En effet, dans ce fichier il n’y a actuellement aucune commande valide. Trouvez-les les commandes permettant d’effectuer les 3 premiers stages uniquement.

Le corrigé est dans la branche correct.

On est obligé de faire l’installation de node dans chaque job car c’est un nouveau conteneur qui est lancé à chaque job. Il n’y a pas de persistance entre les jobs.

J’ai essayé de mettre en place du cache mais apparemment, il n’est pas supporté dans les runners partagés lxc.

Dès que le job test est lancé, ça plante et c’est normal. Il n’y a pas de test dans le projet et la commande retourne la valeur 1. C’est pour cela que le pipeline est en erreur.

Comment savoir que c’est une erreur. Par convention, un script retourne la valeur 0 si tout s’est bien passé et une autre valeur si une erreur est survenue.

$ printf "Hello"
$ echo $?
0
$ printf "Hello %d" a
bash: printf: a: nombre non valable
Hello 0
$ echo $?
1

Runners

Les pipelines GitLab CI/CD sont exécutés par des agents appelés runners. Les runners sont des machines virtuelles ou physiques qui exécutent les tâches du pipeline. Ils peuvent être hébergés sur le cloud, sur site ou sur des machines dédiées. GitLab propose des runners partagés et des runners spécifiques à chaque projet, permettant ainsi de personnaliser l’environnement d’exécution des pipelines en fonction des besoins du projet.

Les runners GitLab CI/CD sont configurés dans les paramètres du projet GitLab. Vous pouvez définir des runners partagés ou spécifiques à chaque projet, en fonction de vos besoins. Les runners peuvent être configurés pour exécuter des tâches spécifiques, utiliser des images Docker personnalisées, ou se connecter à des services externes pour exécuter des tâches spécifiques.

Création d’un runner locale

Pour créer un runner local, il faut installer le logiciel gitlab-runner sur la machine qui va exécuter les tâches du pipeline. Le runner peut être installé sur n’importe quelle machine compatible avec GitLab CI/CD, y compris les machines virtuelles, les conteneurs Docker, les serveurs physiques, etc.

Nous installerons un runner dans une machine virtuelle Ubuntu 20.04. Pour cela, nous allons suivre les étapes suivantes :

  1. Télécharger et démarrer la machine virtuelle Ubuntu.
  2. Installer le logiciel gitlab-runner sur la machine virtuelle en suivant les instructions de la documentation officielle.
ubuntu@m347:~$ curl -sL "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
[sudo] password for ubuntu:
Detected operating system as Ubuntu/jammy.
Checking for curl...
Detected curl...
Checking for gpg...
Detected gpg...
...
ubuntu@m347:~$ sudo apt install gitlab-runner
  1. Dans le projet précédemment créé js-cicd, ajoutez un runner spécifique au projet en suivant les instructions de la documentation officielle.

ubuntu@m347:~$ gitlab-runner register  --url https://git.s2.rpn.ch  --token glrt-ZF_11xZtbP6cJsdaDe5c
ubuntu@m347:~$ gitlab-runner run
Runtime platform                                    arch=amd64 os=linux pid=3420 revision=b92ee590 version=17.4.0
Starting multi-runner from /home/ubuntu/.gitlab-runner/config.toml...  builds=0 max_builds=0
WARNING: Running in user-mode.
WARNING: Use sudo for system-mode:
WARNING: $ sudo gitlab-runner...

Configuration loaded                                builds=0 max_builds=1
listen_address not defined, metrics & debug endpoints disabled  builds=0 max_builds=1
[session_server].listen_address not defined, session endpoints disabled  builds=0 max_builds=1
Initializing executor providers                     builds=0 max_builds=1
  1. Vérifiez que votre pipeline sera exécuté par le runner local.

  1. Exécutez le pipeline sur le runner local et vérifiez que les étapes du pipeline sont exécutées avec succès. Vous pourrez voir les logs évoluer dans la console.
Checking for jobs... received                       job=6941 repo_url=https://git.s2.rpn.ch/versioning/js-cicd.git runner=ZF_11xZtb
Added job to processing list                        builds=1 job=6941 max_builds=1 project=5320 repo_url=https://git.s2.rpn.ch/versioning/js-cicd.git time_in_queue_seconds=1
Appending trace to coordinator...ok                 code=202 job=6941 job-log=0-317 job-status=running runner=ZF_11xZtb sent-log=0-316 status=202 Accepted update-interval=1m0s
Removed job from processing list                    builds=0 job=6941 max_builds=1 project=5320 repo_url=https://git.s2.rpn.ch/versioning/js-cicd.git time_in_queue_seconds=1
Checking for jobs... received                       job=6942 repo_url=https://git.s2.rpn.ch/versioning/js-cicd.git runner=ZF_11xZtb
Added job to processing list                        builds=1 job=6942 max_builds=1 project=5320 repo_url=https://git.s2.rpn.ch/versioning/js-cicd.git time_in_queue_seconds=3
Appending trace to coordinator...ok                 code=202 job=6942 job-log=0-1524 job-status=running runner=ZF_11xZtb sent-log=0-1523 status=202 Accepted update-interval=1m0s

Sources :

Conditions de déclenchement

Cette partie est un copier / coller du blog de Stéphane Robert. Elle est copier ici au cas où le blog disparaitrait.

Lui-même c’est inspiré de la documentation officielle de GitLab.

Tout bon consultant DevOps se doit d’optimiser ses pipelines CI/CD. GitLab CI/CD, offre une variété de conditions de workflow qui permettent une personnalisation et une optimisation importantes des pipelines. Ces conditions, comme if, when, except, rules et workflow, sont des instruments clés pour gérer le comportement des jobs dans vos pipelines.

Pourquoi les Conditions sont-elles essentielles ?

Simplement parce que toutes les étapes d’un pipeline ne doivent pas nécessairement être exécutées à chaque commit ou à chaque modification du code source. C’est pour cette raison que GitLab CI/CD permet l’utilisation de conditions dans le fichier YAML.

Les conditions définissent les règles qui déterminent si une étape du pipeline doit être exécutée ou non. Elles permettent d’optimiser le processus de développement en exécutant uniquement les étapes nécessaires.

Définir des Conditions

Pour définir des conditions sur un job du pipeline, il suffit d’y ajouter une section rules. Chaque règle spécifie une condition sous la forme d’une expression conditionnelle et le job sera exécuté si cette condition est évaluée comme vraie.

job:  
  script:
    - echo "Cette étape s'exécute si la condition est satisfaite"
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

Dans cet exemple, le job sera exécuté uniquement si la branche du commit est main. Cet exemple est très simple mais les possibilités offertes par les conditions sont vastes. Vous pouvez créer des conditions complexes en utilisant des opérateurs logiques ET, OU ainsi que des variables d’environnement que vous aurez vous-même définies ou qui sont fournies par GitLab.

Voyons quelques exemples d’utilisation courants de conditions :

rules:
  - if: '$CI_COMMIT_TAG'

Ici, le job sera exécuté uniquement si le commit est associé à un tag.

rules:
  - if: '$CI_PIPELINE_SOURCE == "manual"'

Ce job s’exécutera seulement si le pipeline a été déclenché manuellement.

Utilisation de when pour Contrôler le Déclenchement

when est utilisé à l’intérieur des règles rules pour définir le moment où un job doit être exécuté. GitLab offre plusieurs options pour when afin de personnaliser le déclenchement de vos jobs en fonction de scénarios spécifiques.

on_success

rules:
  - if: '$CI_COMMIT_BRANCH == "main"' 
    when: on_success

Dans cet exemple, le job sera exécuté uniquement si la branche du commit est main et si tous les jobs précédents dans le pipeline ont été exécutés avec succès.

on_failure

rules:
  - if: '$CI_COMMIT_BRANCH == "main"'
    when: on_failure

Ce job s’exécutera uniquement si la branche du commit est main et si au moins l’un des jobs précédents dans le pipeline a échoué.

always

rules:
  - if: '$CI_COMMIT_BRANCH == "main"'
    when: always

Ce job sera toujours exécuté, quelle que soit l’état des jobs précédents, tant que la branche du commit est main.

manual

rules:
  - if: '$CI_COMMIT_BRANCH == "main"'
  when: manual

Ce job ne s’exécute que lorsque l’utilisateur le déclenche manuellement, indépendamment des autres conditions.

delayed

rules:
  - if: '$CI_COMMIT_BRANCH == "main"'
  when: delayed
  start_in: 30 minutes

Ce job s’exécute automatiquement, mais il est retardé de 30 minutes à partir du déclenchement du pipeline.

scheduled

rules:
  - if: '$CI_COMMIT_BRANCH == "main"'
  when: scheduled
  cron: "0 12 * * *"

Ce job est planifié pour s’exécuter tous les jours à midi.

Les règles sur les pipelines : workflow

La directive workflow détermine si un pipeline doit être créé et exécuté. Contrairement aux règles appliquées à des jobs individuels, workflow permet de contrôler le déclenchement de l’ensemble du pipeline en se basant sur des critères globaux. Cette capacité de gestion à un niveau supérieur offre un contrôle précis et efficace sur l’exécution des pipelines, en particulier dans des projets complexes avec de multiples branches et conditions.

Un cas d’utilisation courant de workflow est la mise en place de conditions pour exécuter des pipelines uniquement pour certains événements ou pour certaines branches. Par exemple, vous pourriez vouloir déclencher un pipeline seulement lors d’un push sur la branche principale ou sur des branches de fonctionnalités, mais pas sur des branches de corrections de bugs. Voici comment cela peut être configuré :

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push"'
    - if: '$CI_COMMIT_BRANCH == "main"'
    - if: '$CI_COMMIT_BRANCH =~ /^feature-/'

Dans cet exemple, le pipeline est déclenché lors d’un push sur la branche principale main ou sur une branche dont le nom commence par feature-. Cette configuration permet d’assurer que les ressources ne sont pas gaspillées sur des pipelines non essentiels, tout en garantissant que les branches importantes reçoivent l’attention nécessaire.

workflow peut également intégrer des logiques plus complexes, comme des conditions basées sur des variables d’environnement, des tags ou des changements spécifiques dans le code. Cela permet une flexibilité et une personnalisation élevées, adaptant vos pipelines aux besoins spécifiques de votre projet.

Exemple : Éviter les exécutions redondantes de pipelines

Imaginons un cas où vous souhaitez éviter que le même pipeline soit déclenché à la fois par un push et par un tag sur le même commit. En effet, si vous créez un tag avec la commande git tag <tagname>, le tag sera créer localement. Vous devrez donc poursuivre votre travail par un git push origin --tags. Ceci est un scénario courant où des pipelines redondants peuvent se lancer si les règles ne sont pas bien configurées. Voici comment workflow peut être utilisé pour gérer cela :

workflow:
  rules:
    - if: '$CI_COMMIT_TAG'
      when: never
    - if: '$CI_PIPELINE_SOURCE == "push"'

Dans cet exemple, le pipeline est configuré pour ne pas se déclencher lorsqu’un tag est appliqué grâce à when: never. Cela signifie que si un commit est à la fois poussé et tagué, le pipeline ne se déclenchera que pour le push, évitant ainsi un déclenchement redondant pour le tag.

Exemple : Gérer les pipelines pour les merge requests

Un autre cas d’usage fréquent est la gestion des pipelines pour les merge requests. Imaginez que vous avez ouvert une merge request et pour une raison quelconque, un commit est poussé sur la branche de la merge request. Vous ne voulez pas que le pipeline se déclenche à la fois pour le commit et pour la merge request. Voici comment workflow peut être utilisé pour gérer cela :

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
      when: never

Dans cette configuration, le pipeline se déclenche uniquement pour les événements de merge requests. Si un commit est poussé sur une branche qui a une merge request ouverte, le pipeline ne se déclenchera pas séparément pour cette branche, évitant ainsi des exécutions en double.

Ces exemples illustrent le fonctionnement de la directive workflow pour affiner la logique de déclenchement de vos pipelines dans GitLab CI/CD, vous permettant ainsi de gérer efficacement les ressources et d’éviter des exécutions inutiles ou redondantes.

Combinaison stratégique de rules et workflow

L’intégration et l’orchestration efficaces des pipelines dans GitLab CI/CD peuvent être grandement améliorées en combinant judicieusement les rules au niveau des jobs avec la directive workflow pour le pipeline global. Cette combinaison permet une gestion fine et une optimisation des déclenchements et exécutions des jobs, adaptant ainsi les pipelines aux exigences spécifiques et aux conditions de votre projet.

Exemple : Gestion de pipelines en fonction des branches et des événements

Imaginez un scénario où vous souhaitez exécuter des jobs différents en fonction de la branche sur laquelle les commits sont poussés, tout en gérant globalement le déclenchement des pipelines pour éviter les redondances. Voici comment cela peut être configuré :

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

deploy:
  script: deploy.sh
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
    - if: '$CI_COMMIT_BRANCH =~ /^release-/'
      when: on_success

test:
  script: test.sh
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Ici, le job deploy est configuré pour s’exécuter manuellement sur la branche principale et automatiquement en cas de succès sur les branches commençant par release-. Le job test est quant à lui déclenché uniquement pour les merge requests. Le workflow global est configuré pour déclencher le pipeline pour les push sur la branche principale, les événements de planification et les événements de merge requests, tout en évitant les déclenchements redondants.

En comprenant et en appliquant ces concepts dans vos projets, vous pourrez assurer que vos pipelines ne sont pas seulement fonctionnels, mais qu’ils sont également conçus de manière à répondre efficacement aux changements et exigences de votre environnement de développement. Cela conduira à une intégration et une livraison continues plus robustes, fiables et efficientes, un objectif clé dans toute démarche DevOps.

Exercices 1, comprendre les conditions

En parcourant un dépôt GitLab, vous trouvez le fichier .gitlab-ci.yml suivant :

stages:
  - build
  - test
  - deploy

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: "$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS"
      when: never
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"

...

💬 Comment connaître le nom de la branche par défaut $CI_DEFAULT_BRANCH de ce dépôt ?

Settings > Repository -> Branch defaults

💬 Quels sont les autres mots clés utilisables avec la variable $CI_PIPELINE_SOURCE ?

predefined-variable

💬 Quel est le comportement du pipeline si un commit est poussé sur la branche principale ?

Le pipeline est déclenché

💬 Quel est le comportement du pipeline si un commit est poussé sur une branche qui a une merge request ouverte ?

Le pipeline ne se déclenchera pas séparément pour cette branche

Currency converter, projet complet 😟

Objectif

Être en mesure de créer un pipeline GitLab CI/CD complet pour un projet de conversion de devises en utilisant les conditions de déclenchement et les règles de workflow.

Le travail s’effectue dans des branches séparées. Interdictions de travailler directement sur la branche main.

Le pipeline doit inclure les étapes suivantes :

  1. [T] Passer un linter sur le code. Si le linter retourne des erreurs, le pipeline doit s’arrêter.
  2. [T] Exécuter les tests unitaires. Si un des tests échoue, le pipeline doit s’arrêter.
  3. [T] Générer un fichier Changelog.md contenant les modifications apportées en se basant sur les messages de commit.
  4. [M] Builder le site web. Si le build échoue, le pipeline doit s’arrêter.
  5. [M] Créer un artifact docs.zip contenant le site web de documentation. Ce fichier zip pourra être téléchargé depuis l’interface GitLab, dézippé dans un dossier www de Laragon par exemple, et visualisé.
  6. [M] Déployer le site web builder sur un serveur de staging. Le déploiement doit être manuel ⏵.

[T] = Toujours, [M] = Uniquement lors d’un merge request sur main

Pour pouvoir déployer le site web sur un serveur de staging, vous devez disposer d’un serveur web Apache2 ou Nginx. Vous pouvez utiliser une machine virtuelle ou un conteneur Docker pour simuler le serveur de staging.

Par exemple, si vous utilisez un runner local dans une machine virtuelle Ubuntu, vous pouvez installer un serveur Apache2 dans cette même machine. Votre runner aura alors la possibilité de pousser les fichiers du runner vers le serveur Apache2.

Pour être complet dans cette simulation, il serait intéressent de pousser les fichiers via rsync ou scp sur le serveur de staging. Dans les deux cas, vous aurez besoin d’un serveur OpenSSH sur la machine Ubuntu.

Conservez la configuration par défaut du serveur OpenSSH pour simplifier la mise en place.

Pour le nom d’utilisateur et le mot de passe à utiliser avec la connexion SSH, vous devez utiliser les variables CI/CD. L’utilisation est détaillée dans la vidéo de M. Droz.