Semantic versioning and releasing made easy

Semantic versioning and releasing made easy

Utiliser le semantic versioning pour générer des releases et déployer simplement

Publié le


Do not index
Do not index
Primary Keyword
Lié à Analyse sémantique (Articles liés) 1
Lié à Analyse sémantique (Articles liés)
Statut rédaction
Terminé
Lié à Analyse sémantique (Articles liés) 2

Introduction

La gestion des versions reste encore et toujours un problème ouvert, et ce depuis le premier programme de l’histoire.
Aujourd’hui nous allons essayer d’apporter une réponse à ce problème en utilisant les concepts suivants :
  • Semantic versioning pour avoir un standard sur la manière dont les montées de versions sont effectuées
  • Semantic release pour gérer l’interaction entre les versions du répertoire de code, et les releases de l’application
  • Git et une gestion des branches et du flow standardisée au sein d’un même répertoire
 

Présentation des outils

Semantic versioning

Semantic versioning est un standard ayant pour but d’uniformiser la gestion des montées de versions pour toutes les applications ayant cette thématique.
SemVer utilise une notation fixée et spécifiée entièrement, pouvant inclure une note de prerelease, ainsi qu’une note de build. D’après la documentation officielle, la grammaire BNF (non exhaustive) de la version finale est la suivante :
 
<valid semver> ::= <version core>
                 | <version core> "-" <pre-release>
                 | <version core> "+" <build>
                 | <version core> "-" <pre-release> "+" <build>

<version core> ::= <major> "." <minor> "." <patch>
 
Ce qui nous donne, par exemple, les versions suivantes :
  • 1.4.3
  • 63.0.0
  • 2.0.0-alpha1
  • 3.5.1+20231002
  • 0.9.11-preview+pr193
 
Pour ensuite gérer les montées de version, on utilise les critères suivants :
  • si la version corrige un bug, ou n’inclue pas de nouvelles fonctionnalités (par exemple une optimisation, ou une réorganisation du code), alors on monte la version de patch
  • si la version inclue une nouvelle fonctionnalité, autant pour les utilisateurs qu’en interne de la base de code, alors on monte la version mineure
  • si la version inclue des changements incompatibles avec la version précédente, alors on monte la version majeure
D’autres cas plus spécifiques, comme les versions 0.Y.Z, sont couverts et documentés sur la page officielle de Semantic versioning.

Semantic release

Semantic release est un logiciel utilisant le Semantic versioning pour générer des releases et des journaux de changements de manière automatisée. Il s’intègre dans un répertoire Git existant sous la forme de tâches à lancer dans les pipelines de CI/CD.
Ici, nous allons l’intégrer de telle manière à générer de nouveaux tags sur Git lors de la poussée de nouvelles versions sur des branches.

Git

Enfin, pierre angulaire de l’immense majorité de nos outils, Git n’a plus besoin d’être présenté.
Ici, nous n’allons pas décrire le logiciel, mais la manière dont nous l’utilisons pour gérer les montées de versions, et les différents environnements.
Tout s’articule autour de deux concepts : les branches et les tags.
Nous allons utiliser les branches pour décrire l’environnement sur lequel nous travaillons, et les tags pour marquer les différentes versions au fur et à mesure du développement.
 
Les développeurs travaillent sur des branches pour chacune des tâches qu’ils ont à effectuer, avec la nomenclature suivante :
  • chore/<branch-name> : pour les changements qui ne s’intègrent pas dans les catégories d’au-dessus (perf, refactoring, etc).
  • fix/<branch-name> : réparation d’un bogue
  • feat/<branch-name> : ajout d’une nouvelle fonctionnalité
Les noms des branches doivent suivre également un standard, et peuvent prendre la forme suivante :
  • nom de la fonctionnalité, description du bogue, etc
  • identifiant d’un ticket sur un logiciel externe (Jira, etc)
  • nom du développeur et identifiant d’une tâche, etc
  • et bien d’autres possibilités
 
Une fois leur fonctionnalité développée, les changements sont intégrés sur une branche de développement, couramment nommée develop.
Lorsque l’on veut intégrer ces changements sur un environnement plus proche de celui de production et y effectuer des tests plus généraux, on peut intégrer les changements de la branche develop sur une autre branche, couramment nommée staging cette fois.
Si tout semble bon, et que les changements sont prêts à partir en production, alors on peut tout intégrer sur la branche principale, typiquement main.
 
Gestion des branches et de la livraison du code entre les différents environnements
Gestion des branches et de la livraison du code entre les différents environnements
 

Mise en place

Nous avons désormais tous les outils en main pour déployer Semantic release sur un répertoire Git.
Les tâches à effectuer sont les suivants :
  • Installation de Semantic release
  • Configuration de Semantic release
  • Mise-à-jour de la CI/CD pour tout y intégrer

Installation de Semantic release

Semantic release prend la forme d’un paquet JavaScript, que l’on peut installer via NPM via la commande suivante :
npm install --save-dev semantic-release
Semantic release vient également avec des dépendances que nous pouvons installer dans la volée, et sur lesquelles nous reviendrons dans la section suivante :
# Requis dans tous les cas
npm install --save-dev @semantic-release/commit-analyzer @semantic-release/exec
# À adapter suivant la plateforme que vous utilisez
npm install --save-dev @semantic-release/gitlab
Toutes les dépendances sont maintenant installées, et nous pouvons passer à la configuration.

Configuration de Semantic release

La configuration de Semantic release est rangée dans le fichier .releaserc.yaml au sein du répertoire Git.
Un exemple minimal de configuration est le suivant :
branches:
	# N'activer que sur la branche 'main'
  - main


plugins:
	# Analyse des commits pour extraire le type et le nom des changements effectués
  - "@semantic-release/commit-analyzer"

	# Créer des nouveaux tags sur Gitlab
  - "@semantic-release/gitlab"

	# Créer un fichier contenant le nom de la nouvelle version à chaque release
	# /!\ il y a bien deux tirets d'affilée
  - - "@semantic-release/exec"
    - verifyReleaseCmd: "echo ${nextRelease.version} > .VERSION"
Ce fichier de configuration met en place les paramètres suivants :
  • Semantic release ne s’active que sur la branche main, en effet, sur les autres branches, il n’y a pas de release à créer puisque les changements n’ont lieu que sur des environnements de tests éphémères et instables
  • configuration de trois plugins, un pour pouvoir lire et analyser le contenu des commits effectués entre chaque release, un pour pouvoir se connecter à Gitlab et créer et pousser de nouveaux tags, et enfin un dernier pour créer un fichier contenant le nom de la nouvelle version. Ce fichier sera utilisé dans les pipelines de CI/CD que nous décrirons après.

Configuration des pipelines de CI/CD

Nous prendrons ici comme exemple une pipeline Gitlab CI, mais le fonctionnement est le même sur Github Actions, Jenkins, Azure DevOps, et tous les autres outils de ce type.
Le fonctionnement de la CI/CD va être le suivant. Deux nouvelles tâches vont s’ajouter à celles précédemment existantes, et effectuer les actions suivantes :
  • La première, dite de Pre-Release, va calculer la nouvelle version de l’application
  • La seconde, dite de Release, va construire puis pousser la nouvelle version de l’application
On va, pour ça, utiliser plusieurs mécanismes. Le fichier .VERSION généré par Semantic release voit son contenu être injecté dans la variable d’environnement VERSION. Cette variable sera ensuite utilisée dans l’action Release comme tag de l’application créée.
 
Le code utilisé par cette pipeline aura donc l’allure suivante :
Pre-Release:
  stage: pre-release
  image: node:18
  script:
    - |
      if [[ "$CI_COMMIT_BRANCH" = "main" ]]; then
        npm install
        npx semantic-release
        test -e ".VERSION" || exit 0
        echo "VERSION=v$(cat .VERSION)" >> build.env
      elif [[ "$CI_COMMIT_BRANCH" = "staging" ]]; then echo "VERSION=staging" >> build.env
      elif [[ "$CI_COMMIT_BRANCH" = "develop" ]]; then echo "VERSION=dev" >> build.env
      else false
      fi
  artifacts:
    reports:
      dotenv: build.env
  variables:
    GL_TOKEN: $GITLAB_PAT
  only:
    - develop
    - staging
    - main

Release:
  stage: release
  image: docker:latest
  dependencies:
    - Build
    - Pre-Release
  services:
    - docker:dind
  script:
    - test -z "$VERSION" && { echo "VERSION is not set or is empty - skipping release"; exit 0; }
	  # On suppose ici qu'un autre job s'occupe de constuire une image Docker $IMAGE
		- docker tag $IMAGE $REGISTRY/$IMAGE:$VERSION
    - docker push $REGISTRY/$IMAGE:$VERSION
  only:
    - develop
    - staging
    - main
L’action Pre-Release est donc en charge de générer la nouvelle version. Si l’on est sur une branche de test (develop ou staging) alors la version est le nom de ladite branche, et, si l’on est sur la branche de production (main) alors c’est Semantic release qui va générer la nouvelle version.
 
Enfin, l’action Release détecte si le déploiement d’une nouvelle version est nécessaire (via la variable VERSION), auquel cas elle met en ligne la nouvelle version de l’application (présentement une image Docker).
 
Ensuite, on peut faire ce que l’on veut avec cette nouvelle release, par exemple, laisser ArgoCD Image Updater détecter la nouvelle image et tout déployer automatiquement !

Conclusion

Dans cet article, nous avons présenté une solution pour gérer la montée de version des applications, ainsi que le déploiement continu et automatique de ces nouvelles versions.
Les montées de version sont standardisées, grâce au Semantic Versioning, et tout est intégré d’une manière unique via des pratiques Git établies.
Nous avons séparé l’intégration de la délivrance, en laissant la seconde être gérée par ArgoCD, et toutes les fonctionnalités qui vont avec.
Le cycle de mise-à-jour devient alors le suivant :
  • Travail local, test du code, puis envoi vers l’hébergeur Git
  • Intégration continue via l’hébergeur, et calcul d’une nouvelle version suivant la branche courante et le nom des commits précédants
  • Construction d’une nouvelle image Docker
  • Délivrance continue gérée par ArgoCD
 
Cette solution élégante et simplifiée pour les développeurs permet de leur retirer de la charge de travail, et les laisser se concentrer sur ce qui les importe le plus : le code !
 

S'inscrire à la newsletter DevSecOps Keltio

Pour recevoir tous les mois des articles d'expertise du domaine

S'inscrire

Écrit par

Victor Franzi
Victor Franzi

Victor est notre première recrue, il est passionné depuis son plus jeune âge et ne veut jamais s’arrêter d’apprendre 🧠

Sujets