Créer votre système d'accroche pour Gitlab

Image utilisateur

Dans le cadre d'un développement, un logiciel de versionning de code est souvent utilisé car il permet de garder les différentes versions du logiciel et de faciliter son déploiement sur des serveurs quand il s'agit de projet web.

Dans le cadre de mon travail, j'ai eu à mettre en place un système d'accroche ou hooks lié au logiciel Gitlab (logiciel open source proposant les mêmes fonctionnalités que github). Jusqu'à récemment, le logiciel de gestion de code utilisé était Subversion, mais les nouveaux projets étaient gérés avec Gitlab (et donc git). Jusqu'à présent, les commits se faisaient depuis l'ordinateur du développeur puis celui-ci allait faire les commandes de pull manuellement sur les différents serveurs (test, production, parfois plusieurs serveurs pour les systèmes en cluster).

Pour un projet, j'utilisais donc Gitlab, mais étant encore au stade de développement et ayant donc des modifications fréquentes, je ne me voyais pas me connecter plusieurs fois par jours sur le serveur afin de déployer les modifications manuellement. En partant d'un simple script PHP, qui s'occupait de faire les pull automatiquement, j'ai amélioré le système afin d'avoir une configuration beaucoup plus complexe des dépôts avec possibilité de lancer des scripts avant et après chaque mise à jour, une gestion très fine des branches à "accrocher", des rapports d'exécution par mail...

 

Deploy Keys et WebHooks, le passage obligé...

 Une partie de la configuration des accroches se fait obligatoirement via l'interface de Gitlab, heureusement ces configurations ne seront à faire qu'une seule fois ... par projet. Et oui, ce n'est pas forcément la méthode la plus simple, mais je n'ai malheureusement rien trouvé d'autre...

Deploy Keys

Pour que le script d'accroche puisse faire des mises à jour depuis notre dépôt Gitlab, il faut que l'utilisateur qui executera le script (l'utilisateur www-data dans la plupart des cas mais pas forcément) puisse faire appel au serveur en ssh sans avoir à taper de mot de passe. Pour cela, Gitlab propose des Deploy Keys. On renseigne la clé publique d'un utilisateur du serveur dans l'interface de gitlab et quand un utilisateur essayera de faire un pull avec la clé privée correspondante, celui-ci n'aura pas à fournir son mot de passe et pourra faire les mises à jour. Le script pourra donc lancer la mise à jour du dépôt sans crainte de se retrouver bloquer par un problème d'identification.

 

Image utilisateur

Menu de gestion des Deploy Keys

WebHooks

Les WebHooks sont une autre fonctionnalité de Gitlab, elles permettent d'appeler une url au choix lors :

  • D'un push
  • D'un push taggé (ainsi sur le serveur de production on pourra ne mettra à jour que les commits taggés...)
  • D'une issue
  • D'une Merge Request

A mon sens, les deux dernières propositions ont beaucoup moins d'utilité que les deux premières surtout dans notre cas mais sachez que Gitlab offre cette possibilité.

Image utilisateur
Menu de gestion des WebHooks

Ainsi en alliant les concepts de Deploy Keys et de WebHooks, on peut lancer une page web qui se chargera de mettre à jour les différents dépôts lors d'un commit.

Script de gestion de l'accroche...

Lors de l'appel d'un WebHooks, Gitlab met à disposition un objet json dans sa requête HTTP, celui-ci est récupérable en PHP par exemple via un appel à une fonction de lecture de fichier comme file_get_contents et le flux php://input. On obtient alors un objet JSON de cette forme :

{
	"before":"095dab0794dcea6b81584f7a472dfc41693c011b",
	"after":"e8f1e3a2b71a70ecd2f58941ffc084de3eadb0d3",
	"ref":"refs/heads/master",
	"checkout_sha":"e8f1e3a2b71a70ecd2f58997ffc0879e3eadb0d3",
	"user_id":2,
	"user_name":"Toshy",
	"project_id":5,
	"repository":{
		"name":"projet-de-test",
		"url":"git@gitlab.toshy.net:toshy/projet-de-test.git",
		"description":"Projet de test Gitlab",
		"homepage":"https://gitlab.toshy.net/toshy/projet-de-test"
	},
	"commits":
		[
			{
				"id":"e8f1e3a2bcda70ecd2f58997ffc084de3eadb0d3",
				"message":"Test de commit 3\n",
				"timestamp":"2015-02-18T19:40:41+01:00",
				"url":"https://gitlab.toshy.net/toshy/projet-de-test/commit/ee8f1e3a2bcda70ecd2f58997ffc084de3eadb0d3",
				"author": {
					"user_name":"Toshy",
					"email":"no-reply@toshy.net"
				}
			},
			// Pour alléger, je n'ai pas mis les deux autres commits...
		],
	"total_commits_count":3
}

Mise à jour simple du dépôt

Il est alors possible grâce à un script PHP de transformer ce json en array (grâce à la fonction json_encode) afin de récupérer des données comme le nom du dépôt, l'url à utiliser pour le cloner ou encore le nom de la branche en faisant un petit travail sur le champs ref du tableau.

Avec ces informations on a déjà de quoi faire un petit système alléchant. Si on ajoute un petit fichier de configuration très simple en json comme celui ci-dessous, on peut alors faire un système un peu plus complexe :

{
	"projet-de-test": {
		"master":{
			"directory": "/var/www/projet-de-test"
		}
	}
}

Il suffit à chaque appel à la page web, de regarder que notre fichier de configuration a bien une entrée pour le projet et pour la branche et dans ce cas, de vérifier si le chemin defini existe ou non. On pourra alors executer une des deux commandes shell suivante avec shell_exec par exemple :

# Dans le cas ou $directory n'existe pas
git clone -b $branch $url $directory
# Sinon c'est juste une mise à jour
git pull origin $branch

On a donc déjà un script assez simple qui fait que mettre à jour notre dépôt mais il est bien sur possible d'aller plus loin...

Image utilisateur

Schéma de la mise à jour simple d'un dépôt

Attention : Dans le cas ou vous auriez fait le premier clone du projet dans l'arborescence du serveur manuellement, vérifier que c'est bien l'utilisateur web (www-data) qui a été utilisé lors du premier clonage. Si ce n'est pas le cas, je vous conseille de supprimer le dossier et de refaire un clone manuellement mais en utilisant cet utilisateur.

Pas mal d'autre choses intéressantes

On a vu jusqu'ici comment faire des mises à jour "simple" sur un dépôt mais comment gérer le fait que certains projets doivent obligatoirement executer certains scripts après une mise à jour, je pense notamment aux projets Symfony2 qui nécessitent de vider le cache pour fonctionner correctement (et il pourrait aussi être intéressant de demander à doctrine de mettre à jour le schéma sur un serveur de test par exemple...).

Le problème de ces traitements c'est qu'ils sont généralement long et que le temps d'exécution d'un script PHP est généralement limité par le serveur. Après quelques réflexions, j'ai décide de faire tourner le script PHP de mise à jour en PHP-Cli sans attendre que le script soit fini pour que le serveur retourne la réponse à Gitlab. Pour ce faire, dans ma page web, je me contente de récupérer les informations de dépôt, de branche et d'url puis j'exécute un autre script PHP en cli en arrière plan (option & dans bash et donc utilisable avec shell_exec). Le traitement PHP peut donc prendre plusieurs minutes sans être coupé au milieu ou que Gitlab attende une réponse pendant de longue minutes...

J'ai personnelement ajouter trois champs dans mon fichier de configuration :

  • preupdate: un ensemble de commandes shell à exécuter avant la mise à jour
  • postupdate: un ensemble de commandes shell à exécuter après la mise à jour
  • mail: un ensemble d'adresses mail où envoyer les logs de mise à jours

Preupdate me sert notamment pour les dépôts de production afin de pouvoir activer une page de maintenance, le script de postupdate vérifiera que la mise à jour s'est bien passée avant de désactiver cette page. Ainsi les utilisateurs du service ne se retrouvent pas face à des pages d'erreurs durant la mise à jour...

La gestion des accroches Gitlab via Gitlab...

Dans le cas, de mon travail, le nombre de serveur sur lequel devait être géré les accroches gitlab était assez important (une petite dizaine) et devait pouvoir facilement augmenter. Le système d'accroche devait être suffisamment simple pour que la totalité des membres de l'équipe puissent facilement créer leurs accroches et que je n'ai qu'à les valider. De plus, le code du script d'accroche devait pouvoir facilement être modifié. J'ai donc décidé de gérer les accroches comme un dépot Gitlab avec une partie code source et une autre partie contenant les fichiers de configuration par serveurs. Un script de postupdate s'occupait de lier le fichier de configuration du serveur actuel vers le fichier de configuration utilisé par le système d'accroche. Ainsi le dépôt Git peut contenir plusieurs dizaines de fichiers de configuration et celui utilisé varie en fonction du serveur.

Arborescence déployée sur un serveur (ici le "serveur 1"):

.
├── conf
│   ├── hooks.serveur1.json
│   ├── hooks.serveur2.json
│   ├── hooks.serveur3.json
│   ├── hooks.serveur4.json
│   ├── hooks.serveur5.json
│   └── hooks.json -> hooks.serveur1.json
├── web.php
└── cli.php

Et voici un exemple de ce que peut contenir le fichier hooks.serveur1.json :

{
	"toshy/accroche-git": {
		"master": {
			"comment": "Partie utiliser pour la mise à jour des accroches Gitlab",
			"location": "/var/www/accroche-git",
			"mail": "toshy@mon-email.fr",
			"preupdate": "rm -f conf/hooks.json",
			"postupdate": "cd conf/; ln -s hooks.serveur1.json hooks.json"
		}
	}
}

De cette manière, le script de mise à jour utilisera toujours le fichier hooks.json quelque soit le serveur. Il sera aussi possible de mettre à jour les fichiers de configuration et le code du système d'accroche directement via le dépôt gitlab. Seul le premier déploiement sur le serveur devra s'effectuer à la main (et aussi si quelqu'un commit un code non fonctionnelle dans le dépôt qui "casse" le script de mise à jour...).

 

J'espère dans cet article vous avoir fourni un apercu assez complet de comment mettre en place un système d'accroche simple avec Gitlab. La solution présentée est sans doute nettement améliorable et posera sans doute problème avec certains projets à cause de certaines faiblesses (script PHP obligatoire mais il est possible de le remplacer par un autre langage, une limitation au niveau de l'utilisateur pouvant faire des mises à jour : uniquement www-data dans la plupart des cas...). Mais le petit bonus, c'est que Github propose aussi un système de webhooks même si la syntaxe du json fourni est un peu différente, mais avec quelques modifications, il sera bien sûr possible d'avoir un resultat assez similaire avec Github.

 

Edition du 26/03/2015 : Merci beaucoup @bobmaerten pour avoir été la source d'inspiration du système ainsi que pour ses précieux conseils pendant la mise en place de la solution (et pour ses précieux conseils pour tout ce qui touche au développement/administration système d'ailleurs clin.png).

L'auteur

Toshy

Passionné par l'informatique depuis mon plus jeune age, j'anime un blog sur le développement web et les différents domaines qui s'y attachent : administration système, bonne pratique de développement, framework web.
Je suis développeur web de profession, diplômé d'un DUT d'informatique, je travaille actuellement au sein du service informatique d'une université.