Versioning de projet avec GitLab CI/CD

  • Julien 

L’objectif de cet article est d’utiliser certaines fonctionnalités de GitLab CI/CD afin de gérer le versioning automatique d’un projet Git.

Problématique

Les différentes releases d’une application prennent généralement la forme suivante : <NOM DE L'APPLICATION> MAJOR.MINOR.HOTFIX

Exemples
Releases Node.js (Node.js 10.5.0, Node.js 10.4.1, Node.js 10.4.0, etc.)
Releases MariaDB (MariaDB 10.3.8, MariaDB 10.2.16, MariaDB 10.1.34, etc.)
Releases Firefox (Firefox 61.0, Firefox 60.0.2, Firefox 59.0.3, etc.)
Et bien d’autres…

En plus d’être pratique, ce genre de convention permet aux développeurs de savoir s’il s’agit :

  • d’une petite correction de bug : HOTFIX s’incrémente,
  • d’un ajout de feature ou d’une correction plus importante : MINOR s’incrémente et HOTFIX est remise à zéro,
  • ou bien d’une mise à jour importante : MAJOR s’incrémente et MINOR et HOTFIX sont remises à zéro.

Dans le cadre d’une approche DevOps, l’objectif de cet article est de voir comment utiliser les fonctionnalités de GitLab CI/CD pour semi-automatiser l’incrémentation de ces variables.

Voyons tout d’abord ce dont nous allons avoir besoin pour atteindre cet objectif.

Prérequis

Compte GitLab

Si vous n’avez pas encore de compte GitLab, vous pouvez en créer un en cliquant ici et en suivant les instructions.

Projet Git

Après connexion à votre compte GitLab, il y a deux possibilités :

  • Soit vous avez d’ores et déjà un projet Git à versionner, et dans ce cas vous pouvez passer directement à la section suivante ;
  • Soit vous n’avez pas encore de projet Git à versionner, et dans ce cas nous allons en créer un rapidement.

.gitlab-ci.yml
Si ce n’est pas encore le cas, veuillez intégrer un fichier nommé .gitlab-ci.yml à la racine de votre projet. Ce fichier peut être ajouté via l’interface GitLab ou à la main.

Vous trouverez toutes les informations nécessaires à propos de ce fichier sur cette page.

Projet de test

Commençons avec un projet de test assez simple. Pour cela, créons deux fichiers texte (file1.txt et file2.txt) avec le contenu suivant :

Hello world!
file2

Maintenant, nous devons remplir notre fichier .gitlab-ci.yml avec les différentes étapes liées à l’intégration et au déploiement continus. Dans un projet basique, il en existe trois :

  • la compilation ;
  • le testing ;
  • et le packaging.

Avec ces informations, nous pouvons compléter notre fichier .gitlab-ci.yml avec le contenu suivant :

image: alpine:latest
 
stages:
  - compile
  - test
  - package
 
compile:
  stage: compile
  script: cat file1.txt file2.txt > compiled.txt
  artifacts:
    paths:
    - compiled.txt
    expire_in: 20 minutes
 
test:
  stage: test
  script: cat compiled.txt | grep -q 'Hello world'
 
package:
  stage: package
  script:
    - "tar -czf package.gz compiled.txt"
  artifacts:
    paths:
    - ./*.gz

Tout d’abord, nous récupérons une image alpine (distribution Linux ultra-légère), la plus récente, pour pouvoir travailler dessus.

Ensuite, nous listons les différentes étapes grâce au mot-clé stages.

Et enfin vient la partie un peu plus intéressante :

  • La compilation consiste à concaténer le contenu des deux fichiers file1.txt et file2.txt dans un fichier compiled.txt ;
  • Le testing consiste tout simplement à vérifier que l’on retrouve bien la chaîne de caractères Hello world à l’intérieur du fichier compiled.txt ;
  • Et le packaging (tar) consiste alors à récupérer ce fichier et à le compresser pour le mettre ensuite à disposition de l’utilisateur.

Options tar
Les options de la commande tar sont les suivantes :

  • L’option -c permet de créer une archive ;
  • L’option -z permet de spécifier que la compression se fait avec gzip ;
  • L’option -f permet de spécifier le nom de fichier de l’archive.

A noter que la liste détaillée des fichiers traités peut être affichée avec l’option -v (ou -vv pour une affichage encore plus détaillé).

Notre fichier packagé ici ne comporte aucune information quant à sa version actuelle.
Maintenant que ce projet de test est en place, voyons comment gérer l’auto-incrémentation avec GitLab CI/CD.

Auto-incrémentation

Nous allons ici utiliser une fonctionnalité de GitLab CI/CD et définir des variables secrètes.

Variables secrètes

Tout d’abord, nous allons avoir besoin de définir une variable pour chaque valeur de MAJOR.MINOR.HOTFIX.

Sur l’interface GitLab de votre projet, dans la barre de navigation située à gauche de l’écran, allez dans Settings > CI/CD > Variables.
Cliquez ensuite sur Expand et créez trois nouvelles variables secrètes de la manière suivante :

GitLab secret variables

Cliquez ensuite sur Save variables.

Nous venons de créer trois numéros de build internes à GitLab qui sont initialisés à la valeur zéro.

Maintenant, nous allons avoir besoin d’une quatrième variable secrète nous permettant de manipuler ces numéros.

Token API GitLab

Nous devons définir une nouvelle variable contenant un token pour l’API GitLab.

Pour cela, cliquez sur votre profil en haut à droite de l’interface GitLab > Settings > Access Tokens.

Créez un nouveau token avec un nom (API_ACCESS_TOKEN par exemple), ne lui donnez pas de date d’expiration pour qu’il reste valide pour une durée infinie, et cochez api dans la section Scopes.

Ensuite cliquez sur Create personal access token et vous verrez un nouveau champ apparaître en haut de la page :

GitLab personal token

Copiez le token affiché sous Your New Personal Access Token et retournez sur l’interface de création de variables secrètes.

Ensuite, créez une nouvelle variable secrète, protégée (Protected) cette fois-ci, avec comme nom API_ACCESS_TOKEN par exemple et comme valeur le token que vous avez copié précédemment.

Puis cliquez sur Save variables.

Vous vous retrouvez avec ceci (en cliquant sur Reveal values) :

GitLab secret variables

Maintenant que nous avons créé toutes les variables nécessaires au versioning, utilisons-les dans le fichier .gitlab-ci.yml à l’aide de scripts bash.

Script bash

Créons un dossier versioning/ à la racine du projet qui contiendra tout ce qui concerne le versioning.

À l’intérieur de ce dossier, nous allons créer un fichier texte VERSION_FILE de la forme suivante :

## Uncomment what you want to increment in
## MAJOR.MINOR.HOTFIX (and comment other lines)
 
#MAJOR
#MINOR
HOTFIX

Comme vous pouvez le deviner grâce au commentaire, ce fichier sera à modifier par l’utilisateur lors d’un push sur la branche principale (master) afin de gérer l’auto-incrémentation. Dans l’exemple ci-dessus, on s’apprête à incrémenter la variable HOTFIX.

Maintenant, ajoutons un script bash (auto_increment) qui va s’occuper de l’incrémentation :

VERSION_FILE=$1
CI_PROJECT_URL=$2
API_ACCESS_TOKEN=$3
CI_PROJECT_ID=$4
 
VERSION=$(grep "^[^# ]" ${VERSION_FILE})
 
if [ -z "${VERSION}" ]; then
    echo "Error: VERSION is empty"
    exit 1
elif [ "${VERSION}" != "MAJOR" ] && [ "${VERSION}" != "MINOR" ] && [ "${VERSION}" != "HOTFIX" ]; then
    echo "Error: VERSION is not MAJOR, MINOR or HOTFIX"
    exit 2
fi
 
GITLAB_URL=$(echo ${CI_PROJECT_URL} | awk -F "/" '{print $1 "//" $2$3}')
VAR=$(curl -s -f  --header "PRIVATE-TOKEN: ${API_ACCESS_TOKEN}" "${GITLAB_URL}/api/v4/projects/${CI_PROJECT_ID}/variables/${VERSION}" | jq  -r '.value' )
VAR=$((VAR+1))
curl -s -f --request PUT --header "PRIVATE-TOKEN: ${API_ACCESS_TOKEN}" "${GITLAB_URL}/api/v4/projects/${CI_PROJECT_ID}/variables/${VERSION}" --form "value=${VAR}"
 
if [ "${VERSION}" == "MAJOR" ]; then
    curl -s -f --request PUT --header "PRIVATE-TOKEN: ${API_ACCESS_TOKEN}" "${GITLAB_URL}/api/v4/projects/${CI_PROJECT_ID}/variables/MINOR" --form "value=0"
    curl -s -f --request PUT --header "PRIVATE-TOKEN: ${API_ACCESS_TOKEN}" "${GITLAB_URL}/api/v4/projects/${CI_PROJECT_ID}/variables/HOTFIX" --form "value=0"
fi
 
if [ "${VERSION}" == "MINOR" ]; then
    curl -s -f --request PUT --header "PRIVATE-TOKEN: ${API_ACCESS_TOKEN}" "${GITLAB_URL}/api/v4/projects/${CI_PROJECT_ID}/variables/HOTFIX" --form "value=0"
fi

Ce fichier en apparence complexe ne l’est pas tant que ça. Il inclut plusieurs étapes que l’on va détailler ci-dessous.

Étape 1 : lecture des arguments

Le script auto_increment prend 4 arguments :

  • VERSION_FILE qui correspond au fichier versioning/VERSION_FILE où l’on indique la variable à incrémenter ;
  • CI_PROJECT_URL qui est une variable interne à GitLab qui correspond à l’adresse HTTP du projet (voir GitLab CI/CD Variables) ;
  • API_ACCESS_TOKEN qui correspond à la variable secrète que l’on a créée permettant de manipuler l’API de GitLab ;
  • CI_PROJECT_ID qui est une variable interne à GitLab qui correspond à l’unique identifiant du projet courant que GitLab CI utilise en interne (voir GitLab CI/CD Variables).

Étape 2 : Parsing de la version

Après avoir affecté les arguments à des variables explicites, le fichier récupère la ou les lignes qui ne commencent ni par un espace ni par un # du fichier passé en argument lors de l’exécution du script, et inscrit le résultat dans une variable VERSION.

Ensuite, on vérifie dans un premier temps que cette variable n’est pas nulle et dans un second temps qu’elle correspond bien à l’une des trois propositions du fichier VERSION_FILE, qui correspondent au nom des variables secrètes créées dans GitLab. Sinon, on renvoie une erreur explicite.

Étape 3 : Incrémentation

Si la variable est bien reconnue, on récupère sa valeur dans VAR, on l’incrémente, puis on la met à jour dans GitLab grâce à une requête PUT.

Étape 4 : Décrémentation

Enfin, on gère le fait que si la variable en question est MAJOR, il faut réinitialiser les valeurs de MINOR et HOTFIX à zéro. Et de la même manière, si la variable incrémentée est MINOR, on réinitialise HOTFIX à zéro.

Maintenant que ceci est en place, rajoutons une nouvelle étape dans le fichier .gitlab-ci.yml.

.gitlab-ci.yml : étape supplémentaire

Nous allons ajouter une étape auto_increment dans le fichier .gitlab-ci.yml. Pour cela, commencez par modifier les stages :

stages:
  - compile
  - test
  - auto_increment
  - package

Ensuite, rajoutez une étape auto_increment :

auto_increment:
  stage: auto_increment
  before_script:
  - apk add --update curl jq
  script:
  - "chmod +x versioning/auto_increment"
  - "./versioning/auto_increment versioning/VERSION_FILE ${CI_PROJECT_URL} ${API_ACCESS_TOKEN} ${CI_PROJECT_ID}"

Dans ce job, nous récupérons tout d’abord le token d’accès à l’API GitLab. Ensuite, avec le mot-clé before_script, nous nous assurons de pouvoir utiliser les commandes curl et jq.

Le script de ce job va d’abord donner le droit d’exécution sur le script auto_increment avec la commande chmod +x, et ensuite lancer ce dernier avec les arguments dont il a besoin (voir Étape 1 : lecture des arguments ci-dessus).

Pour vérifier le bon fonctionnement de notre nouvelle fonctionnalité, il ne faut pas oublier de modifier le job de packaging, par exemple de la manière suivante :

package:
  stage: package
  script:
    - "tar -czf packaged-${MAJOR}.${MINOR}.${HOTFIX}.gz compiled.txt"
  artifacts:
    paths:
    - ./*.gz

Ici, MAJOR, MINOR et HOTFIX correspondent au nom des variables secrètes que l’on a créées précédemment.

Il ne reste plus qu’à lancer un nouveau pipeline en poussant sur GitLab une version du fichier VERSION_FILE modifiée selon nos souhaits.

Pipeline

Pour récupérer notre artifact, il faut aller dans la barre de navigation à gauche sur l’interface GitLab, cliquer sur CI/CD puis sur Pipelines.

Si ce n’est pas fait automatiquement, il faut lancer un nouveau pipeline en cliquant sur le bouton New Pipeline. Et dès que cette pipeline sera terminée, c’est-à-dire dès que le job package sera validé, nous pourrons cliquer sur ce dernier et télécharger sur la droite de l’écran les artifacts contenant le fichier packagé.

Selon ce que vous aurez renseigné, vous vous retrouverez alors avec un fichier contenant l’information de versioning directement dans son nom. Si par exemple vous avez décommenté la ligne HOTFIX dans le fichier VERSION_FILE, alors votre fichier s’appellera packaged-0.0.1.gz.

Conclusion

Grâce à ce procédé, il est possible d’incrémenter semi-automatiquement (‘semi’ car l’utilisateur doit tout de même renseigner la variable à incrémenter dans un fichier texte) la version de son application lors d’une release.

Il est à noter que cette fonctionnalité a été très demandée par la communauté et GitLab a récemment ajouté une feature qui se rapproche de ce que fait ce workaround.
Cela a été évoqué dans de nombreuses issues dont celle-ci et une nouvelle variable CI_PIPELINE_IID a été créée dans la version 11.0. En quelques mots, cette variable est incrémentée à chaque lancement d’un nouveau pipeline sur un projet spécifique et peut donc faire partie du numéro de version d’une application lors du processus de release.

Ressources

Cet article s’est beaucoup inspiré du workaround suivant à qui l’on doit l’idée d’utiliser des variables secrètes : marcos/CI_PIPELINE_IID.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *