Démarrer un projet plus vite que l'UDD

De nos jours, il n'est plus besoin de vanter les mérites d'une usine de développement (UDD) au sein d'un projet. La systématisation des tâches de vérification (compilation, standards de code, tests unitaires, etc.) aide grandement à repérer rapidement les problèmes de qualité. Cependant, il peut arriver en début de projet que celle-ci se laisse désirer et qu'il faille commencer les développements en son absence.

Voyons donc comment assurer un minimum des bienfaits de la systématisation dès le lancement de projet.

Dépôt Git

Si vous développez sur un projet moderne, il y a beaucoup de chances que vous ayez déjà fait votre premier commit dans un dépôt Git. Et si vous vous trouvez dans la situation que j'ai décrite, il se peut que vous n'ayez aucun endroit où le pousser avec un petit git push.

Il est en réalité plutôt simple de partager un dépôt sur un réseau local. Si l'un des membres de l'équipe se dévoue ou que vous disposez d'une machine, même peu puissante, disponible sur le réseau local, vous pouvez vous en servir pour y héberger temporairement un dépôt central. Pourquoi ne pas utiliser ce Raspberry Pi acheté il y a quelques années auquel vous n'avez jusqu'à présent pas trouvé d'utilité ? =)

Via le protocole Git

Toutes les installations de Git fournissent un petit agent Git capable de servir un dépôt sur le simplissime protocole Git. Ce protocole ne propose aucune authentification et est exposé sur le port 9418 par défaut. Assurez-vous qu'il est impossible que cette machine soit accessible depuis l'extérieur du réseau local et elle pourra vous servir. Voici comment procéder sur un GNU/Linux de distribution Debian :

  • On installe les dépendances

    $ apt update
    $ apt install -y git
    
  • On initialise un dépôt vierge dans un dossier

    $ mkdir -p /srv/git
    $ git init --bare /srv/git/mon-projet.git
        Initialized empty Git repository in /srv/git/mon-projet.git/
    
  • On lance le démon git

    $ touch /srv/git/mon-projet.git/git-daemon-export-ok
    $ git daemon --reuseaddr --base-path=/srv/git/ --enable=receive-pack
    

Cette dernière ligne de commande lance un serveur sur le port 9418, prêt à accepter des connexions de vos clients Git. Pour une installation minimale cela suffit, pensez simplement à ne pas arrêter le programme ou éteindre la machine ! Dans mon exemple, la petite machine locale que j'ai utilisée s'est vue attribuer l'adresse 172.17.0.2 sur le réseau local. Depuis ma machine sur le même réseau, je peux donc cloner ce dépôt comme suit :

  $ git clone git://172.17.0.2/mon-projet.git
      Clonage dans 'mon-projet'...
      warning: Vous semblez avoir cloné un dépôt vide.

Ah oui ! Effectivement ça marche. Par contre mon dépôt est vide, et j'ai déjà des commits sur les bras. Vous pouvez plutôt ajouter cette URL à un dépôt local existant et pousser vos commits :

  $ cd mon-projet-local
  $ git remote add origin git://172.17.0.2/mon-projet.git
  $ git push origin master

Et voilà, nous revenons alors dans les sentiers battus de Git. Si vous voulez donner un peu de robustesse à votre serveur Git de fortune, vous pouvez configurer un service systemd qui s'occupera de le maintenir allumé.

Via le protocole SSH

Si vous préférez que les échanges sur votre réseau local soient chiffrés et les accès un tantinet contrôlés, vous pouvez opter pour l'option de transport via SSH. Vous devrez alors ajoutez les clés publiques SSH de tous vos collaborateurs. Toujours sur un système Debian pour l'exemple :

  • On installe les dépendances

    $ apt update
    $ apt install -y openssh-server git
    
  • On crée un utilisateur git qui sera le seul à accéder à ce dépôt

    $ useradd --create-home git
    
  • On configure avec les droits corrects les fichiers dont SSH a besoin pour authentifier les développeurs

    $ echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQQVGZKy... >> /home/git/.ssh/authorized_keys
    $ chown -R git:git /home/git/.ssh
    $ chmod 600 /home/git/.ssh/authorized_keys
    $ chmod 700 /home/git/.ssh
    
  • On crée un dépôt vierge avec les bons droits pour l'utilisateur git

    $ git init --bare /home/git/mon-projet.git
        Initialized empty Git repository in /home/git/mon-projet.git/
    $ chown -R git:git /home/git/mon-projet.git
    
  • On s'assure que le service démarre et reste activé

    $ systemctl enable ssh
    $ systemctl start ssh
    

J'ai abrégé ma clé publique dans la ligne de commande pour plus de lisibilité. Ici nous avons simplement créé un utilisateur git dont le seul rôle est d'héberger dans son dossier notre dépôt. Le serveur SSH est tatillon sur les droits accordés à ses fichiers de configuration.

La machine qui héberge le dépôt a toujours la même adresse et pour cloner le dépôt, rien de bien compliqué :

$ git clone git@172.17.0.2:mon-projet.git
    Clonage dans 'mon-projet'...
    warning: Vous semblez avoir cloné un dépôt vide.

Étrangement similaire ! De la même manière et avec cette URI, vous pouvez configurer un dépôt local existant. De même que précédemment, il convient de s'assurer que le serveur n'est pas accessible depuis l'extérieur du réseau local. SSH est plus sécurisé mais nous n'avons fait aucune configuration de hardening pour l'instant. Il faudrait, entre autres, limiter le shell de l'utilisateur git au programme git-shell comme indiqué ici. Puis bon... c'est une solution censée être temporaire ;)

Git Hooks

Nous pouvons maintenant partager du code entre les développeurs facilement. Nous aimerions maintenant systématiser certaines tâches pour s'assurer que nous n'introduisons pas trop de défauts dans notre base de code. Git propose de nombreux hooks pour surcharger certains comportements de son cycle de vie. Ils permettent d'exécuter du code personnalisé quand nous lançons localement un commit local, push ou autre. Nous pouvons donc systématiser certaines commandes que nous lancerions usuellement sur notre UDD telles que nos tests ou notre linter.

L'inconvénient, c'est que la configuration de ces hooks est locale : Git ne propose pas de la partager avec le dépôt. Il revient à chaque développeur de configurer ces commandes. Mais il existe un moyen de les configurer automatiquement en fonction de la technologie utilisée sur votre projet. Je prendrai ici l'exemple de npm pour un environnement JavaScript, mais vous trouverez probablement les mêmes outils dans votre gestionnaire de build ou de dépendances préféré. (par exemple ici pour maven)

Empêcher du code non conforme aux standards de rentrer dans le dépôt

Pour cette première configuration, il s'agit de s'assurer que le code que l'on souhaite commiter est bien conforme aux normes de style validées par notre linter préféré, ici ESLint. On utilisera le module husky qui nous permet de configurer des hooks lorsque l'on lance la commande npm install, ce qui arrive forcément souvent lorsque l'on développe (en tout cas au moins la première fois que l'on clone le dépôt). Je vais ici considérer que vous avez déjà configuré ESlint sur votre projet.

$ npm install --save-dev husky lint-staged

Cette commande installe husky et lint-staged comme dépendances. Précisez maintenant dans le package.json ces clés supplémentaires.

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
    }
  },
  "lint-staged": {
    "*.js": ["eslint"]
  }
}

Ici, lint-staged est un programme qui permet de configurer, pour certains types de fichiers, une commande à lancer quand ceux-ci sont détectés dans l'index Git, prêts à être commités. C'est ensuite husky qui lance cette commande en tant que hook Git pour éventuellement refuser le commit.

Je trouve personnellement que c'est le meilleur moment pour faire cette vérification, le plus tôt étant le mieux lorsque l'on veut harmoniser le style d'écriture sur une base de code. Si votre IDE le fait aussi, encore mieux ! Cette méthode avec les hooks permet cependant de s'assurer que cette vérification est faite, quel que soit l'outil de travail de chaque développeur.

Empêcher de pousser du code qui introduit des régressions

Ici il s'agit du même principe, mais pour un usage différent. Nous voulons ici utiliser notre harnais de tests (que vous devriez avoir, c'est pratique !) pour vérifier que les nouveaux commits que l'on veut partager ne contiennent pas de bugs sur les fonctionnalités déjà existantes. On se sert souvent de l'UDD pour cette vérification ou même empêcher d'accepter les Pull Requests. Ici nous allons carrément refuser les git push si les tests ne passent pas. Ajoutez ces clés à votre package.json

{
  "scripts": {
    "test": "mocha test"
  },
  "husky": {
    "hooks": {
      "pre-push": "npm test"
    }
  }
}

Ici, husky fera en sorte de lancer votre commande de test par défaut. Ici j'ai mis un exemple où vos tests sont dans le dossier test/ de votre dépôt et utilisent le lanceur Mocha. En pratique, il sera alors impossible de partager aux autres développeurs du code dont les tests ne passent pas ! Et je vous vois venir, ajouter un skip sur les tests, c'est tricher ;)

Notez bien que j'ai placé l'exécution des tests sur git push plutôt que git commit. En effet, l'exécution des tests peut prendre jusqu'à plusieurs secondes et cela peut devenir frustrant à chaque commit ! Si ce procédé devient trop long à chaque push alors cela veut dire que :

  • Vous devriez investir dans des tests unitaires rapides plutôt que des tests d'intégration plus lents

  • Vous attendez votre UDD depuis combien de temps au juste ?

More hooks

Ces 2 exemples de hooks font 80% du travail de votre intégration continue (les 80% les plus faciles) : ils lancent systématiquement vos outils choisis pour assurer la qualité de votre base de code pour repérer au plus tôt les défauts que vous pourriez y introduire. Git fournit également, dans les variables d'environnement des programmes lancés, des informations sur la commande en cours. Vous pourriez par exemple choisir de ne forcer ces vérifications que lorsqu'il s'agit de votre branche master. À vous d'expérimenter.

Il existe beaucoup d'autres événements du cycle de vie Git qui permettent de faire tourner un programme particulier. Je suis sûr que vous trouverez l'inspiration pour d'autres outils en lisant le contenu de man githooks. Vous noterez peut-être que certains comme pre-receive ou update permettent de réagir à un git push du point de vue du receveur. Si vous voulez vous y aventurer, vous pouvez alors sur votre serveur Git de fortune configurer des hooks pour automatiser encore plus votre processus de développement. Cependant prenez bien garde, l'investissement que vous devrez y mettre risque d'être plus important que vous ne le pensez pour des gains peu évidents par rapport à... attendre l'UDD !

L'usine de développement est arrivée !

Et voilà, votre vraie UDD est enfin disponible et vous vous devez de l'utiliser. Pour commencer à utiliser le nouveau dépôt Git à l'adresse git@mon.gitlab.com:mon/projet.git, faites simplement :

$ git clone --mirror git://172.17.0.2/mon-projet.git /tmp/mon-projet.git
$ cd /tmp/mon-projet.git
$ git push --mirror git@mon.gitlab.com:mon/projet.git
$ cd ~/mon-projet.git
$ git remote set-url origin [git@mon.gitlab.com](mailto:git@mon.gitlab.com):mon/projet.git

Vous allez ainsi copier toutes les branches que vous avez pu créer vers le nouveau dépôt. Vous pouvez maintenant débrancher votre Raspberry Pi en toute sécurité.

Et maintenant ?

En ce qui concerne les hooks, à vous de déterminer si vous pouvez encore vous en passer. En général, il est toujours utile de repérer les problèmes de linter et de tests unitaires le plus tôt possible. En revanche, l'utilisation des hooks n'est pas suffisante pour vos tests. En effet, la bonne exécution des tests sera alors conditionnée par l'environnement de développement. Une Usine de Développement apporte l'avantage imbattable de proposer un environnement standardisé dans lequel on souhaite que les tests passent toujours. De plus, on pourra y faire des tests plus poussés, tels des tests fonctionnels (plus lents à exécuter), des tests de bout en bout (qui nécessitent une environnement complet proche de la production), des tests de performance (très lourds à faire tourner) ou même des analyses de sécurité.