La plateforme d’intégration continue mobile d’Ineat s’articule autour du célèbre serveur d’intégration continue Jenkins, nous avons, dès sa release en version 2.0, pu profiter de nombreuses nouveautés et notamment le “Pipeline As Code”.

Après des mois d’utilisation, notamment sur des projets d’applications mobiles, nous avons tiré quelques bonnes pratiques et nous allons aborder un aspect en particulier dans cet article : la Shared Library.

Tout d’abord un rapide rappel, le pipeline sous Jenkins est un script Groovy qui permet de définir un workflow de build. Le pipeline a été introduit via un plugin dédié que vous retrouverez ici. De nombreuses fonctions y sont intégrées comme :

  1. echo “Votre message”
  2. sh “Votre script a lancer”
  3. git branch: “Nom de la branche”, credentialsId: “credential id”, url: “url du projet git”
  4. etc…

En plus des fonctions, nous pouvons utiliser toute la puissance du langage Groovy et notamment les DSL (Domain Specific Language).

Les plus connues sont les suivantes :

  1. node 
  2. stage(‘nom de votre stage’)

( retrouvez l’ensemble des informations sur les DSL Jenkins ici )

Suite à l’ajout de quelques scripts pipelines, j’ai remarqué que le workflow était sensiblement le même hormis les éléments suivants spécifiques au projet :

  1. URL du git
  2. Credential ID pour récupérer le projet

Dans les grandes étapes, voici un exemple du workflow:

// JOB jenkins

def gitUrl = "..."
def credentialsId = ""


node {

    stage('clean') {
        deleteDir()
    }

    stage('checkout') {
        def branch = 'master'
        if (env.getEnvironment().containsKey('BRANCH')) {
            branch = BRANCH
        }
        git branch: branch, credentialsId: credentialsId, url: gitUrl
    }

    stage('configure') {
        def branches = "master\ndevelop" // récupérer automatiquement les branches via un script sh git
        properties([
            parameters([
                choice(choices: branches, description: 'Branche de build', name: 'BRANCH'),
                booleanParam(defaultValue: false, description: 'Faire une release', name: 'RELEASE')
            ]),
            disableConcurrentBuilds(),
            buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '10')), 
            pipelineTriggers([pollSCM('@hourly')])
        ])
    }

    stage('build') {
        sh('./gradlew clean assemble')
    }

    stage('release') {
       // 1. Git flow release
       // 2. Push nexus OSS 
       // etc...
    }

}

 

Puis je me suis posé la question suivante :

 N’est-il pas possible de centraliser ce workflow et de l’importer sur tout mes projets mobiles ?

Après quelques recherches, j’ai trouvé qu’il était possible d’importer des projets contenant des classes, des fonctions utilitaires, des DSL etc… Cela se nomme les Shared Librairies.

Pour l’exemple de ce post, nous développerons un projet permettant d’appliquer une DSL englobant notre workflow que j’avais l’habitude de répliquer sur l’ensemble de mes jobs... No more duplication !

Alors on se lance ?

Tout d’abord sachez qu’un projet démo est disponible sur le repository Github d’Ineat afin que vous poussiez l’importer dans votre jenkins. Vous le trouverez ici.

Nous allons traiter de quelques notions Groovy, au besoin n’hésitez pas à allez voir la documentation officielle. Keep calm, it’s just an easy example

1. Récupération du projet

Créer un projet vide avec votre IDE préféré. Pour ma part j’utilise IntelliJ qui m’a créé un projet Groovy + Gradle.

2. Création de la DSL

Nous allons créer une DSL qui permettra de centraliser le contenu de notre workflow défini précédemment.

A savoir, toutes les DSL déclarées dans la librairie se situent dans un répertoire vars que vous allez créer à la racine du projet. Le nom de la DSL utilisée dans votre job sera porté par votre nom de fichier.

Nous nommerons notre DSL pluginMobile, celle-ci sera donc créée dans /vars/pluginMobile.groovy. Elle sera appliquée dans le job jenkins de la façon suivante:

// JOB jenkins

pluginMobile {

    gitUrl = "URL de votre projet git"
    credentialsId = "Credential pour ayant les droits d'accès au projet"

}

 

3. Passons à l’implémentation

Lorsque la DSL est invoquée dans le job, la méthode def call(…) est appelée. Cette fonction prend en paramètre de fonction une closure. Dans l’exemple ci-dessus, la closure correspond à la partie suivante :

{

    gitUrl = "URL de votre projet git"
    credentialsId = "Credential pour ayant les droits d'accès au projet"

}

Si le concept de closure ne vous est pas familier, voici un lien qui pourra vous éclairer ici.

Ajoutons la closure en paramètre de fonction call :

// /vars/pluginMobile.groovy

def call(Closure context) {
    // création d'un map
    def config = [:]
    // affecter la map en tant que delegate de la closure
    context.resolveStrategy = Closure.DELEGATE_FIRST
    context.delegate = config
    // appeler la closure
    context()
    // commencer son workflow
}

La closure n’aura pour but que d’initialiser un ensemble de variables.

Afin de récupérer les variables déclarées dans celle-ci, nous utiliserons le principe de délégation. En groovy il est possible d’appliquer un delegate a une closure. Le delegate pour notre cas sera une map.

Revenons sur la closure, nous avons deux variables (gitUrl et credentialsId), notre hashMap sera initialisée automatiquement avec les deux clés ( gitUrl et credentialsId) et leur valeur respective.

Utiliser une closure et une délégation par map permet à la DSL d’être plus flexible et évolutive. Par exemple si une nouvelle variable est nécessaire un jour comme “projectName”, nous avons plus qu’à ajouter projectName dans la closure comme ceci :

// JOB jenkins

pluginMobile {

    projectName = "Nom de mon projet"
    gitUrl = "URL de votre projet git"
    credentialsId = "Credential pour ayant les droits d'accès au projet"

}

La signature de la fonction def call(Closure context) reste inchangée et l’appel de la closure aussi.

4. Écrire le workflow

Nous avons précédemment récupéré l’ensemble des informations qui seront passées à la DSL. Maintenant il suffit d’ajouter le workflow et d’y ré-appliquer les valeurs stockées dans def config = [:]  :

  1. Récupérer le credential id : config.crendentialsId
  2. Récupérer l’URL git : config.gitUrl

Ce qui donne finalement :

// /vars/pluginMobile.groovy

def call(Closure context) {
    // création d'un map
    def config = [:]
    // affecter la map en tant que delegate de la closure
    context.resolveStrategy = Closure.DELEGATE_FIRST
    context.delegate = config
    // appeler la closure
    context()
    // commencer son workflow
   node {

       stage('clean') {
           deleteDir()
       }

       stage('checkout') {
           def branch = 'master' 
           if (env.getEnvironment().containsKey('BRANCH')) { 
               branch = BRANCH 
           } 
           git branch: branch, credentialsId: config.credentialsId, url: config.gitUrl
       }

       stage('configure') {
           def branches = "master\ndevelop" 
           // récupérer automatiquement les branches via un script sh git 
           properties([ 
               parameters([ 
                   choice(choices: branches, description: 'Branche de build', name: 'BRANCH'), 
                   booleanParam(defaultValue: false, description: 'Faire une release', name: 'RELEASE') 
               ]), 
               disableConcurrentBuilds(), 
               buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '10')),               
               pipelineTriggers([pollSCM('@hourly')]) 
           ])
       }

       stage('build') {
           sh('./gradlew clean assemble')
       }

       stage('release') {
            // 1. Git flow release 
            // 2. Push nexus OSS // etc...
       }



   }

}

5. Ajouter la dépendance de la librairie dans Jenkins

Assurez-vous tout d’abord d’avoir les droits nécessaires pour ajouter la dépendance. Puis il faut se rendre dans “Manage Jenkins / configure System / Global Pipeline Libraries“, cliquer sur le bouton add et remplir les informations suivantes :

  1. Name : Nom permettant d’importer votre librairie, nous la nommerons my-shared-library-mobile
  2. Default version : branche git sur laquelle jenkins va récupérer les dépendances
  3. Load implicitly : permet de charger automatiquement la librairie dans l’ensemble de vos jobs. Pour l’exemple ne cliquez pas dessus vous verrez par la suite pourquoi 😉
  4. Puis les informations SCM nécessaires à la récupération de la librairie : credentials, url etc…
Add Global shared library

Load implicitly permet de charger ou non directement la librairie dans votre job. Ici nous préférerons importer à la demande notre DSL car le serveur Jenkins peut être utilisé pour des projets différents.

Pour charger la librairie, il faut ajouter l’instruction suivante : @Library(‘my-shared-library-mobile’)

Notre job ressemble maintenant à ceci :

// JOB jenkins

@Library('my-shared-library-mobile') _ 

// _ permet d'importer le script qui se situe dans /vars


pluginMobile {

    gitUrl = "URL de votre projet git"
    credentialsId = "Credential pour ayant les droits d'accès au projet"

}

Bravo, vous venez de créer votre première DSL pour Jenkins.

6. Conclusion

Dans cet article nous avons créé une DSL pour gérer notre pipeline de build; Il est possible d’en créer d’un tout autre type : git flow, déploiement nexus, notifications etc… Libre à votre imagination.

Si l’article vous a plu n’hésitez pas à le partager mais aussi à laisser votre avis en commentaire.