Les outils GitHub, une usine logicielle à la mesure de vos projets perso

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 de j’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 https://github.com/marketplace?type=actions. Vous retrouverez également un résumé des actions les plus utiles ici https://github.com/sdras/awesome-actions. 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
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">

    <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 https://packagecloud.io/.

Un exemple de projet utilisant package-cloud pour ses snapshots : https://github.com/sarahBuisson/easy-rules/blob/master/.github/workflows/build.yml

Déploiement des sites statiques avec Github-Pages

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 ?”

Configuration de Github Pages

Pour publier, ajouter l’action 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 }}

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.

Et si c’est un projet qui nécessite un server ?

Malheureusement GitHub ne propose pas d’outil de déploiement pour les serveurs. Mais des solutions (gratuites !) existent. Par exemple https://www.heroku.com 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 : https://github.com/sarahBuisson/server-sample

Réutiliser son usine logiciel

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.
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 finir, je vous donne quelques-un de mes projets utilisant GitHub-Actions comme usine logicielle :

Et pour conclure

Grâce aux différents outils proposés par GitHub j’ai réussi à avoir l’usine logicielle que je souhaitais, et qui est :

  • Complètement gratuite (du moins tant que tous mes repos sont publics)
  • Ne nécessite pas de manipuler un outil supplémentaire
  • Rapide à mettre en place et personnalisable (je duplique le répertoire “.github/workflows“ entre mes différents projets similaires et j’adapte au besoin)
  • Entièrement automatisée (grâce à GitHub-actions)
  • Assure un déploiement continu et un hébergement gratuit de mes sites (grâce à GitHub-pages pour les sites web statique et à heroku pour les micro-services)
  • Rend mes projets disponibles et faciles à réutiliser (grâce à GitHub-packages pour les releases et package-cloud pour les snapshots)
  • Est sécurisée (grace à GitHub-secrets)

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.

Published by

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.