Introduction
Aussi loin que je me souvienne, j’ai toujours eu un projet personnel ou deux en cours, en plus de mon travail.
Certains de ces “side-projects” sont mêmes allés jusqu’au bout !
La plupart, non.
Avec l’expérience, j’ai observé qu’un des principaux critères pour assurer le succès de mes plus gros projets a été d’automatiser le maximum de tâches, afin de concentrer mon temps sur les projets en eux-mêmes, sans pour autant que la configuration de ces automatisations n’occupe la majeure partie du temps dédié à mes projets perso.
Avec les outils proposés par GitHub j’ai justement réussi à monter facilement et rapidement une usine logicielle légère, complète et efficace. Et surtout, réutilisable sur mes projets suivants.
Pourquoi les usines logicielles sont-elles si rares avec les projets perso ?
Si nous sommes tous d’accord de l’utilité d’une usine logicielle dans notre travail, pourquoi sont-elles si rares sur nos projets perso ?
Principalement car la plupart des solutions sont conçues pour être utilisées dans le cadre d’une ou plusieurs équipes, parfois plus.
En conséquence tout travail investi dans une usine logicielle lourde va être rentabilisé rapidement car utilisé par un grand nombre de personnes. De même, les coûts de maintenance se trouveront partagés entre les membres de l’équipe. Sans compter que les opérations d’hébergement et de dépannage sont souvent assurées par une personne ou une équipe dédiée.
Sur un projet personnel en revanche, les avantages ne bénéficient qu’à une seule personne (vous), tandis que les opérations de maintenance seront toujours à votre charge. S’occuper d’une usine logicielle mal dimensionnée finit généralement par devenir un véritable travail et monopolise la majorité du temps que vous dédiez à votre projet personnel.
D’où la nécessité d’une usine logicielle adaptée à la taille de l’équipe du projet (généralement, juste une seule personne : vous)
Ce que j’attends de mon usine logicielle (pour mes projets perso)
Malgré mon désir de légèreté, j’avais néanmoins des exigences précises concernant l’usine logicielle :
- Être entièrement gratuite.
- Ne pas être chronophage.
et, dans une moindre mesure :
- Ne pas ajouter un nouvel outil avec une nouvelle interface à consulter et maintenir (afin de limiter le nombre d’outils à gérer).
- Avoir une config de build intégrée, personnalisable et réutilisable sauvegardée dans chaque projet.
- Lancer automatiquement le build ET les tests que
j’ai la flemme dej’oublie de lancer sur les projets non-professionnels. - Être à distance pour ne pas ralentir ma machine de dev et pouvoir y accéder partout, à tout moment, que ce soit depuis mon poste de travail ou mon téléphone.
- Pouvoir réutiliser mes livrables (release ET snapshot).
- Avoir un déploiement continu des sites statiques et une publication continue des librairies.
En bref, les seules concessions que j’étais prête à faire sur l’usine logicielle de mes projets personnels, en comparaison de ce dont je disposais dans un cadre professionnel étaient :
- De permettre de soutenir l’activité de toute une équipe (je suis la seule à livrer sur mes projets personnels)
- D’être privé (je n’ai pas d’objections à exposer les sources et les livrables)
(notons que GitHub propose également une version privée de tous ses outils et qui tient la charge d’une équipe, mais payante …)
Les outils GitHub
Heureusement GitHub propose gratuitement différents outils pour construire cette usine logicielle.
Nous verrons donc dans cet article :
- GitHub-actions, comme exécuteur de tâche (l’équivalent du Jenkins habituel)
- GitHub-secrets, pour stocker les informations confidentielles
- GitHub-packages, pour stocker et réutiliser vos livrables (l’équivalent du nexus d’entreprise/npm registry)
- GitHub-pages, pour publier vos déploiements
- Et quelques autres outils gratuits qui ne font pas partie de Github mais qui permettent de compléter agréablement le tout.
Github-actions comme usine logicielle
GitHub-actions est un exécuteur de tâches. C’est lui qui va orchestrer les différentes étapes du cycle de vie du build, de l’intégration à la publication de notre projet. Il est gratuit et disponible de base sur tous les repositories.
Les concepts clés
Pour comprendre Github-actions, il est nécessaire de comprendre plusieurs concepts :
Les actions sont les étapes atomiques du build du projet. Il peut s’agir aussi bien de l’exécution d’une ligne de commande, ou de réutiliser une action de base déjà définie.
Les actions sont écrites dans les jobs, eux-mêmes regroupés au sein d’un workflow.
À chaque workflow est associé un ou plusieurs événements déclencheurs (un commit, la création d’un PR, une action utilisateur…) qui lancera l’exécution des jobs.
Les jobs effectueront leurs actions respectives les unes à la suite des autres. Chaque job tournant sur un runner séparé (comme un serveur). Par défaut les jobs tourneront en parallèle.
Notons qu’il est possible de passer des informations entre les jobs et de les exécuter les uns à la suite des autres, mais qu’il n’y a pas de communication ou d’interaction possible entre les différents workflows d’un même projet. En conséquence chaque workflow doit être pensé comme parfaitement indépendant des autres workflows d’un même repository.
Initialiser GitHub-Actions
Pour utiliser GitHub-Actions, il suffit de créer (et commiter) un nouveau projet dans GitHub, puis de se rendre sur sa page principale.
Comme nous l’avons vu, GitHub-Actions se base sur des workflows. Dans cet article nous aborderons comment les créer manuellement, mais sachez que de base GitHub propose différents workflows pré-configurés pour les besoins les plus courants, accessibles depuis l’onglet Action -> New workflow
Syntaxe des workflows
La configuration de GitHub-Actions se trouve dans le répertoire “.github/workflows” à la racine du projet, qui contient les fichiers YAML définissant les différents workflows.
Chacun de ces YAML suivra la même syntaxe. Par exemple pour le fichier build.yml :
name: MyWorkflow # le nom du workflow on: [push] # l’event qui déclenche le workflow jobs: # les jobs myJob1: # Le nom du job runs-on: ubuntu-latest # l’environnement sur lequel tourne un job steps: # les steps à executer - name: checkout uses: actions/checkout@v2 # checkout le projet courrant dans le runner de job1 - name: Install Node # nom facultatif pour cette action uses: actions/setup-node@v2 # install nodeJs version 12 with: node-version: 12.x - name: Build # le nom est obligatoir pour les actions de ligne de commande run: npm install # execute la ligne de commande myJob2: # un autre job needs: myJob1 # attend la fin du job1 pour s'executer steps: - if:error==0 # lance cette step si aucune erreur n'a été détectée auparavant name: Echo run: | # execute un script sur plusieur ligne de commande echo “fin du job2” echo “build OK”
Ce sont les actions qui effectueront les tâches de base du build de votre projet. Par exemple un checkout, une ligne de commande, l’installation d’un outil, la publication d’un livrable, un déploiement…
Les actions sont en fait des scripts réutilisables entre projets :
Par exemple le code ci-dessous va réutiliser la version v1 de l’action « setup-java », en spécifiant en paramètre la version du JDK à installer.
- uses: actions/setup-java@v2 with: java-version: 11
Ce code va checkout le projet en cours
- uses: actions/checkout@v2
Et ce code va exécuter une ligne de commande. Les actions “run” doivent obligatoirement avoir un nom.
- name: Greeting run: | echo 'Hello world'
Notons que GitHub (et sa communauté) ont implémenté un très large panel d’actions de base, disponibles ici Vous retrouverez également un résumé des actions les plus utiles ici Attention à l’indentation et aux erreurs d’auto-complétion qui seront la source de nombreux bugs.
Afin de les éviter je vous recommande FORTEMENT d’éditer directement vos fichiers à partir de l’éditeur en ligne proposé par GitHub ou bien d’installer un plugin GitHubActions dans votre IDE.
Exemple de workflow
Généralement, chaque projet a au moins un workflow comportant :
1 – Un job lancé au push
name: RunWhenPushWorkflow on: [push] jobs: job1: ...
2 – Le checkout du projet sur l’environnement de build, et l’installation des outils de build
... steps: - uses: actions/checkout@v2 - uses: actions/setup-java@v2 with: java-version: 11 ...
3 – Et un script shell qui lance le build du projet
... steps: ... - name: Build your project run: | mvn clean install mvn verify
Dans l’exemple ci-dessus, run : |
indique qu’il s’agit d’un script shell multi ligne.
Build d’un projet
GitHub-Actions & gestion des events : auto-builder lors d’un commit
Nous allons donc créer un workflow qui va lancer le build de notre projet et exécuter les tests.
Contenu du fichier .github/workflows/build.yml
name: Build on: push: # a chaque push sur le master ou PR branches: - master pull_request: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-java@v2 with: java-version: 11 - name: Build run: mvn clean install. #instruction de build de mon projet
Une fois ce fichier poussé sur GitHub, nous pouvons voir le job s’exécuter. Pour cela allez dans votre répertoire de projet sur GitHub, puis dans l’onglet “Actions” :
Vous devriez voir cet écran s’afficher avec, pour chaque workflow, les résultats des derniers builds
En cliquant sur chaque commit , vous aurez accès à divers informations (détails du build, logs, artifacts, relancer le build, etc).
Lors des prochains push, vous pourrez ainsi aisément vérifier si l’intégration a réussi ou échoué, et pourquoi.
Les limitations de GitHub-Actions
Malheureusement GitHub-actions reste un outil assez basique, qui ne permet pas :
- D’inclure ou d’importer un workflow dans un autre
- De réaliser une boucle ou une indirection (les instructions else, for ou while n’étant pas supportées)
Toutes ces limitations peuvent être contournées en créant une action custom. Ce point ne sera pas abordé dans cet article.
Un vault sécurisé avec GitHub-secrets
Pour la suite de ce tutoriel, nous allons avoir besoin de manipuler des tokens de connexion et autres mots de passe confidentiels. En bref, des données que vous ne souhaiteriez pas voir apparaître en clair dans vos repositories GitHub. C’est ce qu’on appelle des secrets. Souvent, vous aurez besoin de ces secrets/données sécurisées pour publier ou déployer vos projets.
GitHub-secrets vous permet de chiffrer et stocker ces secrets dans un vault, une sorte de “coffre-fort numérique” sécurisé et interne au projet.
Pour cela, allez dans l’onglet “Settings”, puis la section “Secrets” :
Une fois insérés ces “secrets” pourront être renommés, modifiés, supprimés mais leur contenu ne sera plus affichable dans l’éditeur. Ils ne pourront être consultés que par les workflows GitHub.
Vous pourrez ensuite manipuler vos “secrets” dans GitHub-actions depuis la variables “secrets”, via la syntaxe: ${{ secrets.SECRET_KEY }}
Notez que GitHub-secrets provisionne déjà de base l’objet “secrets” avec différentes données confidentielles. Par exemple, ${{ secrets.GITHUB_TOKEN }}
donne le token de connexion de la session GitHub-actions en cours.
Gestion des livrables taggés et versionnés avec GitHub-Packages
GitHub-packages est un outil d’hébergement de packages, un outil pour publier et consommer les livrables de vos builds, à la manière d’un repository Node.js ou d’un nexus maven traditionnel. Il vous permettra ainsi d’héberger les librairies de vos précédents projets et de les réutiliser pour les projets suivants.
Exemple de workflow avec GitHub-Packages
Attention : si votre repository GitHub est public, vous ne pourrez pas supprimer les versions publiées dans GitHub-packages. En conséquence il est fortement déconseillé de publier les versions snapshot sur GitHub-Packages. (une solution sera proposée dans la sous-partie “Quelles solutions pour les versions snapshots ?”)
Afin d’éviter la publication des versions snapshot nous allons donc proposer le build de version lors de l’event d’un tag de version. Nous allons donc créer un fichier : .github/workflows/delivery.yml
name: Delivery on: create: tags: - v*. # spécifie une regexp pour les noms de version (fortement conseillé) jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-java@v1 with: java-version: 11 - name: Build # comme on ne peut pas passer d'information entre le workflow de build run: gradle build # et celui de delivery, # la commande de build doit être ré-exécutée - name: Publish to GitHub Packages env: # passage de paramètres pour la tâche de publication GITHUB_ACTOR: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPO_URL: ${{ github.repository }} run: gradle publish # script de publication (gradle)
Dans la plupart des cas, la publication d’un livrable dans GitHub-packages (ou sa récupération depuis GitHub-packages) se fait via l’outil de build du projet (maven, gradle ou npm). En conséquence, il est nécessaire de modifier la configuration du build du projet :
Par exemple, pour un projet gradle, dans le fichier build.gradle.kt
:
project.apply(plugin = ("maven-publish")) val publishingExtension = project.extensions["publishing"] as org.gradle.api.publish.internal.DefaultPublishingExtension; if (System.getenv("GITHUB_REPO_URL") != null) publishingExtension.repositories { maven { this.setUrl("https://maven.pkg.github.com/" + System.getenv("GITHUB_REPO_URL")) this.credentials { username = System.getenv("GITHUB_ACTOR") password = System.getenv("GITHUB_TOKEN") } } }
Et si vous avez un projet maven :
- name: Deploy to Github Package Registry env: GITHUB_USERNAME: x-access-token GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: mvn --settings settings.xml deploy
Le contenu du fichier settings.xml
est le suivant :
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 <activeProfiles> <activeProfile>github</activeProfile> </activeProfiles> <profiles> <profile> <id>github</id> <repositories> <repository> ... </repository> </repositories> </profile> </profiles> <servers> <server> <id>github</id> <username>${env.GITHUB_USERNAME}</username> <password>${env.GITHUB_TOKEN}</password> </server> </servers> </settings>
Mais comment être sûr qu’il ne s’agit pas d’une version snapshot ? Nous allons rajouter une condition pour vérifier le nom de la version :
- name: extract build version run: | gradle generatePomFileForKotlinMultiplatformPublication echo "::set-env name=BUILD_VERSION::$(mvn help:evaluate -Dexpression=project.version -q -f M3/build/publications/kotlinMultiplatform/pom-default.xml -DforceStdout)" echo "::set-env name=BUILD_ARTIFACT_ID::$(mvn help:evaluate -Dexpression=project.artifactId -q -f M3/build/publications/kotlinMultiplatform/pom-default.xml -DforceStdout)" echo "::set-env name=BUILD_GROUP_ID::$(mvn help:evaluate -Dexpression=project.groupId -q -f M3/build/publications/kotlinMultiplatform/pom-default.xml -DforceStdout)" - if: (env.BUILD_VERSION && false == contains(env.BUILD_VERSION, 'SNAPSHOT')) name: Publish to GitHub Packages env: GITHUB_ACTOR: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPO_URL: ${{ github.repository }} run: gradle build publish
Quelles solutions pour les versions snapshots?
Comme nous l’avons dit plus haut, GitHub-packages ne permet pas de supprimer des livrables des projets publics et en conséquence ne permet pas de stocker les versions dites “snapshot” des projets publics. Heureusement, il existe d’autres outils gratuits proposant le même service mais acceptant la suppression et la re-création de versions, tels que
Un exemple de projet utilisant package-cloud pour ses snapshots :
GitHub propose une plateforme d’hébergement pour les sites web statiques de base : GitHub-Pages. Une solution pour les sites dynamiques et les micro-services sera abordée dans la sous-partie “Et si c’est un projet qui nécessite un serveur ?” Pour publier, ajouter l’action Github-pages : L’URL sur lequel le site-web sera déployé est définie dans les settings d’un projet, dans la section “github-pages”, en fonction du nom du repository et du projet (par exemple https://nomDuRepo/github.io/nomDuProjet) Notez que publier un site-web sur GitHub-pages revient à créer une branche nommée ‘gh-pages’. Mais l’avantage d’utiliser une action du market-place est qu’on peut ainsi bénéficier de différentes options avancées intéressantes, comme par exemple déployer en plus du site web la couverture de tests et les métriques de qualités. Malheureusement GitHub ne propose pas d’outil de déploiement pour les serveurs. Mais des solutions (gratuites !) existent. Par exemple propose de faire tourner vos conteneurs docker (sous couvert de certaines conditions et de ne pas avoir de gros besoins.) Un exemple de projet nodeJs déployé sur heroku :
Malheureusement il n’est pas possible d’importer un workflow depuis un autre, ce qui nous oblige à, pour réutiliser la configuration d’un build, dupliquer le répertoire .workflows entre nos différents projets GitHub et les ré-adapter. Et pour finir, je vous donne quelques-un de mes projets utilisant GitHub-Actions comme usine logicielle : Grâce aux différents outils proposés par GitHub j’ai réussi à avoir l’usine logicielle que je souhaitais, et qui est : Et surtout, le plus important : mon usine logicielle ne nécessite aucune maintenance une fois en place, me permettant ainsi de me concentrer sur mes projets eux-même, de les mener à bout et de les réutiliser.Déploiement des sites statiques avec Github-Pages
Configuration de Github Pages
- name: Deploy
if: success(). # publication seulement en cas de success des actions précédentes
uses: crazy-max/ghaction-github-pages@v1
with:
target_branch: gh-pages
build_dir: dist # le répertoire “dist” va être publié
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Et si c’est un projet qui nécessite un server ?
Réutiliser son usine logiciel
Notons-toutefois la présence de l’outil “GitHub-template”, qui nous permet de sauvegarder nos configuration de build et de les réutiliser facilement lors de la création d’un nouveau projet.Le meilleur pour la fin : quelques exemples de projets
Et pour conclure