blog.bressure.net

Application, Gestion de projet, Paramétrage

CI/CD avec workflow Git et pipeline Gitlab

admin

Dans le développement agile l’utilisation de Git comme référentiel de source et de Gitlab comme outil tout intégré de mise œuvre du CI/CD (Continuous Integration/Continuous Deployment) constituent un choix courant. On peut reprocher à Gitlab de ramener à lui toutes les étapes qui conduisent du code à l’exécution en production et créer une sorte de dépendance vis-à-vis d’un seul outil. Certes, mais le gain apporté est tel, avec le fameux Graal du commit suivi d’étapes dont la finalité est le déploiement automatique en production, le jeu n’en vaut -il pas la chandelle ?

L’époque lointaine où j’étais responsable de Software Factory qui correspond à la gestion du référentiel de source et de l’intégration continue (CI) en 2006, le déploiement automatique et continu (CD) n’était pas chose courante. De nos jours l’intégration continue et le déploiement continu sont maîtrisés par les gros acteurs de l’IT notamment ceux qui œuvrent dans le Cloud. Gitlab dans son approche tout intégrée offre une porte d’entrée dans ce monde du CI/CD aux équipes moins aguerries et cet article pose quelques réflexions et solutions de mise en œuvre.

Les concepts

Avant de montrer une mise en œuvre du CI/CD nous commençons pas poser la définition des concepts à manipuler. Le premier d’entre eux étant le référentiel de source et son utilisation. Cela nous conduira à définir un workflow permettant de gérer le code source de manière fluide et en évitant la majorité des écueils du passage d’un développement artisanal avec un seul développeur à un développement industriel avec de nombreux développeurs. Le second est l’étapes de construction et de déploiement. Nous verrons que le volet construction (CI) et le volet déploiement (CD) sont à voir d’un seul tenant que l’on appelle le pipeline de CI/CD. Enfin le dernier est la notion d’environnement d’exécution que l’on comprend intuitivement comme des ressources telles que des machines.

Définition

Référentiel de source

Je préfère au mot référentiel celui d’historique. Le gestionnaire de code source est le gardien de l’histoire du code source. Le code source est un livre écrit à plusieurs mains. Chaque développeur ou même un seul développeur peut prendre en charge plusieurs chapitre de ce livre. Lorsque les chapitres sont terminées on les fusionne (assemblage des chapitres) pour obtenir un livre cohérent. Le gestionnaire de code source doit offrir une gestion de cet aspect.

Dès lors qu’une modification du code source est requise, il faut l’isoler des autres modifications en prenant une photo du code source pour démarrer une histoire parallèle: c’est ce qu’on appelle une branche (de l’histoire…). Quand la modification est terminée, la branche doit réintégrer l’histoire principale. On parle de fusion vers la branche mère.

Les branches permettent de définir un niveau d’isolation. Chaque branche peut ainsi évoluer (le code de la branche change) sans impacter les autres branches. Cette gestion de branche défini par ce qu’on appelle un workflow de développement impose une gestion rigoureuse de la modification du code source. Cette gestion va permettre d’apporter de nombreuses modification en parallèle et de s’assurer de leur fusion contrôlée pour n’embarquer dans l’histoire principale du code source que celles que l’on veut voir dans la future version du logiciel.

Lorsque le code source est dans un état donné le pipeline de CI/CD peut alors être déclenché.

Pipeline de CI/CD

Le pipeline de CI/CD décrit les étapes de constructions et de déploiement. De manière intuitive on voit 3 étapes:

  1. construction
  2. test
  3. déploiement

La construction est la phase de compilation et production de livrable, la phase de test celle de l’exécution du livrable pour des tests, enfin le déploiement est l’exécution du livrable dans

Ce pipeline simple fonctionne bien pour un projet qui doit être déployé en tant qu’application web. La phase de déploiement consiste à déployer l’application sur un serveur de production. Même si les applications nouvelles sont pour la plupart dans ce cas, ce pipeline ne semble pas adapté à un projet qui produit une librairie par exemple. L’étape de déploiement sera alors un dépôt sur un serveur pour mise à disposition de la librairie produite.

Il est important de définir le sens de ce qui se cache derrière chaque étape du pipeline. Chaque projet peut avoir des besoins différents mais des règles communes peuvent être trouvées comme on vient de le voir pour le déploiement de librairie et le déploiement d’une application web. Il n’est donc pas nécessaire de séparer la construction et le dploiement: le CI et CD. Le CI/CD est un processus d’un seul tenant qui se déclenche pour une version de code source donné quel que soit le type de projet: librairie ou application web.

Environnements

Les environnements sont les ressources parmis lesquelles on voir intuitivement les machines où s’executent une version du code l’application.

Relation entre les concepts

On trouve sur internet des schémas qui montrent la gestion des différentes branches du code sources, les étapes de constructions ou le passage de l’application d’un environnement à l’autre. La difficulté pour bien appréhender la relation ente concepts que nous venons de décrire est de prendre garde qu’il sont a priori orthogonaux. Je m’explique.

Le code source peut être disjoint du CI/CD dans le sens où la contrôle du CI/CD peut être en dehors du code source. C’est le cas quand un service de CI/CD transverse à l’organisation gère comment le code doit être construit. Par exemple un service de CI/CD scrute le référentiel de source de l’application pour lancer un pipeline défini par le service et non le code source de l’application même.

Le pipeline du CI/CD peut produire des binaires et les déployer sur n’importe lequel des environnements. Intuitivement on imagine bien un pipeline qui déploie en environnement de développement puis en test et enfin en production en s’abstrayant complètement de la gestion des branches du référentiel de source. il peut déployer en production un code d’une branche temporaire !

Cette orthogonalité nous pousse à nous méfier des schémas qui mélangent ces concepts. Ainsi une branche production, qui est un environnement, n’a pas de sens. De même une étape production dans le pipeline de CI/CD non plus !

L’essence du CI/CD

L’enjeu du CI/CD est d’orchestrer la construction à partir du code source en intégrant les règles d’utilisation du ce référentiel (branche stable, branche temporaire etc ) afin de déployer l’application in fine dans le bon environnement.

Pour exemple:

code sourceCI/CDEnvironnement
branche stableconstruction, test, déploiementproduction
branche temporaireconstruction, testX
branche future versionconstruction, test, déploiementdéveloppement
Exemple de relation code, CI/CD, environnement

Nous allons voir comment la définition de tous ces concepts sont possibles dans Gitlab au sein de la définition du CI/CD.

Mise en œuvre

Git et Gitflow

Git est un gestionnaire de code source décentralisé ce qui signifie que toutes les fonctionnalités Git sont disponibles sans avoir de serveur central. Chaque développeur est donc libre de faire gérer l’historique du code source en local et de se synchroniser quand il veut avec un serveur non pas central mais nommé « origin » et ce par convention. Le versionnage du code source est basé sur la photo du code source qui est un condensat (un haché) du code.

La devise de l’utilisateur de Git est pour moi « brancher sans limite ». Bien plus que la décentralisation, le fonctionnalité la plus utilisée est le branchage (fork) qui permet de gérer le workflow de développement de manière efficace. La worklflow le plus utilisé est bien connu et le standard defacto:

source: https://nvie.com/posts/a-successful-git-branching-model/

D’un premier abord il paraîtra compliqué pour utilisateurs venant d’autre SCM (tel que subversion) mais ce workflow a été éprouvé par la communauté, fonctionne dans la majorité des cas et permet d’introduire dans le référentiel de source des notions utiles pour le CI/CD en fournissant des indication sur la destination du code par la branche sur laquelle il se trouve et la présence d’un tag ou pas.

L’objectif à retenir de ce workflow est qu’on veut produire sur la branche master le code le plus stable qui soit c’est-à-dire ayant passé toutes les vérifications, au moins manuelles de chaque développeur sa branche, puis d’intégration avec les fusions régulières sur la branche develop et enfin sur la branche de livraison où on stabilise la version qui va être fusionnée sur la branche master. La finalité du code sur master étant de partir en production, on estampille après la fusion de release sur master avec un tag (notion Git) qui versionne le code un nom lisible pour les humains.

Notons bien que à aucun moment la branche ne porte mention de l’environnement de d’exécution cible.

Gitlab est une application web qui offre un dépôt à Git et une mécanisme de CI/CD. Gitlab est un produit reconnu dans le monde Agile/DevOps. Dans cet article on utilise la version communautaire (gratuite) téléchargeable et installable dans son propre environnement.

Par ailleurs si le respect du workfow ainsi défini relève en premier lieu de la discipline des acteurs, Gitlab permet de renforcer certain aspect comme l’interdiction de commit direct sur la branche master et des outils tel que gitflow permettent d’automatiser quelque peu la discipline de création de branche avant tout travail sur le code source ainsi que la préparation des branche de livraison (release).

Pipeline gitlab

Le pipeline GItlab est défini dans un fichier .gitlab-ci.yml dans le code source du projet. Le pipeline est constitué par des tâches regroupés dans des étapes. Les étapes permettent de poser un ordre d’exécution. Ainsi les étapes suivantes sont créées par défaut:

  1. build
  2. test
  3. deploy

Les tâches de l’étape build se déroulent toutes avant celles de test qui se déroulent toutes avant celles de deploy. Les étapes sont redéfinissables dans le fichiers .gitlab-ci.yml.

A l’intérieur d’une étapes, les tâches se déroulent en parallèle mais une relation de précédence peut être définie. On peut conditionner ainsi une tâche de test qui requiert des ressources importantes (par un test de charge) par une tâche de test nominale plus rapide (vérification du lancement de l’application).

Pour accélérer le pipeline une tâches d’une étape peut explicitement déclarer requérir une tâche d’une étape précédente afin d’être lancée sans attendre la complétude de l’ensemble des tâches de l’étape précédente.

La richesse de ces possibilités permet de définir un pipeline de tâches flexible selon le besoin de chaque projet.

Cependant notre choix du pipeline GItlab est guidé par les contraintes suivantes:

Les étapes du pipeline

Les étapes par défaut build, test, deploy sont presque suffisantes. Je préfère intercaler une 4e entre le test et le déploiement: la revue. En effet au cours du développement une étape de revue est nécessaire. Elle permet d’effectuer des vérifications humaines dans un environnement d’exécution et que je distingue de l’étape test qui regroupe les taches de tests automatisés.

L’étape deploy disparait au profit d’une étape deliver qui correspond soit à un déploiement dans un environnement (ex: code sur la branche master) soit à un passage à l’étape suivante du gitflow par un merge (ex: code sur une branche de bug) ou un fork (ex: code sur la branche develop).

Je rajoute également une étape hotfix afin d’introduire dans le CI/CD la notion du même nom vue dans le workflow Git.

Les étapes du pipeline du CI/CD sont donc:

  1. buid : compilation
  2. test : déroulement des test automatisés
  3. review : déroulements des tests en déploiement « réel » ie exécution réel sur un environnement dans le cas d’une’application.
  4. deliver : mise à disposition « publique » de l’application. C’est la publication d’une version.
  5. hotfix : correction d’une version publiée donc potentiellement en production.

En fonctions de la branche le contenu des étapes change. Ainsi on a :

étape /branchefeature develop releasetag/masterhotfix
buildcompilationcompilationcompilationcompilationcompilation
testtests automatiséstests automatiséstests automatiséstests automatiséstest automatisés
reviewsur environnement dédié temporaire pour la branche (auto)

sur environnement dédié temporaire pour le commit (manuel)
sur environnement dédié permanent develop (auto)

sur environnement dédié temporaire pour le commit (manuel)
sur environnement dédié temporaire release (auto)

sur environnement dédié permanent staging (auto)

sur environnement dédié temporaire à la branche (auto)

sur environnement dédié temporaire pour le commit (manuel)
delivermerge sur develop (manuel)branche release (manuel)merge sur develop (manuel)


merge sur develop + merge et tag sur master (manuel)
déploiement en production (auto ou manuel par précaution)merge sur develop + merge et tag sur master (manuel)
hotfixbranche hotfix
Pipeline piloté par la branche

Ce qui pilote le pipeline c’est la branche d’où provient le commit. Pour rappel dans le workflow git l’ordre chronologique d’utilisation des branches est:

  1. feature
  2. develop
  3. release
  4. tag/master
  5. hotfix

feature

C’est une branche de travail pour ajouter une évolution. C’est la première étape du développement. Le code est en cours d’écriture dans cette branche isolée issue de develop. L’objectif de cette branche est de développer une fonctionnalité qui sera intégrée dans develop.

Les étapes de build et test contiennent les tâches intuitives de compilation et de tests automatisés. L’intérêt de maintenir même en développement des tests automatisé et d’être prévenu au plus tôt des régression. Si le temps d’exécution des tests est très importants (plusieurs dizaines de minutes) on peut affiner en les séparant par tâches dont certaines ne seront pas activées sur les branches feature.

L’étape de revue (review) permet de publier le résultat de la compilation (librairie ou application) afin d’être inspecté par des paires. Le code est commun, une branche feature n’appartient pas à une personne en particulier. Plusieurs personnes peuvent y travailler. Même celles qui n’y travaille pas directement (en regardant le source) peuvent regarder le résultat et cela sans avoir à passer par la case compilation/exécution sur son propre poste de travail. L’étape review et à disposition l’application ou la librairie pour une utilisation tierce. Dans le cas d’une application c’est un déploiement dans un environnement temporaire (rendu possible par la conteneurisation ou la virtualisation) et dans le cas d’une librairie cela se traduit pas un dépôt dans un entrepôt de librairies en cours de développement.

On distingue un déploiement en automatique dans l’environnement temporaire associé à la branche feature donné. Le contenu de environnement est alors écrasé par la dernière version de la branche à chaque commit sur celle-ci. Il y a aussi un déploiement manuel du commit afin d’avoir si besoin un environnement de revue pour le commit donnée (de la branche). Ceci est utile si on veut faire valider un travail intermédiaire tout en continuant à pousser sur la branche feature.

L’étape deliver pour une branche feature est l’étape finale qui pour rappel est l’intégration du code dans la branche develop. La tâche principale de cette étape du pipeline est donc une manipulation Git de fusion de la branche feature sur develop

L’étape hotfix n’existe pas pour la branche feature.

develop

La branche develop recueille les fonctionnalités et correction pour la future version du projet. Elle est issue de la branche master qui à l’origine du projet est vide e tout code. Les commit sur cette branche proviennent uniquement des fusions depuis d’une part, les branches feature et d’autre part, les branches release et hotfix qui seront vues plus loin. L’objectif de la branche devlop est de récolter un ensemble de fonctionnalités définies dans le périmètre su sprint agile.

Les étapes de build et test contiennent les tâches intuitives de compilation et de tests automatisés. Ces derniers seront tous déroulés y compris ceux qui sont coûteux en temps.

L’étape review consite à déployer automatiquement dans un environnement permanent (appelé develop) la dernière version de la branche develop. Un déploiement manuel permet de publier un commit particulier dans un environnement temporaire dont l’utilité est de revenir sur un commit passé pour exclure les fusions postérieures.

Dès la branche develop l’étape de revue inclus des test de type QA (tests d’IHM etc) car comme l’envirionnement est permanent, les infrastructures nécessaires peuvent être mises en place.

L’étape deliver pour une branche develop consiste à envoyer un recette un lot de fonctionnalité jugé satisfaisant car ayant passé tous les test automatisé et les test de type QA dans l’environnement de revue. La tâche principale de cette étape est donc la création d’une branche release à partir de la version jugée satisfaisante de la branche develop.

L’étape hotfix n’existe pas pour la branche feature.

release

La branche release a pour objecif de préparer version de l’application pour une livraison. C’est une branche issue de develop qui inclus un nombre de fonctionnalité restreinte à un périmètre jugé suffisant pour une livraison.

Les étapes de build et test contiennent les tâches intuitives de compilation et de tests automatisés. Ces derniers seront tous déroulés y compris ceux qui sont coûteux en temps.

L’étape review consite à déployer automatiquement dans un environnement permanent (appelé recette) la dernière version de la branche release. En effet la découverte de bug remonté par les test de types QA (si non fait sur develop) ou par la recette MOA (si non automatisée….) donnera lieu à une correction sur la branche release. Des déploiements successifs peuvent donc avoir lieu.

Suite à ces corrections éventuelles, dans l’étape suivante deliver pourra lancer les tâches de fusion retour vers develop. En fin de recette on valide la version de l’application qui est sur la branche release. Cela consiste en:

La pose du tag sur master est la condition sinequanone du déploiement en production.

L’étape hotfix n’existe pas pour la branche feature.

master avec tag

La branche master contient le code le plus stable qui soit. Il a passé tous les tests y compris la validation MOA en recette. Ce code est éligible à un déploiement en production.

Le tag est posé sur la branche master du code source. Le pipeline ne se déclenche que si le tag est de la bonne forme disons : v-xxxx.

Les étapes de build et test contiennent les tâches intuitives de compilation et de tests automatisés. Ces derniers seront tous déroulés y compris ceux qui sont coûteux.

L’étape review se décline pour branche master en un déploiement en environnement iso-prod permanent appelé staging. Il s’agit de vérifier une toute dernière fois que le déploiement en production va bien se passer et d’effectuer également des tests de performance dans un environnement iso-prod en terme de capacité.

Enfin l’ultime étape deliver contient le déploiement effectif en production. Les projets pourront choisir de rendre cette dernière étape manuelle par précaution si elles n’ont pas pleine confiances dans les phase de validation en recette ou en staging.

hotfix

La branche de hotfix est tirée à partir du tag d’une version en production donc est provient de master. Le but de cette branche est d’apporter des correctifs qui seront directement envoyés en production sans passer par l’étape de recette (branche release). Les correctifs sont soit urgents ou soit n’impactant pas l’étape de recette. Dans tous les cas la correction doit être rapide (quelques heures ou quelques jours). Sinon même si la bug est constaté en production et que la correction nécessite investigation ou des développements longs ayant pour conséquence la nécessité de faire des tests complets alors il faut renoncer au hotfix et ouvrir une branche de bug classique (branche feature –> branche develop –>branche master avec tag).

Le pipeline se décline de la manière suivante pour la branche hotfix.

Les étapes de build et test contiennent les tâches intuitives de compilation et de tests automatisés. Ces derniers seront tous déroulés y compris ceux qui sont coûteux en temps.

L’étape review consiste à déployer automatiquement dans un environnement tempporaire la dernière version de la branche hotfix. Un déploiement manuel permet de publier un commit particulier dans un environnement temporaire dont l’utilité est de revenir sur un commit passé pour exclure les commit postérieures. On voir que la branche hotfix doit être vue comme une branche qui ne prend pas les développements en cours (branche develop) mais uniquement la version déjà en production à laquelle on apporte des correctifs. Cela ne veut pas dire que le code de hotfix n’est pas testé ou mis en revue !

Quand la correction est satisfaisante, l’étape deliver va consister à fusionner avec develop et master avec pose d’un tag. L’étape deliver ressemble donc à celle de l’étape review de la branche release. Au détail près que ce n’est que sur la validation complète du correctif que l’on fusionne avec develop et master.

Branche master avec ou sans tag ?

Dans les étapes du pipeline on remarque que la branche master n’apparaît que si on y pose un tag. Le pipeline n’est pas prévu pour se déclencher sur la branche master quand il n’y a pas de tag. Ce choix est-il judicieux ou bien aurait-on un intérêt à avoir un pipeline sur master quand il n’y a pas de tag ?

La réponse est dans l’usage de la branche master. Revenons à la définition: branche qui contient les versions les plus stables du code dont chacune est éligible à une mise en production. La subtilité réside dans le éligible. Cela veut dire qu’il y a des commit (version) sur master qui n’ont pas été mis en production. SI ces versions ont un tag alors le problème ne se pose pas. Si ces versions n’ont pas de tag alors comme elles proviennent chacune branche release, si jamais ont veut les envoyer en production, il faudra y poser le tag correspondant à la branche release d’où ces versions proviennent. Comme nous n’avons plus cette information, on pourrait taguer un commit avec une release qui n’est pas la sienne !

Admettons maintenant que l’on pose cette information dans le source qui va sur master ou par un tout autre moyen qu’on résolve le problème. On peut alors garder sur master une version du code sans tag et qui de ce fait ne peut pas aller en production, puis à l’étape deliver du pipeline on peut choisir de mettre en production en ajoutant un tag. On a alors le tableau suivant:

étape /branche releasemastertag
buildcompilationXcompilation
testtests automatisésXtests automatisés
reviewsur environnement dédié temporaire release (auto)

sur environnement dédié temporaire (manuel et optionnel)sur environnement dédié permanent staging (auto)

delivermerge sur develop (manuel)

merge sur develop + merge sur master (manuel)

merge sur develop + merge et tag sur master (manuel)
tagdéploiement en production (auto)
hotfixbranche hotfix
pipeline pour master entre la release et le tag

Pour peu que l’on est un moyen de bien poser le tag associé à la release, on obtient alors une temporisation entre la fin de la recette et le mise en production. Pour assurer la bonne association entre release et tag, on pourra poser tag distinct de celui adopté pour le pipeline des tag de version. Par exemple quand la branche release-x.y.z est validée alors on aura:

  1. tag rc-x.y.z sur master : juste pour stockage sur master
  2. puis tag v-x.y.z sur matser, déclenchement du pipeline complet jusqu’à la mise en production possible.
étape /branche releasemastertag version
buildcompilationXcompilation
testtests automatisésXtests automatisés
reviewsur environnement dédié temporaire release (auto)

sur environnement dédié temporaire (manuel et optionnel)sur environnement dédié permanent staging (auto)

delivermerge sur develop (manuel)

merge sur develop + merge et tag release sur master (manuel)

merge sur develop + merge et tag version sur master (manuel)
tag versiondéploiement en production (auto)
hotfixbranche hotfix
pipeline pour master entre la release et le tag avec temporisation

Automatisation complète

En consultant le premier tableau on voir plusieurs étapes manuelles. Celles de l’étape deliver nous interessent ici car ce sont elles qui conditonnent le passage à la banche suivante ou le déploiement.

Dans CI/CD on dit seulement que les choses doivent se faire en continu mais pas en automatique…. le moyen le plus efficace de rendre continu le processus est de le rendre automatique just in time c’est-à-dire dès que c’est possible. Examinons ce que cela implique pour chacune des étapes du pipeline.

build et test

L’exécution de la compilation et des tests doivent se faire sans intervention humaines. Il est clair que ces possibles si on parle des tests unitaires, des tests de couvertures. On reste proche du code en étant indépendant d’entrées extérieures non déjà incluses dans le code.

review

Elle consiste à déployer et éxecuter l’application dans un environnement pour soumission à un contrôle humain. C’est pour cela que l’étape suivante Deliver est manuelle dans le pipeline. Intéressons-nous à l’étape Review pour chaune des branches pour voir ce que son automatisation implique.

feature

Il faut pouvoir vérifier que la fonctionnalité est implémentée. Dans les cas simples il peut ne s’agir que d’une modification de code qui peut être testé et vérifié par des moyens triviaux de tests unitaires. Dans la plupard des cas malheureusement une fonctionnalité est une expression de besoin émanant d’un non informaticien et exprimée dans un langage humain. L’emploi de méthode comme le BDD (Behavior Drive, Developement) peut permettre de spécifier avant tout code ce que l’on attend de la fonctionnalité. A l’aide d’outils de vérifications tels que Coccumber on peut automatiser ce processus dans l’étape de de revue pour la branche feature (la revue automatisée est un test qui pourrait être remonté dans l’étape du même non).

A la création de la branche feature un tes non passant doit automatiquement être ajouté au code source afin de bloquer le pipeline. Puis on enrichie ce test non passant pour qu’il corresponde à la fonctionnalité attendue. Enfin on ajoute la fonctionnalité. Les étapes de test et de revue finissent par passer.

L’étape suivante du pipeline deliver qui consiste à fusionner la branche feature dans develop pourra à son tour devenir automatique dès lors que la revue est validée automatiquement.

develop

La revue sur la branche develop consiste à vérifier que les fonctionnalités développées sont bien présentes (on a vue que précédemment comment) et qu’elle sont toutes présentes pour pouvoir être embarquées dans la livraison atendue. En clair la revu de develop vérifie que toutes les fonctionnalités attendues sont là. Cela ressemble à une simple vérification de la complétude des fonctionnalités. En méthodes agiles, la complétude n’est même pas requise, la livraison doit avoir lieu avec ce qui a été developpés puisque à intervalle régulier on livre quoi qu’il arrive, même si on n’a pas tout développé. Cela montre l’importance des étapes test et review de la branche feature.

La review de develop peut donc être automasée et son étape deliver rendu automatique car guidée principalement par l’horloge du sprint en méthode agile. SI l’on est pas en agile, on ajoutera comme condition le fait que toutes les fonctionnalités sont présentes.

release

Il s’agit de la recette de l’application. On imagine bien à cette étape du développement que la revue consiste en une validation de la MOA lors d’une présentation de l’application. Automatiser cette étape imminement humaine et contractuelle semble une gageure. Heureusement qu’elle devrait être une formalité si méthodes et outillages de vérification ont été appliqués dans les branches précédentes… sinon une validation humaine manuelle est nécessaire. C’est l’étape la plus difficile à automatiser ne serait-ce que pour son aspect contractuel.

SI on arrive à automatiser le travail de validation de la recette, par exemple en poussant l’usage de la méthode au maximun comme suit:

On pourra alors rendre automatique l’étape deliver du pipeline !

tag/master

L’étape de revu eest un déploiement dans environnement iso-prod où des tests de performance pourront avoir lieu. Ces tests pourront être automatisés, à moins qu’une procédure manuelle subsiste. L’effort consistera à automatiser toutes procédures.

On pourra alors rendre automatique l’étape deliver du pipeline ! déploiement en production !

hotfix

Le pipeline de la branche hotfix ressemble à celui de la branche release. Cependant son étape Review ne fait pas intervenir d’élément contractuel de validation. La branche hotfix a pour but de corriger une version déjà en production quand la correction est rapide, simple et ne remaitant pas e cause la recette précédente (ie pas de modification/suppression ou d’ajout de fonctionnalités).

Dans ce cas l’utilisation de test unitaire pourra suffir. Il faudra appliquer la même création de test non passant dès la création de la branche hotfix afin de bloquer l’étape de revue. Son résultat sera positif dès lors que le code correctif sera ajouté.

On pourra alors rendre automatique l’étape deliver du pipeline !

deliver et hotfix

L’automatisation des tâches de ces étapes ne pose pas de difficultés.

Environnement

Nous comptons au minimum 3 environnements: l’intégration qui reçoit le code en développement, la recette qui reçoit le code d’un branche release et la production qui reçoit le code validé par la recette. Parmis ces envrionnements celui de la branche release (recette) est temporaire alors que les autres sont permanent.

Lorsque le projet s’y prête comme c’est le cas d’une application conteneurisée, on peut multiplier à l’infini les environnements. Gitlab offre la possibilité de lié ces environnement à une version du code. Couplé au pipeline du CI/CD on alors un puissant outils pour créer à la demande des environnement de revue.

POC

Pour notre POC nous prenons en exemple une application docker lancée par docker-compose. Le code source est constitué principalement d’un fichier docker-compose ce qui permettra de supprimer la phase de construction (pas de compilation nécessaire) et de simplifier le déploiement en supprimant la phase de production/récupération d’artefact car le code source se suffit à lui-même (la fonction compilation est la fonction identité).

Le code source du fichier gitlab-ci.yml est visible ici:

image:     
    name: docker/compose:1.27.4
    entrypoint: [""]
  
before_script:
    - docker info
    - docker-compose version
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY

stages:
    # compilation 
    - build
    # tests automatisés
    - test
    # déploiement pour tests humains, qa, review, load test
    - review
    # deploiement pour run
    - deliver
    # maintenance
    - hotfix
    

run_gitlab:
   stage: test
   tags:
     - docker
     - infra
     - regular


   script:
     - echo "run $CI_PROJECT_NAME  for testing"
     - docker-compose -p ${CI_PROJECT_NAME}_${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA} --env .env.local.sample up -d
     - docker-compose -p ${CI_PROJECT_NAME}_${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA} --env .env.local.sample ps
     - docker-compose -p ${CI_PROJECT_NAME}_${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA} --env .env.local.sample down
   except:
     - master
    


run_gitlab_with_dind:
    stage: test
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/' 
        - if: '$CI_COMMIT_TAG =~ /^release-.*$/'        
        
    tags:
        - dind
        - infra
        - regular
        
    variables:
        # For non-Kubernetes executors, we use tcp://docker:2375/
        DOCKER_HOST: tcp://docker:2375/
        DOCKER_TLS_CERTDIR: ""
        # When using dind, it's wise to use the overlayfs driver for
        # improved performance.
        DOCKER_DRIVER: overlay2
    services:
        - name: docker:dind     
          entrypoint: ["/bin/sh","-c" ,'echo "192.168.122.1 registry-backup.bressure.net registry-proxy.bressure.net">>/etc/hosts && dockerd-entrypoint.sh --insecure-registry registry-backup.bressure.net:5000 --insecure-registry registry-proxy.bressure.net:5000 --registry-mirror https://registry-proxy.bressure.net:5000' ]       

        
    script:
       - echo "run gitlab for testing"
       - docker-compose -p ${CI_PROJECT_NAME}_${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA} --env .env.local.sample up -d
       - docker-compose -p ${CI_PROJECT_NAME}_${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA} --env .env.local.sample ps
       - docker-compose -p ${CI_PROJECT_NAME}_${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA} --env .env.local.sample down




deploy_to_staging:
    stage: review
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/' 
    tags:
        - infra
        - docker
        - regular
    script:
        - echo "deploy $CI_PROJECT_NAME to staging"
        - docker-compose pull
        - docker-compose  -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_NAME} --env-file .env.staging -f docker-compose.yml -f docker-compose.traefik.externe.yml config
        - docker-compose  -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_NAME} --env-file .env.staging -f docker-compose.yml -f docker-compose.traefik.externe.yml up -d --remove-orphans
  
    environment:
        name: staging   
        url: https://staging.bressure.net:444/
        on_stop: stop_staging


stop_staging:
    stage: review
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/'
          when: manual
          allow_failure: true
    tags:
        - infra
        - docker
        - regular
  
    variables:
        GIT_STRATEGY: none
    script:
        - echo "Remove gitlab"
        - docker-compose  -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_NAME} --env-file .env.staging -f docker-compose.yml -f docker-compose.traefik.externe.yml  config
        - docker-compose  -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_NAME} --env-file .env.staging -f docker-compose.yml -f docker-compose.traefik.externe.yml  down 

    environment:
        name: staging
        action: stop

deploy_to_production:
    stage: deliver
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/'
          when: manual
          allow_failure: true
    tags: 
        - infra
        - docker
        - regular
    script: 
        - echo "deploy $CI_PROJECT_NAME to $CI_ENVIRONMENT_NAME"
        - docker-compose pull
        - docker-compose  -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_NAME} --env-file .env.prod -f docker-compose.yml -f docker-compose.traefik.externe.yml  config
        - docker-compose  -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_NAME} --env-file .env.prod -f docker-compose.yml -f docker-compose.traefik.externe.yml  up -d --remove-orphans


    environment:
        name: production        
        url: https://gitlab.bressure.net
        on_stop: stop_production

stop_production:
    stage: deliver
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/'
          when: manual
          allow_failure: true
    tags:
        - infra
        - docker
        - regular

    variables:
        GIT_STRATEGY: none
    script:
        - echo "Remove $CI_PROJECT_NAME"
        - docker-compose  -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_NAME} --env-file .env.prod -f docker-compose.yml -f docker-compose.traefik.externe.yml  config
        - docker-compose  -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_NAME} --env-file .env.prod -f docker-compose.yml -f docker-compose.traefik.externe.yml  down 


    environment:
        name: production
        action: stop




.deploy_review: &deploy_review
    - echo "Deploy a review app"
    - docker-compose pull
    - pwd && ls -la
    - echo "CI_ENVIRONMENT_SLUG" ":" "$CI_ENVIRONMENT_SLUG"
    - export TRAEFIK_HOST=${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}.bressure.net
    - export TRAEFIK_REGISTRY_HOST=registry_${CI_ENVIRONMENT_SLUG}.bressure.net
    - export TRAEFIK_ROUTER_NAMES=${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}
    - export HOST_VOLUME=/srv/${CI_PROJECT_NAME}/${CI_ENVIRONMENT_SLUG}
    - docker-compose -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG} --env-file .env.staging -f docker-compose.yml -f docker-compose.traefik.externe.yml config
    - docker-compose -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG} --env-file .env.staging -f docker-compose.yml -f docker-compose.traefik.externe.yml up -d --remove-orphans

  

deploy_to_develop:
  stage: review
  tags:
    - infra
    - docker
    - regular
  script:
     - *deploy_review

  environment:
    name: develop
    url: https://${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}.bressure.net:444
    on_stop : stop_review_develop   
  only:
    - develop



review_commit:
  stage: review
  tags:
    - infra
    - docker
    - regular
  script:
     - *deploy_review

  environment:
    name: review/${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHORT_SHA}
    url: https://${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}.bressure.net:444
    on_stop : stop_review_commit   
    auto_stop_in: 1 hour   
  when: manual
  except:
      - master
      - tags
      - /^release-.*$/  
    


deploy_to_recette:
  stage: review
  tags:
    - infra
    - docker
    - regular
  script:
     - *deploy_review

  environment:
    name: recette
    url: https://${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}.bressure.net:444
    on_stop : stop_recette
    auto_stop_in: 1 day
  only:    
    - /^release-.*$/       

review_release:
  stage: review
  tags:
    - infra
    - docker
    - regular
  script:
     - *deploy_review
  when: manual
  environment:
    name: review/$CI_COMMIT_TAG
    url: https://${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}.bressure.net:444
    on_stop : stop_review_release
    auto_stop_in: 1 hour
  only:    
    - /^rc-.*$/ 
    - /^v-.*$/   


review_branch:
  stage: review
  tags:
    - infra
    - docker
    - regular
  script:
     - *deploy_review

  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: https://${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}.bressure.net:444
    on_stop : stop_review_branch    
    auto_stop_in: 1 hour
  except:    
    - /^release-.*$/       
    - develop
    - master
    - tags


.undeploy_review: &undeploy_review
    - echo "Stop review app "
    - echo "CI_ENVIRONMENT_SLUG" ":" "$CI_ENVIRONMENT_SLUG"
    - export TRAEFIK_HOST=${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}.bressure.net
    - export TRAEFIK_ROUTER_NAMES=${CI_ENVIRONMENT_SLUG}_${CI_PROJECT_NAME}
    - export HOST_VOLUME=/srv/${CI_PROJECT_NAME}/${CI_ENVIRONMENT_SLUG}
    - docker-compose -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG} --env-file .env.staging -f docker-compose.yml -f docker-compose.traefik.externe.yml config
    - docker-compose -p ${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG} --env-file .env.staging -f docker-compose.yml -f docker-compose.traefik.externe.yml down


stop_review_branch:
    stage: review
    tags:
        - infra
        - docker
        - regular

    variables:
        GIT_STRATEGY: fetch
    script:
        - *undeploy_review
    when: manual
    environment:
        name: review/$CI_COMMIT_REF_NAME
        action: stop
    only:
       - branches
    except:
       - master
       - tags
       - /^release-.*$/       
       - develop
       

stop_review_develop:
    stage: review
    tags:
        - infra
        - docker
        - regular

    variables:
        GIT_STRATEGY: fetch
    script:
        - *undeploy_review
    when: manual
    environment:
        name: develop
        action: stop
    only:
       - develop

stop_recette:
    stage: review
    tags:
        - infra
        - docker
        - regular

    variables:
        GIT_STRATEGY: fetch
    script:
        - *undeploy_review
    when: manual
    environment:
        name: recette
        action: stop
    only:
       - /^release-.*$/  


stop_review_release:
    stage: review
    tags:
        - infra
        - docker
        - regular

    variables:
        GIT_STRATEGY: fetch
    script:
        - *undeploy_review
    when: manual
    environment:
        name: review/$CI_COMMIT_TAG
        action: stop
    only:
       - /^rc-.*$/  
       - /^v-.*$/         


stop_review_commit:
    stage: review
    tags:
        - infra
        - docker
        - regular

    variables:
        GIT_STRATEGY: fetch
    script:
        - *undeploy_review
    when: manual
    environment:
        name: review/${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHORT_SHA}
        action: stop
    except:
      - master
      - tags
      - /^release-.*$/  
       

.clone_repos_with_authent: &clone_repos_with_authent
        - TEMP_DIR=`mktemp -d`
        - cd $TEMP_DIR
        - URL_WITH_AUTHENT=`echo ${CI_REPOSITORY_URL} | sed -e "s/gitlab-ci-token:.*@/project_1_bot\:${CI_CD_TOKEN}@/g"`
        - echo $URL_WITH_AUTHENT        
        - git clone $URL_WITH_AUTHENT
        - cd $CI_PROJECT_NAME

prepare_release:
    stage: deliver
    image:
       name:  alpine/git 
       entrypoint: [""]    

    tags:
        - infra
        - docker
        - regular
    before_script:
        - git --version
        - echo $RELEASE
        - if [ -z "$RELEASE" ]; then echo "RELEASE is not defined"; exit 1; fi
    script:                
        - *clone_repos_with_authent
        - git checkout -b release-${RELEASE} $CI_COMMIT_SHA
        - echo "TODO modifications eventuelles du code source"
        - git push --set-upstream origin release-${RELEASE}
    when: manual
    only:
        - develop




.merge_commit_sha_to_develop: &merge_commit_sha_to_develop
        - git checkout develop
        - git merge $CI_COMMIT_SHA
        - git push


merge_to_develop:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    tags:
        - infra
        - docker
        - regular
    before_script:
        - git --version
    script:        
        - *clone_repos_with_authent
        - *merge_commit_sha_to_develop
        - echo "TODO delete branch if feature"        
    when: manual
    except:
        - develop
        - master
        - tags




prepare_hotfix:
    stage: hotfix
    image:
        name:  alpine/git 
        entrypoint: [""]    
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/'
          when: manual
          allow_failure: true                    
    tags:
        - infra
        - docker
        - regular
    before_script:
        - git --version
        - echo $HOTFIX
        - if [ -z "$HOTFIX" ]; then echo "HOTFIX is not defined"; exit 1; fi        
    script:
        - *clone_repos_with_authent
        - git checkout -b hotfix-${HOTFIX} $CI_COMMIT_TAG
        - echo "TODO modifications eventuelles du code source"
        - git push --set-upstream origin hotfix-${HOTFIX}



.merge_commit_sha_to_master: &merge_commit_sha_to_master
        - git checkout master
        - git merge $CI_COMMIT_SHA
        - git push

.merge_commit_sha_and_tag_master_with_version: &merge_commit_sha_and_tag_master_with_version
        - *merge_commit_sha_to_master
        - BRANCH_SUFFIX=`echo $CI_COMMIT_BRANCH | cut -d - -f 1`
        - git tag `echo $CI_COMMIT_REF_NAME | sed -e "s/$BRANCH_SUFFIX/v/g"`
        - git push --tag

.merge_commit_sha_and_tag_master_with_release: &merge_commit_sha_and_tag_master_with_release
        - *merge_commit_sha_to_master
        - BRANCH_SUFFIX=`echo $CI_COMMIT_BRANCH | cut -d - -f 1`
        - git tag `echo $CI_COMMIT_REF_NAME | sed -e "s/$BRANCH_SUFFIX/rc/g"`
        - git push --tag

.create_version_from_release: &create_version_from_release        
        - RC_SUFFIX=`echo $CI_COMMIT_TAG | cut -d - -f 1`
        - git tag `echo $CI_COMMIT_TAG | sed -e "s/$RC_SUFFIX/v/g"`
        - git push --tag



release_version:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    tags:
        - infra
        - docker
        - regular
    before_script:
        - git --version
    script:
        - *clone_repos_with_authent
        - *merge_commit_sha_to_develop
        - *merge_commit_sha_and_tag_master_with_version
        - echo "TODO delete release branch"

    when: manual
    only:
        - /^release-.*$/
        - /^hotfix-.*$/


release_candidate:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    tags:
        - infra
        - docker
        - regular
    before_script:
        - git --version
    script:
        - *clone_repos_with_authent
        - *merge_commit_sha_to_develop
        - *merge_commit_sha_and_tag_master_with_release
        - echo "TODO delete release branch"

    when: manual
    only:
        - /^release-.*$/


validate_release_candidate:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    tags:
        - infra
        - docker
        - regular
    before_script:
        - git --version
    script:
        - *clone_repos_with_authent
        - *create_version_from_release        
    when: manual
    only:
        - /^rc-.*$/


validate_hotfix:
    stage: deliver
    tags:
        - infra
        - docker
        - regular
    image:
        name:  alpine/git 
        entrypoint: [""]    
    before_script:
        - git --version              
    script:        
        - *clone_repos_with_authent
        - *merge_commit_sha_to_develop
        - *merge_commit_sha_and_tag_master_with_version
        - echo "TODO delete release branch"

    when: manual
    only:
        - /^hotfix-.*$/




Pour un test du workflow présenté dans cet article et dépouvu de toutes adhérence au projet du POC, j’ai épurer le code du CI/CD pour en faire un squelette fonctionnel en l’état et utilisable dans d’autres porjets.

Squelette du fichier gitlab-ci.yml est visible ici:

image:     
    name: bash
    entrypoint: [""]

variables:
   ROOT_DOMAIN: bressure.net
   MAIN_BRANCH: main
   BOT_NUMBER: ""
   


stages:
    # compilation 
    - build
    # tests automatisés
    - test
    # déploiement pour tests humains, qa, review, load test
    - review
    # deploiement pour run
    - deliver
    # maintenance
    - hotfix
    
build:
   stage: build
   script:
     - echo "build $CI_PROJECT_NAME"
   

test_for_every_one:
   stage: test
   script:
     - echo "basic test for ${CI_PROJECT_NAME}"
     - echo "should do here only test that are strictly required for ***every** build"
   except:
     - ${MAIN_BRANCH}
    


test_for_release:
    stage: test
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/' 
        - if: '$CI_COMMIT_TAG =~ /^release-.*$/'        
        
        
    script:       
       - echo "release test for ${CI_PROJECT_NAME}"
       - echo "should do here deepest test that should be ran only on release and final version"


deploy_to_staging:
    stage: review
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/' 
    script:
        - echo "deploy $CI_PROJECT_NAME to $CI_ENVIRONMENT_NAME"
        
    environment:
        name: staging   
        url: https://${CI_PROJECT_NAME}.${ROOT_DOMAIN}:444/
        on_stop: stop_staging


stop_staging:
    stage: review
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/'
          when: manual
          allow_failure: true
    script:
        - echo "undeploy $CI_PROJECT_NAME from $CI_ENVIRONMENT_NAME"
    environment:
        name: staging
        action: stop

deploy_to_production:
    stage: deliver
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/'
          when: manual
          allow_failure: true
    script: 
        - echo "deploy $CI_PROJECT_NAME to $CI_ENVIRONMENT_NAME"

    environment:
        name: production        
        url: https://${CI_PROJECT_NAME}.${ROOT_DOMAIN}
        on_stop: stop_production

stop_production:
    stage: deliver
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/'
          when: manual
          allow_failure: true

    script:
        - echo "undeploy $CI_PROJECT_NAME from $CI_ENVIRONMENT_NAME"

    environment:
        name: production
        action: stop




.deploy_review: &deploy_review
    - echo "deploy $CI_PROJECT_NAME to $CI_ENVIRONMENT_NAME"
    
deploy_to_develop:
  stage: review
  script:
     - *deploy_review

  environment:
    name: develop
    url: https://${CI_PROJECT_NAME}_${CI_ENVIRONMENT_SLUG}.${ROOT_DOMAIN}:444
    on_stop : stop_review_develop   
  only:
    - develop



review_commit:
  stage: review
  script:
     - *deploy_review

  environment:
    name: review/${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHORT_SHA}
    url: https://${CI_ENVIRONMENT_SLUG}.${CI_PROJECT_NAME}.${ROOT_DOMAIN}:444
    on_stop : stop_review_commit   
    auto_stop_in: 1 hour   
  when: manual
  except:
      - ${MAIN_BRANCH}
      - tags
      - /^release-.*$/  
    


deploy_to_recette:
  stage: review
  script:
     - *deploy_review

  environment:
    name: recette
    url: https://${CI_ENVIRONMENT_SLUG}.${CI_PROJECT_NAME}.${ROOT_DOMAIN}:444
    on_stop : stop_recette
    auto_stop_in: 1 day
  only:    
    - /^release-.*$/       

review_release:
  stage: review
  script:
     - *deploy_review
  when: manual
  environment:
    name: review/$CI_COMMIT_TAG
    url: https://${CI_ENVIRONMENT_SLUG}.${CI_PROJECT_NAME}.${ROOT_DOMAIN}:444
    on_stop : stop_review_release
    auto_stop_in: 1 hour
  only:    
    - /^rc-.*$/ 
    - /^v-.*$/   


review_branch:
  stage: review
  script:
     - *deploy_review

  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: https://${CI_ENVIRONMENT_SLUG}.${CI_PROJECT_NAME}.${ROOT_DOMAIN}:444
    on_stop : stop_review_branch    
    auto_stop_in: 1 hour
  except:    
    - /^release-.*$/       
    - develop
    - ${MAIN_BRANCH}
    - tags


.undeploy_review: &undeploy_review
    - echo "undeploy $CI_PROJECT_NAME from $CI_ENVIRONMENT_NAME"


stop_review_branch:
    stage: review
    script:
        - *undeploy_review
    when: manual
    environment:
        name: review/$CI_COMMIT_REF_NAME
        action: stop
    only:
       - branches
    except:
       - ${MAIN_BRANCH}
       - tags
       - /^release-.*$/       
       - develop
       

stop_review_develop:
    stage: review
    script:
        - *undeploy_review
    when: manual
    environment:
        name: develop
        action: stop
    only:
       - develop

stop_recette:
    stage: review
    script:
        - *undeploy_review
    when: manual
    environment:
        name: recette
        action: stop
    only:
       - /^release-.*$/  


stop_review_release:
    stage: review
    script:
        - *undeploy_review
    when: manual
    environment:
        name: review/$CI_COMMIT_TAG
        action: stop
    only:
       - /^rc-.*$/  
       - /^v-.*$/         


stop_review_commit:
    stage: review
    script:
        - *undeploy_review
    when: manual
    environment:
        name: review/${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHORT_SHA}
        action: stop
    except:
      - ${MAIN_BRANCH}
      - tags
      - /^release-.*$/  
       

.clone_repos_with_authent: &clone_repos_with_authent
        - TEMP_DIR=`mktemp -d`
        - cd $TEMP_DIR
        - URL_WITH_AUTHENT=`echo ${CI_REPOSITORY_URL} | sed -e "s/gitlab-ci-token:.*@/project_${CI_PROJECT_ID}_bot${BOT_NUMBER}\:${CI_CD_TOKEN}@/g"`
        - echo $URL_WITH_AUTHENT        
        - git clone $URL_WITH_AUTHENT
        - cd $CI_PROJECT_NAME

prepare_release:
    stage: deliver
    image:
       name:  alpine/git 
       entrypoint: [""]    

    before_script:
        - git --version
        - echo $RELEASE
        - if [ -z "$RELEASE" ]; then echo "RELEASE is not defined"; exit 1; fi
    script:                
        - *clone_repos_with_authent
        - git checkout -b release-${RELEASE} $CI_COMMIT_SHA
        - echo "TODO modifications eventuelles du code source"
        - git push --set-upstream origin release-${RELEASE}
    when: manual
    only:
        - develop




.merge_commit_sha_to_develop: &merge_commit_sha_to_develop
        - git checkout develop
        - git merge $CI_COMMIT_SHA
        - git push


merge_to_develop:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    before_script:
        - git --version
    script:        
        - *clone_repos_with_authent
        - *merge_commit_sha_to_develop
        - echo "TODO delete branch if feature"        
    when: manual
    except:
        - develop
        - master
        - tags




prepare_hotfix:
    stage: hotfix
    image:
        name:  alpine/git 
        entrypoint: [""]    
    rules:
        - if: '$CI_COMMIT_TAG =~ /^v-.*$/'
          when: manual
          allow_failure: true                    
    before_script:
        - git --version
        - echo $HOTFIX
        - if [ -z "$HOTFIX" ]; then echo "HOTFIX is not defined"; exit 1; fi        
    script:
        - *clone_repos_with_authent
        - git checkout -b hotfix-${HOTFIX} $CI_COMMIT_TAG
        - echo "TODO modifications eventuelles du code source"
        - git push --set-upstream origin hotfix-${HOTFIX}



.merge_commit_sha_to_master: &merge_commit_sha_to_master
        - git checkout ${MAIN_BRANCH}
        - git merge $CI_COMMIT_SHA
        - git push

.merge_commit_sha_and_tag_master_with_version: &merge_commit_sha_and_tag_master_with_version
        - *merge_commit_sha_to_master
        - BRANCH_SUFFIX=`echo $CI_COMMIT_BRANCH | cut -d - -f 1`
        - git tag `echo $CI_COMMIT_REF_NAME | sed -e "s/$BRANCH_SUFFIX/v/g"`
        - git push --tag

.merge_commit_sha_and_tag_master_with_release: &merge_commit_sha_and_tag_master_with_release
        - *merge_commit_sha_to_master
        - BRANCH_SUFFIX=`echo $CI_COMMIT_BRANCH | cut -d - -f 1`
        - git tag `echo $CI_COMMIT_REF_NAME | sed -e "s/$BRANCH_SUFFIX/rc/g"`
        - git push --tag

.create_version_from_release: &create_version_from_release        
        - RC_SUFFIX=`echo $CI_COMMIT_TAG | cut -d - -f 1`
        - git tag `echo $CI_COMMIT_TAG | sed -e "s/$RC_SUFFIX/v/g"`
        - git push --tag



release_version:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    before_script:
        - git --version
    script:
        - *clone_repos_with_authent
        - *merge_commit_sha_to_develop
        - *merge_commit_sha_and_tag_master_with_version
        - echo "TODO delete release branch"

    when: manual
    only:
        - /^release-.*$/
        - /^hotfix-.*$/


release_candidate:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    before_script:
        - git --version
    script:
        - *clone_repos_with_authent
        - *merge_commit_sha_to_develop
        - *merge_commit_sha_and_tag_master_with_release
        - echo "TODO delete release branch"

    when: manual
    only:
        - /^release-.*$/


validate_release_candidate:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    before_script:
        - git --version
    script:
        - *clone_repos_with_authent
        - *create_version_from_release        
    when: manual
    only:
        - /^rc-.*$/


validate_hotfix:
    stage: deliver
    image:
        name:  alpine/git 
        entrypoint: [""]    
    before_script:
        - git --version              
    script:        
        - *clone_repos_with_authent
        - *merge_commit_sha_to_develop
        - *merge_commit_sha_and_tag_master_with_version
        - echo "TODO delete release branch"

    when: manual
    only:
        - /^hotfix-.*$/




Tags:

Laisser un commentaire

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

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Back to top