Affichage des articles dont le libellé est Jenkins. Afficher tous les articles
Affichage des articles dont le libellé est Jenkins. Afficher tous les articles

samedi 11 février 2017

Introduction au pipeline de Jenkins

J'utilise Jenkins depuis quelques années. Un peu comme tout le monde je suis passé à la 2 au moment de sa sortie. Comme beaucoup, je n'ai pas vraiment vu de grosse différence avec les versions 1.6xx (si ce n'est que mes jobs ne fonctionnait plus mais ça, c'est une autre histoire).
Au bout d'un moment je suis tombé sur une pépite : les pipeline/workflow de Jenkins.

Mais avant de parler de ça, je vais vous parler de ce que je faisais traditionnellement.

Création de job et parallélisation


Pour résumé, je gère énormément d'installations ou de compilation en tout genre et Jenkins me sert d'esclave personnel : il lance des playbooks Ansible, des shells Unix ou des compilations maven (beurk !). Je colle généralement ces scripts dans le repository Git des projets et je les appelle depuis Jenkins. Le petit problème est que j'ai quand même à créer ces jobs et que je dois gérer l'enchaînement de ces éléments.

Fonction de la difficulté de la tâche, je passe soit par des déclenchements de jobs (natif Jenkins) soit carrément des jobs permettant d'orchestrer plusieurs lancements (plugin Multijob).

Pour ceux qui ne le connaîtrait pas, le plugin Multijob est tout de même bien fait et permet de très facilement gérer des lancements d'opérations en parallèle assez complexe (cf capture d'écran).

Exemple de Multijob
Malgré tout, ça implique que pour chaque phase, vous aurez un job à définir (qui ne vont pas contenir grand chose si ce n'est un appel à un playbook Ansible) ainsi qu'un job supplémentaire permettant de piloter le lancement des autres jobs et ceci depuis l'interface graphique de Jenkins.

Vous l'aurez compris, ça fait beaucoup d'opérations manuelles à réaliser et vous n'avez pas vraiment de moyen de faire ça proprement (sauf à bricoler les fichiers XML de Jenkins). Autre problème, vous ne savez pas qui modifie quoi à quel moment. Vous n'avez aucun moyen de savoir si une modification a été réalisée par quelqu'un qui pourrait expliquer le gros plantage de compilation au moment de la release de votre projet.

C'est là qu'on va voir en quoi les pipelines/workflows Jenkins peuvent répondre à ce type de problème.

Les pipelines sous Jenkins


Première chose à savoir, vous n'avez plus à aller dans l'interface Jenkins pour mettre à jour vos jobs : vous créez votre Job dans l'interface Jenkins (vivement qu'on puisse scripter ça proprement ...), vous associez votre repository de code, vous rentrez le nom du fichier décrivant le pipeline et c'est à peu près tout. Et le reste me direz-vous ? On va voir que le pipeline remplace ça avantageusement :

  • Besoin de récupérer la dernière version du code ? Utilisez le mot clé checkout scm ;
  • Besoin de lancer une tâche ? Le mot clé sh est là pour ça ;
  • Lancement en parallèle ? La fonction parallel est là pour ça !
Vous l'aurez compris, ça permet de remplacer à la fois vos micro jobs ainsi que le job chapeau de pilotage des autres jobs.
Autre chose, c'est du groovy ce qui permet de faire des choses relativement propre par rapport à du shell Unix. Il est même possible de faire des librairies de fonction pour réutiliser du code. C'est magique je vous avais dit !

Exemple de pipeline


Avant d'aller plus loin, voici un petit exemple de pipeline :
node() {
  // Étape 1
  stage('etapes-1') {
    parallel(
      'etape-1.1': {
        echo "Job 1 de l'étape 1"
      },
      'etape-1.2': {
        echo "Job 2 de l'étape 1"
      },
    )
  }
  // Étape 2
  stage('etapes-2') {
    echo "Job 1 de l'étape 2"
  }
  // Étape 3
  stage('etapes-3') {
    parallel(
      'etape-3.1': {
        // Cette opération doit prendre moins de 10 secondes
        timeout(time: 10, unit: 'SECONDS') {
          echo "Job 1 de l'étape 3"
        }
      },
      'etape-3.2': {
        echo "Job 2 de l'étape 3"
      },
      'etape-3.3': {
        echo "Job 3 de l'étape 3"
      },
    )
  }
}
Les sections parallel vous permettent de lancer x jobs en même temps. Jenkins attendra gentiment que tous les jobs finissent leur lancement. Vous avez peur qu'un de ces petits coquins ne prenne trop de temps et ne vous rende jamais la main ? Ajoutez une section timeout et vous êtes sauvé !
Vous avez envie de gérer la reprise sur incident ? C'est du groovy ! Ajoutez une section try/catch !

Intégration dans Jenkins


On a vu à quoi ressemblait le code de notre pipeline. On va maintenant voir comment l'intégrer dans Jenkins :


On y voit plusieurs opérations :
  • Création du job pipeline dans Jenkins ;
  • Lancement du job et visualisation du résultat ;
  • Affichage dans la nouvelle interface Jenkins Blue Ocean.
Ici, le code a été intégré directement dans l'interface Jenkins. On peut utiliser ce mécanisme dans le cadre d'une mise au point ou de test mais si vous voulez travailler proprement, rien n'égale l'intégration dans un repository Git. Ça tombe bien, on va voir ça dans le chapitre suivant.

Stockage du pipeline dans Git


Première chose, nous allons créer un repository Git avec notre code pipeline :
Création du repository :

$ mkdir git/test
$ cd git/test
$ git init .

Intégration du code :

$ git add Jenkinsfile
$ git commit -m "Ajout fichier Jenkinsfile d'exemple."
[master (commit racine) ccc0630] Ajout fichier Jenkinsfile d'exemple.
 1 file changed, 34 insertions(+)
 create mode 100644 Jenkinsfile

Reste maintenant à intégrer notre code dans l'interface de Jenkins de la manière suivante :


NB : Si vous appelez votre fichier de pipeline autrement que Jenkinsfile, il faudra le préciser au moment de la création de votre job.

Pour finir


Voilà, ça sera tout pour l'instant. Ça donne déjà un bon aperçu de ce qu'il est possible de faire. Dans un prochain article, j'essaierai de vous présenter une autre fonction très sympathique des pipelines : l'écriture de librairie pipeline (avec le mot clé load). En attendant, bonne intégration continue !

mercredi 13 mars 2013

Mécanisme de hook de SVN


Je sais, vous allez me dire que SVN est vieillot et que de toute façon, en bon geek qui se respecte, ça fait longtemps que vous êtes passé sous git.

Mais voilà, si vous n'êtes pas encore complètement convaincu que SVN n'est pas une solution si mauvaise que ça et que vous utilisez ça chez votre client/employeur, j'ai quelques astuces pour vous.

Tout d'abord, les hooks sont présent dans le répertoire hooks de votre repository. De base, votre repository SVN, à sa création, vient avec un template pour toutes les opérations de hooks possible sous SVN. Ça se présente sous la forme suivante :


ls *.tmpl
post-commit.tmpl
post-unlock.tmpl
pre-revprop-change.tmpl
post-lock.tmpl
pre-commit.tmpl
pre-unlock.tmpl
post-revprop-change.tmpl
pre-lock.tmpl
start-commit.tmpl

Comme vous pouvez le constater, toutes les opérations possible et imaginable sont disponible. Personnellement, j'ai eu l'occasion d'en mettre deux en oeuvre :

  • pre-revprop-change : lancé avant une modification sur une révision ;
  • post-commit : lancé au moment de la fin d'un commit.


Ces hooks vont servir à changer/régler le comportement de votre repository à l'aide de script. Pour les mettre en place, rien de plus simple :

  • Faîtes un script dans le langage qui vous amuse (même si j'aurais tendance à privilégier un script shell Unix tout simple) ;
  • Copier/lier ce script dans le répertoire du hooks en respectant le nom de l'opération à laquelle vous voulez le lier.

Exemple de hooks SVN : pre-revprop-change


Prenons le cas du pre-revprop-change. Ce script est lancé pour vérifier que l'opération de modification d'une révision de votre repository est bien autorisé. Je sais, vous aller me dire que ce type de demande vous parez étrange. Mais comme chacun sait, le client est roi et à coeur vaillant, rien d'impossible !

Dans le cas présent, nous allons justement restreindre cette opération à la modification du message de log de votre révision. Nous allons également interdire que cette modification puisse se faire par un autre utilisateur que celui à l'origine de la modification.

Sans plus attendre, voici donc le fameux script en charge de cette modification :

#!/bin/bash
REPOS="$1"
REV="$2"
USER="$3"
PROPNAME="$4"
ACTION="$5"

if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ] && \
   [ `svnlook author -r "$REV" "$REPOS"` = "$USER" ]; then
  exit 0
fi

echo "Changing revision properties other than svn:log is prohibited" >&2
exit 1

Le principe est simple :
  • On vérifie bien que nous n'allons modifier que le message de la révision (PROPNAME=svn:log) ;
  • L'action est une modification (ACTION=M) ;
  • Enfin, l'auteur de la modification est bien celui à l'origine du commit (svnlook author ... = $USER).
De là, si ces conditions sont remplies, on renvoie un exit 0 et SVN considère que l'opération est valide.

Dans les autres cas, nous renvoyons exit 1 et SVN refusera de lancer la modification.

Couplage avec Jenkins à l'aide d'un événement de post-commit

Prenons maintenant un autre cas : vous voulez coupler votre repository SVN avec un moteur d'intégration continue (type Jenkins) afin de lancer automatiquement vos constructions sans pour autant avoir à poller régulièrement votre repository SVN tout le temps. Ici, l'utilisation d'un évènement de post-commit fera parfaitement l'affaire et pourra lancer automatiquement votre job de construction.

Pour se faire, nous allons créer le script suivant que nous lierons ensuite dans le répertoire hooks avec comme nom post-commit (bien s'assurer également qu'il est exécutable) :

#!/bin/bash
log="/tmp/post_commit.log"
url_jenkins="http://127.0.0.1:8080/job/makeApp-generic/buildWithParameters?token=makeApp"

REPOS="$1"
TRANS="$2"

exec  > $log
exec 2> $log

repository=$(basename $REPOS)

# Définition des paths des différents binaires
for tag in $(svnlook changed -r $TRANS $REPOS | \
             sed 's/tags\//' | awk '{ print $2 }' | \
             sed 's/\/.*//g' | sort -u)
do
  if [ $tag = "trunk" ]; then tag=HEAD ; fi
  echo "Lancement de la construction de '$repository' (tag='$tag')."
  curl "$url_jenkins&application=$repository&tag=$tag" 2> /dev/null
done
NB : Il va sans dire qu'il faudra que votre serveur Jenkins soit en mesure d'accepter le job qui s'appelle makeApp-generic et surtout que vous ayez créé un token d'appel associé pour pouvoir le lancer avec un curl. Tient, je sens que je vais faire un article sur Jenkins dans peu de temps :).

En gros, le script va simplement regarder les répertoires des fichiers qui vont être modifié et, en fonction de ça, lancer une construction (avec les appels curl) auprès de Jenkins. Dans notre exemple, si la modification se fait sur le trunk, le tag est changé en HEAD.

Il ne nous reste plus maintenant qu'à faire une modification dans notre repository et vérifier que le build s'est bien passé en consultat la log /tmp/post_commit.log :

cat /tmp/post_commit.log
Lancement de la construction de 'test' (tag='HEAD').

Voilà, ça sera à peu près tout pour aujourd'hui. A bientôt pour de nouvelle aventure !