Déploiement automatique dans Azure d'une application Angular avec VSTS

Même lorsque l'on sort des technologies Microsoft, VSTS peut s'avérer être un allié de poids dans la réussite de vos projets.

Voyons aujourd'hui comment profiter de cet outil pour déployer automatiquement, à chaque modification de code, une application Angular sur Azure.

Création d'une application Angular avec la CLI

Pour la suite de l'article, on va partir sur une application Angular simple, créée via la CLI.
Si vous ne savez pas comment faire, c'est très rapide. Il faut d'abord installer la CLI d'Angular sur votre machine via la commande npm suivante (il vous faudra NodeJS d'installé évidemment) :

npm install -g @angular/cli

Une fois installée, il suffit d'appeler la commande new de la CLI :

ng new ngtoazureviavsts

Et voilà, notre application est prête !

Création du hosting NodeJS avec Express

Dans Azure, notre application sera hébergée sur un serveur NodeJS. Il nous faut donc créer un serveur HTTP en NodeJS, ce que l'on va faire en utilisant le paquet npm express.

Dans le dossier /src de notre application, nous allons créer un répertoire /server qui contiendra un fichier server.js, permettant de créer le serveur HTTP, et un fichier package.json, déclarant les dépendances de notre serveur HTTP.

solution-explorer.png

Le fichier package.json déclare simplement une dépendance vers express : 

{
  "name": "blog",
  "version": "0.0.0",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "express": "^4.15.3"
  }
}

Le fichier server.js, quant à lui, contient le code suivant : 

const express = require('express');
const path = require('path');
const http = require('http');

const app = express();

app.use(express.static(__dirname));
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'index.html'));
});

const port = process.env.PORT || '3000';
app.set('port', port);

const server = http.createServer(app);
server.listen(port, () => console.log(`server running on localhost:${port}`));

Ce code crée simplement un serveur express qui retourne systématiquement le fichier index.html, sauf pour les fichiers statiques (je vous laisse vous référer à la doc d'express pour plus de détails sur le fonctionnement).

Il faut maintenant modifier le fichier .angular-cli.json pour intégrer les deux fichiers créés précédemment comme output de la build :

"apps": [{
    "root": "src",
    "outDir": "dist",
    "assets": [
        "assets",
        "favicon.ico",
        {
          "glob": "server.js",
          "input": "./server/",
          "output": "./"
        },
        {
          "glob": "package.json",
          "input": "./server/",
          "output": "./"
        }
    ],
}]

Si vous faites un ng build, vous devriez maintenant avoir le contenu suivant dans le dossier dist :

solution-explorer-distfolder.png

En exécutant le fichier server.js, vous lancerez le serveur express hostant notre application Angular :

serverjs-running.png

Notre hosting NodeJS est prêt.

Création de la build

Sur votre souscription VSTS, créez une nouvelle build. Nous utiliserons un template vide. La build va être composée des tâches suivantes :

  • Get sources : Cette tâche est créée par défaut et permet de récupérer les sources depuis plusieurs providers (VSTS via Git ou TFVC, Github, etc.). Configurez la de manière à accéder à votre repository cible.
  • npm install @angular/cli -g : Cette tâche de type npm permet d'installer la CLI d'Angular en global, que nous utiliserons dans la suite de la build
  • npm install : Cette tâche de type npm permet d'installer toutes les dépendances npm de notre projet
  • ng build --prod --aot : Cette tâche de type command line permet de build l'application en mode production via la CLI, en précisant le flag aot pour activer la compilation Ahead of Time
  • npm install (dans le répertoire dist) : Cette tâche de type npm permet d'installer toutes les dépendances de notre serveur express. Attention de bien préciser dans le paramètrage que le répertoire d'exécution de la commande doit être le répertoire /dist
  • Publish Build Artifacts : Cette tâche de type Publish Build Artifacts va publier le répertoire /dist en tant qu'artefact de la build.

La build est prête. Après exécution, vous devriez obtenir les artefacts suivant (dans mon cas, j'ai nommé l'artefact front-app) :

build-artifacts.png

Dans son onglet Triggers, vous pouvez indiquer à la build de s'exécuter après chaque changement de code (c'est-à-dire après chaque push ou check-in) en cochant Continuous Integration.

Et maintenant les tests !

Comme nous sommes des développeurs consciencieux, notre application est testée ! Nous allons donc rajouter l'exécution de ces tests dans le process de build afin de nous assurer de la qualité de l'application.

Par défaut, l'application créée par la CLI d'Angular va exécuter les tests en lançant un navigateur Chrome, via la commande ng test. Dans le cas d'une build, les tests s'exécutent via un agent sur lequel n'est pas installé Chrome, ce qui provoquera un échec. Pour éviter ce comportement, nous allons devoir exécuter nos tests sur phantomjs, un navigateur Web sans interface graphique et basé sur WebKit.

L'utilisation de ce navigateur se fait en installant deux paquets npm dans l'application, correspondant respectivement à phantomjs et au plugin Karma permettant de lancer des tests sur phantomjs :

npm install phantomjs-prebuilt --save-dev
npm install karma-phantomjs-launcher --save-dev

La configuration Karma de notre application, via le fichier karma.config.js, doit être modifiée afin de rajouter le plugin karma-phantomjs-launcher :

plugins: [
    ...
    require('karma-phantomjs-launcher')
]

Vous devriez maintenant pouvoir lancer vos tests avec phantomjs via la commande ng test --browsers=PhantomJS.

karma-execution.png

Nous allons également faire en sorte de pouvoir récupérer le résultat de l'exécution des tests pour l'afficher ensuite dans VSTS (ce serait dommage de voir que les tests échouent sans savoir précisemment lesquels et pour quelles raisons). On va installer maintenant un reporter Karma permettant d'exposer l'output au format JUnit :

npm install karma-junit-reporter --save-dev

Il faut, comme précédemment, déclarer ce plugin dans la configuration Karma :

plugins: [
    ...
    require('karma-phantomjs-launcher'),
    require('karma-junit-reporter')
]

On va également y rajouter une propriété junitReporter afin de configurer ce reporter (l'objectif étant de définir le nom du fichier qui contiendra l'output des tests).

junitReporter: {
    outputDir: '',
    outputFile: 'test-junit.xml'
}

Il reste maintenant à modifier la propriété reporters pour ajouter JUnit :

reporters: config.angularCli && config.angularCli.codeCoverage ? [... , 'junit'] : [... , 'junit']

Notre application est maintenant configurée pour que les tests puissent être exécutés sur VSTS, via phantomjs. De retour sur la build, rajoutons une tâche de type command line lançant la commande suivante : ng test --watch=false --reporters=junit,progress --browsers=PhantomJS --singleRun=true. Les paramètres permettent  :

  • --watch=false : De ne pas écouter les fichiers pour relancer les tests après une modification
  • --reporters=junit,progress : D'utiliser les reporters junit et progress
  • --browsers=PhantomJS : D'utiliser le navigateur PhantomJS
  • --singleRun=true : D'exécuter les tests une fois puis d'arrêter l'exécution

A l'heure où j'écris cet article, il existe un bug sur la capture de PhantomJS par Karma lors de l'exécution sur un agent VSTS (je vous laisse lire l'issue suivante pour plus d'informations : https://github.com/Microsoft/vsts-tasks/issues/1486). Pour régler ce problème, il suffit d'ajouter une variable d'environnement (onglets Variables sur la définition de la build) nommée PHANTOMJS_BIN et ayant comme valeur C:\NPM\Modules\PhantomJS.cmd.

L'exécution des tests se fait maintenant correctement, il reste à exposer le résultat des tests en ajoutant une tâche Publish Test Results. Sur la configuration de cette tâche, le format doit être JUnit et le pattern des fichiers doit être **/test-junit.xml (en fonction de la configuration Karma de votre reporter JUnit).

La build est maintenant finie et devrait ressembler à ça :

build-process.png

Le résultat de l'exécution de la build devrait donner ça :

build-output.png

Il est possible de voir le détail de l'exécution de tests via l'onglet Tests :

build-tests-output.png

Création de la release

La build terminée, il reste à créer la release qui va déployer l'application sur un App Service Azure. Vous pouvez pour cela utiliser le template Azure App Service Deployment.

Sur la tâche Azure App Service Deploy, vous aurez besoin de sélectionner la souscription à utiliser ainsi que le nom de l'App Service sur laquelle notre application sera déployée. Sur le paramètre Package or folder, il faudra renseigner l'artefact généré par la build (dans mon cas $(System.DefaultWorkingDirectory)/**/front-app). Petite subtilité, comme nous souhaitons exécuter notre application sur NodeJS, nous allons demander la génération d'un fichier Web.config et y déclarer un handler iisnode démarrant sur le fichier server.js. La release ressemble alors à ça :

release.png

Dans son onglet Triggers, vous pouvez indiquer à la release de s'exécuter après chaque build, en cochant Continuous Deployment puis en sélectionnant la build. Et voilà, le déploiement continu est prêt, il ne vous reste plus qu'à coder !

Bonus : Optimisation de la build

Si vous avez lancé la build, vous vous êtes sans doute rendu compte qu'elle est... très longue. Un peu moins de 4 minutes pour n'exécuter que quelques tests, ca fait beaucoup. Le problème vient majoritairement du fait que l'on demande l'installation d'Angular CLI en global, pour ensuite appeler les commandes ng.

Pour éviter cette installation, on va déclarer des nouvelles commandes dans le fichier package.json qui seront appelées depuis la build (de cette manière on utilisera la CLI du projet et non plus celle globale) :

"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "test-build": "ng test --watch=false --reporters=junit,progress --browsers=PhantomJS --singleRun=true",
    "build-prod": "ng build --prod --aot"
}

On peut alors supprimer l'installation de la CLI Angular, l'appel à ng test et ng build, et remplacer tout ça par un appel aux deux commandes précédentes :

build-process-optimized.png

Si on réexécute la build, elle ne prend maintenant "plus que" 2,5 minutes.

Pour plus d'informations sur le framework Angular, vous pouvez vous référer à notre livre : Angular: developpez vos applications web avec le framework javascript de google.

Bons déploiements !

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus