AngularJS en intégration continue sous Jenkins
Vous souhaitez mettre votre projet JavaScript dans un environnement d’intégration continue ? Vous avez essayé mais n’êtes pas allez au bout de la démarche par manque de temps, d’outil ou de retour d’expérience ? Voici comment nous y sommes parvenu chez ContentSide.
Qu’apporte l’intégration continue ?
Rappelons-le, l’intégration continue apporte beaucoup dans la vie d’un projet. Donc ce n’est pas un caprice de geek que de vouloir mettre un framework JavaScript dans un Jenkins, mais un vrai besoin.
Tout comme un projet java, le principal but est d’avoir au plus tôt (donc après chaque commit je dirai) un avertissement si les sources deviennent instables. Par exemple, des modifications de code incompatible avec celles de votre coéquipier, ou des tests unitaires qui échouent (ces fameux tests qui passent chez vous, mais qui échouent ailleurs car l’environnement est différent). Pourquoi ? Parce qu’une anomalie décelée pendant la phase de réalisation coûte (beaucoup) moins cher (au sens propre !) que décelée plus tard (pendant la recette interne, la recette client, en pré-production, en production). Car il faut reproduire en local, car il faut se remettre dans des développements peut-être anciens, car il faut préparer de nouveaux livrables, car il faut re-livrer, … sans parler de votre réputation / de la confiance du client.
Donc bien que nous soyons convaincus que l’intégration continue soit une bonne pratique, nous ne l’avons que peu vue décrite avec un projet JavaScript sur Internet. Peut-être par manque de temps, ou parce que tout le monde n’est pas aussi convaincu que nous. Voici donc notre petite expérience sur ce sujet.
Pour information, il s’agit d’un projet AngularJS géré avec Yeoman (Yo, Grunt et Bower). Dans l’architecture de ce type de projet, vous trouverez les modules NodeJS (répertoire ‘nodes_modules/’) et les librairies Bower (répertoire ‘bower_components/’). Petite bonne pratique avant de commencer : ne mettez pas ces répertoires dans votre outil de gestion de sources ! Des fichiers de configuration listent les versions des modules et des librairies nécessaires pour le projet. Donc inutile de les versionner.
Solution choisie : Un plugin Maven
Pour intégrer le projet dans un Jenkins, nous avons fait le choix de le « mavenniser » pour confier l’enchainement des tâches Grunt du build à un plugin maven : yeoman-maven-plugin. L’idéal aurait été d’avoir un plugin Jenkins qui se charge de cela, mais il n’en existait pas, et nous n’avions pas le coeur à en développer un. Alors qu’un plugin maven attendait sur la toile qu’on le teste un peu 🙂
Le fait de mavenniser le projet a aussi l’avantage de pouvoir faire des releases du projet dans notre repository maven. Ainsi on peu facilement tirer cette dépendance dans un autre projet tel qu’un Back Office. Avec une bonne configuration de son pom, on peut obtenir un seul War comprenant le Front et le Back. Ainsi cela facilite les livraisons, les déploiements et évite les problèmes de cross-domaine (CORS).
“Mavennisation” du projet
Rien de compliqué pour commencer : On ajoute un pom, un répertoire src/ vide et nos sources Angular dans un répertoire yo/ à côté
On ajoute le plugin dans le pom qui va orchestrer les tâches Grunt du build. Et on configure dans les plugins connus de Maven l’emplacement des fichiers à mettre dans le War final et les répertoires à vider lors de la tâche Maven ‘clean’.
Voilà, si la tâche ‘grunt’ fonctionne en local dans le répertoire yo/, la tâche ‘mvn clean package’ sur le répertoire racine en fera autant.
Si vous rencontrez des difficultés sur le ‘grunt’, vous aurez les mêmes avec le plugin maven. Je vous conseille donc de mettre à jour le package node.js ‘yo’ : « npm update yo ». Et puis si vous avez toujours des problèmes, vous pouvez mettre en commentaire les tâches grunt qui posent problèmes afin de poursuivre sur notre sujet principal. Mais là il faut savoir où on met les pieds, car certaines tâches dépendent d’autres tâches. Exemple de tâches grunt mises en commentaires : jshint, uglify, …
Un job Jenkins “Free-style” !
Créons maintenant le job Jenkins sur votre serveur d’intégration en choisissant “Build a free-style software project” et non “Build a maven2/3 project”. J’explique pourquoi plus bas. Sinon, configuration classique : Création du job, configuration du serveur de versionning à minima, choix du build (Maven3).
Lançons un build … et si votre serveur d’intégration n’est pas votre machine de dév, le résultat n’est pas très concluant. Et oui, le plugin maven exécute les tâches ‘npm install’, ‘bower install’ et ‘grunt’. Mais ces outils ne sont pas forcément installés sur votre serveur.
Au passage, voilà pourquoi il est inutile de mettre les répertoires ‘nodes_modules/’ et ‘bower_components/’ dans l’outil de gestion de configuration. Les dépendances sont gérées par les fichiers de configuration ‘package.json’ et ‘bower.json’ et sont installées par les 2 premières tâches.
Installation des outils de build sur le serveur
Préparons le serveur hébergeant votre Jenkins. Pour cela il fait suivre les mêmes étapes que pour votre environnement de développement, à savoir les installations de : Node.js (npm), Karma, Yeoman (Yo, Grunt et Bower), Ruby (gem), Sass, Compass.
Par contre vous rencontrerez peut-être des problèmes de droits sur une machine linux. Avec quelques notions Unix, voire un administrateur système, vous passerez ces étapes sans problème (un plugin de gestion de variables d’environnement sous Jenkins pourrait aussi vous sauver la vie).
De mon côté je me suis heurté à des problèmes dans les tâches grunt. Si les logs de jenkins ne sont pas assez claires, exécutez les tâches de façon isolée. Pour cela, placez vous dans une console SSH dans le répertoire yo/ de votre job Jenkins (un truc du genre /var/lib/jenkins/job/…workspace/yo/) et avec le même utilisateur que celui utilisé par Jenkins.
Lancez un ‘grunt’ comme le plugin, ou directement la tâche qui pose problème dans Jenkins.
Il peut arriver d’avoir une différence sur l’exécution d’une tâche avec des utilisateurs différents (root et utilisateur Jenkins par exemple). Si c’est le cas il s’agit d’un problème de droit ou de définition de propriétés system qui seraient différentes.
Il arrive aussi (trop souvent) que des versions plus anciennes de modules nodes posent moins de problème. Par exemple, un karma@0.8.3 vs karma@0.8.6 ou encore grunt-contrib-imagemin@0.1.4 vs grunt-contrib-imagemin@0.2.0.
Exécution des tests unitaires
Dernier point, les tests ! Dans les fichiers karma.conf et karma-e2e.conf le navigateur utilisé pour les tests est spécifié. Vérifiez qu’il s’agit de PhantomJs, sinon modifiez-le. Normalement il a été installé avec la suite Grunt.
Un peu plus touchy avec ce plugin maven, les tests en échec ne rendent pas le build instable (avec la liste des tests en échec) mais le rendent en échec. Il vous faudra regarder les logs de Jenkins pour savoir ce qu’il s’est passé.
Pour résoudre cela, il faut récupérer la sortie des tests Karma et les rendre interprétables par Jenkins. La configuration est décrite sur le site de Karma version 0.10. En fonction de votre version de Karma, la configuration diffère un peu. Donc utilisez la bonne !
Un détail important : La documentation Karma demande d’ajouter l’action “Publish JUnit test result report” dans la section “Post-build”. Dans mon cas, je n’avais pas cette option dans mon job Jenkins. Effectivement, j’avais créé un job en sélectionnant “Build a maven2/3 project”. Et dans cette configuration de job, il n’est pas possible d’ajouter l’action demandée par Karma. Il faut donc créer un job en choisissant “Build a free-style software project”.
Vous voilà maintenant avec un job Jenkins qui build votre projet AngularJS tout en remontant clairement les tests en échec.
Conclusion
La mise en place a été un peu longue, mais nous sommes ravis d’avoir mis notre projet AngularJS dans un environnement d’intégration continue. Il s’agissait pour nous d’une étape indispensable pour notre équipe de dév. Et même si vous êtes tout seul, cela assure une certaine qualité de votre code et vous permet d’effectuer vos releases plus facilement. Maintenant, on peut réfléchir à un déploiement automatique … 😉
En discuter avec nos devs ?
Ce sujet vous intéresse ?