Hébergement Github Page avec sources privées

La mise en production d'un site Github Page par d'un workflow automatisé est un sujet bien documenté. Il s'agit généralement de compiler les sources présentes sur une branche du dépôt puis de copier le résultat sur une autre branche du même dépôt. Or un prérequis de Github Page est d'être hébergé en public.

Je vais détailler ici une solution alternative permettant de séparer les sources du build. Cette architecture présente deux principaux avantages :

  • cela autorise les sources à être privées et donc permet de contrôler ce que l'on dévoile en production ;
  • cela permet une claire isolation entre le stockage des sources et l'hébergement du site. Ainsi de futures migrations de services s'effectuerons sans laisser de "résidus". Les dépôts devenus inutiles pourront simplement être supprimés ou archivés.

Le plan de bataille

Nous allons avoir besoin de deux dépôts Github et d'un environnement de développement front (NodeJs, Git, SSH...). Nous hébergerons le code source sur un dépôt privé qui exécutera un workflow automatisé Github Actions afin de copier une version compilée du site sur un dépôt public défini comme Github Page. Voici une représentation simplifiée des éléments mis en œuvre.

Le dépôt de production

Un compte personnel Github peut comporter une Github Page principale et des Github Pages secondaires qui y seront rattachées. Le dépôt de la Github Page principale doit être nommé suivant le pattern username.github.io.

DépotPage
username.github.io.gitusername.github.io
projectOne.gitusername.github.io/projectOne
projectTwo.gitusername.github.io/projectTwo

Nous devons donc créer un dépôt respectant cette contrainte en prenant soin de le définir comme public. Une première configuration peut être effectuée de suite en nous rendant dans l'onglet Settings du dépôt nouvellement créé, puis dans la section Pages. Selectionnez y la branche principale comme source de la Github Page.

Le dépôt de développement

Créons maintenant un second dépôt pour accueillir les sources. Par soucis de clarté, il me semble pertinent d'utiliser le pattern précédent en y ajoutant un suffixe : username.github.io.sources.

Ce dépôt peut être défini comme privé puisque c'est le but de cette article.

Authentification SSH

L'authentification entre les deux dépôts est effectuée par clés SSH stockés dans les configurations de de Github. Je ne m'attarde pas sur ce sujet car la procédure pour en générer est clairement expliquée dans la documentation de Github.

Une fois obtenu nous allons les enregistrer la clé privée comme Secret du dépôt de sources. Les valeurs secrètes de Github sont des variables d'environnement protégées. Vous pouvez utiliser leurs références mais leurs valeurs restent secrète. Rendez-vous donc dans l'onglet Settings du dépôt puis Secrets.

La clé publique est utilisée sur le dépôt accueillant le build du site. Allez dans ses Settings puis cette fois dans Deploy Keys afin d'enregistrer votre clé. Attention, il est nécessaire de cocher la case Allow write access afin d'autoriser la copie des fichiers compilés dans ce dépôt.

Le script de déploiement

Nous devons maintenant automatiser le workflow par un script définissant les actions à effectuer. Les Github Actions nous permettent cela très facilement. Ouvrez l'onglet Actions du dépôt de sources puis créez un nouveau workflow vierge.

Les workflows de Github Actions permettent d'exécuter des commandes dans l'environnement conteneurisé de notre choix. Ils sont écrits en syntaxe [YAML](https://fr.wikipedia.org/wiki/YAML). Voici le type de workflow que j'utilise :
1name: Build and Deploy
2 
3on:
4  push:
5    branches:
6      - main
7 
8jobs:
9  deploy:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v2
13 
14      - uses: actions/setup-node@v2.1.2
15        with:
16          node-version: '14.x'
17 
18      - name: Get yarn cache directory path
19        id: yarn-cache-dir-path
20        run: echo "::set-output name=dir::$(yarn cache dir)"
21 
22      - uses: actions/cache@v2
23        id: yarn-cache 
24        with:
25          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
26          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
27          restore-keys: |
28            ${{ runner.os }}-yarn-
29 
30      - run: yarn install --frozen-lockfile
31      - run: yarn run build
32 
33      - uses: peaceiris/actions-gh-pages@v3
34        with:
35          deploy_key: ${{ secrets.DEPLOY_KEY }}
36          publish_dir: ./public
37          external_repository: AurelienDud/AurelienDud.github.io
38          publish_branch: main

La clé on définit les événements déclenchant le script. Il peut s'agir d'un cron ou changement sur dépôt. Soyez prudent si vous êtes tenté de définir un déclenchement basé sur un cron car les comptes Free de Github sont limités à 2000 minutes de fonctionnement par mois sur les dépôts privés. Le plus classique pour un site est de déployer lorsque des modifications sont poussées sur la branche principale.

1on:
2  push:
3    branches:
4      - main

Voici au passage un rappel sur les bonnes pratiques de branches git. Les développements doivent s'effectuer sur des branches dédiées. Lorsqu'une release est souhaitée une nouvelle branche doit être créée afin d'accueillir les fusions. Ainsi la branche principale n'est pas impactée en cas de fusion problématique.

La clé *jobs* va décrire les actions à effectuer. L'unique action de ce workflow se nomme *deploy*. ```yaml jobs: deploy: ```

Nous définissons ensuite un environnement serveur.

1runs-on: ubuntu-latest

Puis les steps des actions à exécuter sous forme de liste. Le mot clé uses permet d'indiquer que nous souhaitons utiliser des scripts réutilisables. Comprendre par là une librairie Github Actions.

Nous commençons par récupérer les dernières modifications sur le dépôt.

1- uses: actions/checkout@v2

Avant d'initialiser NodeJs à la version souhaitée.

1- uses: actions/setup-node@v2.1.2
2    with:
3      node-version: '14.x'

et d'activer le cache tel que défini dans la documentation dédiée.

1- name: Get yarn cache directory path
2  id: yarn-cache-dir-path
3  run: echo "::set-output name=dir::$(yarn cache dir)"
4 
5- uses: actions/cache@v2
6  id: yarn-cache 
7  with:
8    path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
9    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
10    restore-keys: |
11      ${{ runner.os }}-yarn-

Ensuite nous exécutons les commandes d'installation et de compilation contenu dans le package.json du projet que nous créerons plus tard. Un coup d'œil dans la documentation de GatsbyJs nous confirme qu'elles seront très classiques.

1- run: yarn install --frozen-lockfile
2- run: yarn run build

Le première commande installe les dépendances en respectant les versions mentionnées dans le fichier yarn.lock (la commande npm ci doit-être utilisé en cas d'utilisation de NPM). Cela va garantir l'usage de versions testées en développement et donc prévenir des problèmes d'incompatibilité.

Puis la seconde commande exécute la compilation des sources.

Le site est généré alors mais ses fichiers se trouvent sur une machine virtuelle de Github.

Pour déployer ces sources sur le dépôt dédié j'utilise une action développée par un confrère developpeur. Il suffit d'indiquer la référence de la clé SSH (deploy_key), le répertoire dans lequel a été généré le build (publish_dir), puis les informations de destination (external_repository, publish_branch).

1- uses: peaceiris/actions-gh-pages@v3
2  with:
3    deploy_key: ${{ secrets.DEPLOY_KEY }}
4    publish_dir: ./public
5    external_repository: AurelienDud/AurelienDud.github.io
6    publish_branch: main

Voilà, l'infrastructure du projet est prête ! C'est à maintenant à vous de jouer pour développer un beau projet. A la prochaine.

Publié par Aurélien Dudonney le 2 septembre 2021
© 2021 Aurélien Dudonney