Après avoir mis en place mon nouveau blog avec le générateur Hugo, il m’avait fallu voir comment automatiser la publication. En effet, Hugo génère dans un dossier public/ les pages web statiques et celles-ci doivent être déposées sur un serveur web pour les présenter. Voici donc ce que j’ai construit rapidement pour qu’une publication soit aussi simple qu’un git commit; git push.

Les outils

Voici les outils utilisés, j’avais d’ores et déjà ceux-ci à disposition.

  • Un hébergement Git (en l’occurrence une instance Gitea personnelle)
  • Jenkins comme outil de CICD (je pense qu’un équivalent fera aussi bien le taff, mais c’est celui que je connais le mieux)
  • Le binaire Hugo
  • Git installé

Initialisation du site

Pour pouvoir écrire tranquillement mes billets depuis mon PC, j’ai le binaire Hugo installé dessus dans /usr/local/bin. J’ai également git installé dessus pour pouvoir communiquer avec mon repo.

Les sources de mon blog seront déposées dans /home/seb/blog. Ce dossier est initialisé par Hugo de la façon suivante :

$ hugo new site blog

Ce qui nous donne le retour suivant :

Congratulations! Your new Hugo site is created in /home/seb/blog.

Just a few more steps and you're ready to go:

1. Download a theme into the same-named folder.
   Choose a theme from https://themes.gohugo.io/ or
   create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
   with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".

Visit https://gohugo.io/ for quickstart guide and full documentation.

La sortie de la commande nous propose d’installer un thème graphique, vous pouvez choisir celui à votre préférence sur leur site. Pour ma part, il s’agit du thème Casper3. Nous l’installons de la manière suivante :

$ cd /home/seb/blog
$ git init
$ git submodule add https://github.com/jonathanjanssens/hugo-casper3.git themes/hugo-casper3

L’intérêt de le cloner en submodule est que le repo du thème sera indépendant du votre tout en se trouvant dans la même arborescence du projet. Cela permet de gérer les dépendances tierces d’un projet tout en gardant des commits séparés.

Nous ajoutons ensuite ce thème à la config du site.

$ echo 'theme = "hugo-casper3"' >> /home/seb/blog/config.toml

On ajoute un premier article.

$ hugo new posts/first_article.md
/home/seb/blog/content/posts/first_article.md created

On ouvre /home/seb/blog/content/posts/first_article.md avec son éditeur de texte favori (pour ma part j’utilise Zettlr pour la rédaction en Markdown). Dedans vous verrez que le début est une entête au format YAML.

---
title: "First_article"
date: 2021-01-04T16:38:47+01:00
draft: true
---
  • Title vous permet de définir le titre de l’article, par défaut il s’agira du nom du fichier.
  • La date de création
  • L’état brouillon. Passer à false pour qu’il soit visible sur le site.

Mettez drat: false, puis ajoutez un peu de contenu et sauvegardez le fichier.

Le minimum est fait, on peut lancer le serveur de test d’Hugo pour voir ce que ça donne.

$ hugo serve

Se connecter à l’URL http://localhost:1313 et voilà le résultat :

hugo1.png

Notez que si vous éditez le fichier de l’article, Hugo le rechargera à chaque sauvegarde sans avoir besoin de redémarrer le serveur.

Voilà, le site de base est construit. Il nous reste donc à le passer en mode versionné via Git.

Créer le repo Git

Chez votre hébergeur favori, créez un nouveau dépôt Git. Récupérez ensuite son URL pour pouvoir le définir comme remote dans le répertoire de travail précédemment créé.

$ git remote add origin https://url_du_repo.git
# on consulte les remote pour voir
$ git remote -v
origin	https://url_du_repo.git (fetch)
origin	https://url_du_repo.git (push)

L’état actuel du repo devrait être le suivant :

$ git status

Sur la branche master

Aucun commit

Modifications qui seront validées :
  (utilisez "git rm --cached <fichier>..." pour désindexer)
	nouveau fichier : .gitmodules
	nouveau fichier : themes/hugo-casper3

Fichiers non suivis:
  (utilisez "git add <fichier>..." pour inclure dans ce qui sera validé)
	archetypes/
	config.toml
	content/

Vous noterez que Git ne voit que le dossier content/ dans lequel se trouve notre premier billet. Git ignore les dossiers vites. Si vous souhaitez les envoyer sur le remote, créez dans chacun des dossiers un fichier caché vide. (ex : touch .emptydir)

Envoyons tout ça chez notre hébergeur.

$ git add -A
$ git status
Sur la branche master

Aucun commit

Modifications qui seront validées :
  (utilisez "git rm --cached <fichier>..." pour désindexer)
	nouveau fichier : .gitmodules
	nouveau fichier : archetypes/default.md
	nouveau fichier : config.toml
	nouveau fichier : content/posts/first_article.md
	nouveau fichier : themes/hugo-casper3

$ git commit -m 'first commit'
[master (commit racine) 56f708b] first commit
 5 files changed, 22 insertions(+)
 create mode 100644 .gitmodules
 create mode 100644 archetypes/default.md
 create mode 100644 config.toml
 create mode 100644 content/posts/first_article.md
 create mode 160000 themes/hugo-casper3

$ git branch --set-upstream-to=origin/master master
La branche 'master' est paramétrée pour suivre la branche distante 'master' depuis 'origin'.

$ git push -u origin master
Énumération des objets: 10, fait.
Décompte des objets: 100% (10/10), fait.
Compression par delta en utilisant jusqu'à 16 fils d'exécution
Compression des objets: 100% (6/6), fait.
Écriture des objets: 100% (10/10), 895 octets | 895.00 Kio/s, fait.
Total 10 (delta 0), réutilisés 0 (delta 0), réutilisés du pack 0
remote: . Processing 1 references
remote: Processed 1 references in total
To https://url_du_repo.git
 + 28f6a11...56f708b master -> master
La branche 'master' est paramétrée pour suivre la branche distante 'master' depuis 'origin'.

Et voilà c’est envoyé.

hugo_gitea.png

Mise en place du Webhook avec Jenkins

Pour pouvoir automatiser la génération, il faut que Jenkins (mon moteur de CICD) soit informé qu’il doive déclencher le traitement associé lorsqu’un commit est poussé sur le repo. Pour cela nous utilisons les Webhooks. Attention, cela nécessite que le serveur Jenkins soit joignable depuis Internet dans le cas où vous hébergez depuis GitHub ou GitLab par exemple. Chez moi, ils discutent entre deux sur le LAN.

Création d’utilisateurs

Dans mon cas toutes les actions sont liées à Gitea, mais elles doivent être similaires pour GitHub ou autre. Adaptez selon votre hébergeur. Chez votre hébergeur Git, créez un token pour l’utilisateur qui sera chargé de faire les déploiements à partir de ce repo.

Enregistrez ce token dans les credentials de Jenkins

deploy_token.png

Allez dans le menu Configure => Configure System de Jenkins pour créer un nouveau serveur Gitea. Mettez en Credentials celui créé précédemment.

gitea_jenkins.png

Jenkins ne permettant pas les connexions anonymes par défaut, il est nécessaire de créer un utilisateur pour ce traitement. Une fois créé, connectez-vous avec pour lui générer un token dans le menu Configure de son profil. Sauvegardez bien ce token, vous ne le reverrez plus.

jenkins_gitea_token.png

Retournez chez votre hébergeur Git et créez un nouveau Webhook au niveau du repo Blog. Dans mon cas sur Gitea voici les instructions à renseigner :

gitea_webhook.png

L’URL cible peut être formée de la manière suivante :

https://serveur_jenkins/gitea-webhook/post?job=<Job_a_lancer>

Dans le champ “Secret”, renseignez l’utilisateur et le token de la manière suivante : user:token. Celui-ci sera envoyé dans la requête POST pour l’authentification auprès de Jenkins.

Testons le résultat avec le bouton “Test delivery”. Celui-ci va envoyer un faux commit via le Webhook.

test_delivery.png

En cas d’erreur :

  • 404 : l’URL est probablement mal formatée pour Jenkins. Dans le cas du plugin Gitea, il attend bien gitea-webgook dans celle-ci.
  • 403 : Problème d’identification, assurez-vous que les credentials sont les bons

Jenkins et Gitea discutent entre eux, on peut passer à la suite en créant le job !

Création du job Jenkins

En raison de l’organisation de mes serveurs, j’ai créé deux jobs : un qui build sur la machine Jenkins et envoie les sources sur le serveur Web, un qui place les sources au bon endroit avec les bons droits. Mon serveur web ne peut pas accéder à Gitea, Jenkins ne peut donc pas l’utiliser comme agent pour build le site localement. Si vous n’avez pas ces contraintes, il suffit de les fusionner.

Je n’ai pas fait très compliqué, il s’agit de deux jobs Freestyle.

Job Hugo_Build

Hugo_Build est paramétré de la façon suivante :

  • Code Source Management : Git, avec l’adresse et le credential du repo
  • Build Triggers : Pool SCM, sans rien dedans. C’est cette case à cocher qui permettra de réveiller Jenkins lors d’un commit.

Il s’agit de ce nom de job qu’il faut mettre dans l’URL du webhook : https://serveur_jenkins/gitea-webhook/post?job=Hugo_Build

Enfin, j’exécute une simple tache shell qui envoie le contenu du dossier public d’Hugo:

/usr/local/bin/hugo && rsync -avz --delete -r public/ cicd_user@mon_serveur_web:/tmp/hugoblog

La commande rsync permet de synchroniser deux dossiers. Ici, elle est paramétrée pour supprimer ce qui n’est pas présent sur la source. Cela permet donc d’effacer sur le serveur web un article que j’aurais supprimé du Git.

Une dernière étape post build appelle le job Hugo_Publish

Job Hugo_Publish

Hugo_Publish est encore plus simple. Il est configuré pour s’exécuter sur le serveur web (Restrict where this project can be run) puis se contente de faire le même rsync en interne, mais en mettant les bons droits au passage.

sudo rsync -avz --chown apache:apache --delete /tmp/hugoblog/ /srv/hugoblog

Bien évidemment, adaptez l’emplacement des fichiers à l’emplacement attendu par votre serveur Web !

Et voilà, nous avons toute la chaîne technique de créée !

Testons tout ça

Vu que le job a été créé après le commit, nous pouvons le lancer directement dans Jenkins. Si tout va bien niveau droits d’accès, votre premier billet devrait avoir été publié sur votre serveur web.

Pour tester tout la chaîne, il vous suffit de créer ou de modifier le post fait au tout début puis de le pousser sur Git. Si tout va bien, le traitement va se déclencher dans Jenkins puis le résultat sera en ligne au bout de quelques secondes.

N’hésitez-pas à me faire un retour sur cette méthode, si vous voyez des axes d’amélioration ou bien d’autres méthodes !