Un peu plus d’un mois après la création de mon article sur le déploiement de mon blog, je tombe sur cet article, qui parle notamment d’une nouvelle fonctionnalité de gitea, l’intégration de CI/CD directement dans l’outil.
Je me dis que je testerai bien tout ça, et quoi de mieux que déployer mon blog avec.
Pour ceci, je ne touche pas à la configuration nginx que j’avais déjà, vous pouvez la retrouver dans mon précédent article.
Gitea Actions
Gitea Actions n’est pas sans rappeler Github Actions, et pour cause, c’est exactement la même syntaxe, techniquement si vous êtes sous github, vous pouvez quasiment copier votre repository de github à gitea, avec simplement le répertoire .github à renommer en .gitea (et encore, je ne suis pas sûr que ce soit réellement utile).
Je découvre
Personnellement, je n’avais jamais utilisé Github Actions avant, donc c’est un nouveau format que je découvre en écrivant cet article. Juste pour votre information, je me suis principalement basé sur la documentation officielle de Github, et notamment la syntaxe et les évènements.
Pour manipuler un peu, j’ai forké un projet sur Github, un plugin asdf (d’ailleurs ça fera l’objet d’un futur article), pour lequel j’ai créé une petite pipeline.
Installation
Pour ceci, il vous faudra au minimum la version 1.19 de gitea.
Configuration de gitea
Je pars du principe que vous avez déjà un gitea d’installé, cependant il y a une petite configuration à faire, il suffit de modifier le fichier app.ini
:
[actions]
ENABLED = true
Ensuite dans les paramètres (onglet Repository) de votre dépôt git, vous aurez une option Enable Repository Actions qu’il suffit d’activer.
Installation d’un runner
Service systemd
Avant de l’installer, nous allons créer un utilisateur pour notre runner, et le mettre dans le groupe docker :
$ useradd --system --shell /bin/bash --home-dir /var/lib/gitea-runner --create-home gitea-runner
$ usermod -G docker -a gitea-runner
On télécharge l’agent :
$ wget https://dl.gitea.com/act_runner/main/act_runner-main-linux-amd64 -O /usr/local/bin/act_runner
$ chmod +x /usr/local/bin/act_runner
Nous allons créer un petit service systemd également :
# /etc/systemd/system/gitea_runner.service
[Unit]
Description=Gitea Actions Runner
After=network-online.target
[Service]
Type=simple
WorkingDirectory=/var/lib/gitea-runner/
User=gitea-runner
Group=gitea-runner
ExecStart=/usr/local/bin/act_runner daemon
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
Nous ne l’activons pas maintenant
Configuration de gitea
Il faut maintenant aller sur gitea, avec un compte administrateur, pour aller enregistrer un nouveau runner. Pour ceci il faut aller dans Administration du site puis Runners, et on clique sur Create new Runner. Il vous donnera donc un token.
Enregistrement du runner
Puis on se connecte avec cet utilisateur pour enregistrer notre runner :
$ sudo -i -u gitea-runner
et on lancera une commande du style :
$ ./act_runner register --instance http://<your_gitea_instance> --token <your_runner_token> --no-interactive
Il faut savoir que le runner à un système de label, qui permets de savoir sur quel runner lancer nos workflows, mais aussi qu’elle image lancer.
Par défaut, nous avons 4 labels (de souvenir), ubuntu-latest, ubuntu-22.04, ubuntu-20.04 et ubuntu-18.04. En réalité, ces 4 labels utilisent la même image docker, à savoir node:16-bullseyes (quoi qu’il me semble que ubuntu-18.04 utilise une base buster).
De ce que j’ai compris, beaucoup d’Actions utilisent nodejs pour s’exécuter, par exemple le plus utilisé, qui est actions/checkout@v3
, ne fonctionnera pas si vous n’avez pas nodejs sur le conteneur (on y reviendra).
Donc nous pouvons utilisez nos propres labels, personnellement, j’ai mis ceci :
- linux
- linux-x86
- linux-amd64
- ubuntu-latest (car c’est souvent ce qu’on a quand on fait un copier/coller bêtement ^^)
Nous pouvons aussi associé une image, en utilisant cette syntaxe :
alpine:docker://alpine:3.17
. Mais puisqu’il faut nodejs sur l’image utilisée, ce n’est pas forcément utile.
Donc pour enregistrer mon runner, j’ai lancé ceci :
$ ./act_runner register --instance http://<your_gitea_instance> --token <your_runner_token> --no-interactive --labels "linux,linux-x86,linux-amd64,ubuntu-latest""
Une fois qu’il apparait bien dans votre gitea, nous pouvons lancer le service systemd :
$ systemctl daemon-reload
$ systemctl enable --now gitea_runner.service
Et voilà, notre runner est maintenant fonctionnel, je compte en créer un autre sur un raspberry Pi, pour avoir un runner ARM.
Workflow
Si vous êtes familier avec les github actions, vous savez donc qu’il y a 3 niveaux de définition :
- Les workflows : Les workflows sont composés de jobs, et seront exécuter sur un évènement (push, tag, pull_requests …)
- Les jobs : Les jobs sont composés de steps, ce sera un ensemble de tâches à exécuter ()
- Les steps : Les steps vont êtres les tâches à exécuter, soit on utilise une actions, soit on utilise une commande.
Mon workflow
Si vous avez lu mon précédent article, vous avez dû voir qu’avec droneio, j’utilisais le principe de promote pour passer à la prod. Nous n’avons plus ce principe coté gitea Actions, donc nous utiliserons les Pulls Requests :
- J’écris mon article sur une nouvelle branche, ça me crée un environnement temporaire.
- Je crée un Pull Request.
- Une fois ok, je merge mon article vers la main.
- De cet évènement, nous supprimons mon environnement temporaire.
- Un push (via le merge) s’effectue sur ma branche main, et lance une pipeline de déploiement vers la production.
Pour ceci, j’ai créé 3 workflows différents :
- Dev, sur push sur une branche autre que main
- Delete_env, sur clôture et merge d’une Pull Request
- Production, sur push sur main
Dev
Sur ce workflow, nous allons avoir plusieurs jobs :
- build : Cette partie va lancer la commande hugo pour générer mon blog
- test : Quelques tests, notamment vérifier que le html est correcte, et une passe orthographique
- deploy : Déploiement sur un environnement de dev
Pour chaque jobs, j’ai ajouté un step pour la notification, pour le build et le test, je ne veux qu’en cas d’erreur, par contre, pour le deploy, je veux tout le temps recevoir une notification.
Voici ce que ça donne sur discord :
Et voici donc mon fichier :
name: Build and Test blog
run-name: buildandtest
on:
push:
branches-ignore:
- main
jobs:
build:
steps:
- name: checkout
uses: https://github.com/actions/checkout@v3
- name: configure container
run: apt update && apt install curl
- name: Setup Hugo
uses: https://github.com/peaceiris/actions-hugo@v2
with:
hugo-version: '0.102.1'
extended: true
- name: Build Blog
env:
TZ: 'Europe/Paris'
run: hugo --minify --environment production
- name: Push artifacts
run: |
cd public && \
tar czvf ../blog.tar.gz * && \
cd .. && \
GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | cut -d"/" -f1) && \
GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d"/" -f2) && \
curl -XPUT -H "Authorization: token ${{ secrets.CI_GITEA_PACKAGES_TOKEN }}" --upload-file blog.tar.gz $GITHUB_SERVER_URL/api/packages/${GITHUB_REPO_OWNER}/generic/${GITHUB_REPO_NAME}/${GITHUB_SHA}/blog.tar.gz
- name: Notify if failed
uses: https://github.com/sarisia/actions-status-discord@v1
if: ${{ failure() }}
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: "Failure"
content: "Hey"
title: "build"
description: "Build ${{ env.CI_GIT_BRANCH }} Failed"
color: 0xff0000
test:
needs: [build]
steps:
- name: checkout
uses: https://github.com/actions/checkout@v3
with:
fetch-depth: 0
- name: Download artifacts
run: |
GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | cut -d"/" -f1) && \
GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d"/" -f2) && \
curl -XGET -H "Authorization: token ${{ secrets.CI_GITEA_PACKAGES_TOKEN }}" $GITHUB_SERVER_URL/api/packages/${GITHUB_REPO_OWNER}/generic/${GITHUB_REPO_NAME}/${GITHUB_SHA}/blog.tar.gz > blog.tar.gz && \
mkdir public && tar xzf blog.tar.gz -C public && rm -f blog.tar.gz
- name: html5validator
uses: https://github.com/Cyb3r-Jak3/html5validator-action@v7.2.0
with:
root: public/
- name: Setup java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
- name: Languagetool
run: |
wget -4 -q https://languagetool.org/download/LanguageTool-5.6.zip
unzip -q LanguageTool-5.6.zip
for file in $(git diff --name-only origin/main | grep ".md"); do echo "Test $file : " && sed -e '/---/,/---/d' -e '/```/,/```/d' $file | java -jar LanguageTool-5.6/languagetool-commandline.jar -d "WHITESPACE_RULE,LAB,APOS_INCORRECT" -l fr -c utf-8 -; done
- name: Notify if failed
uses: https://github.com/sarisia/actions-status-discord@v1
if: ${{ failure() }}
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: "Failure"
content: "Hey"
title: "test"
description: "Test ${{ env.CI_GIT_BRANCH }} Failed"
color: 0xff0000
deploy:
needs: [build]
steps:
- name: Download artifacts
run: |
GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | cut -d"/" -f1) && \
GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d"/" -f2) && \
curl -XGET -H "Authorization: token ${{ secrets.CI_GITEA_PACKAGES_TOKEN }}" $GITHUB_SERVER_URL/api/packages/${GITHUB_REPO_OWNER}/generic/${GITHUB_REPO_NAME}/${GITHUB_SHA}/blog.tar.gz > blog.tar.gz && \
tar xzf blog.tar.gz && rm -f blog.tar.gz
- name: Set branch name
run: |
echo "CI_GIT_BRANCH=$(basename $GITHUB_REF_NAME)" | tee -a $GITHUB_ENV
- name: Deploy blog
uses: https://github.com/appleboy/scp-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
source: "*"
target: "/opt/www/blog/${{ env.CI_GIT_BRANCH }}/"
rm: true
- name: Configure variables for notify
if: ${{ always() }}
run: |
if ${{ success() }}; then
echo "DISCORD_COLOR=0x00ff00" | tee -a $GITHUB_ENV
echo "DISCORD_STATUS=Success" | tee -a $GITHUB_ENV
elif ${{ failure() }}; then
echo "DISCORD_COLOR=0xff0000" | tee -a $GITHUB_ENV
echo "DISCORD_STATUS=Failure" | tee -a $GITHUB_ENV
else
echo "DISCORD_COLOR=0xeca714" | tee -a $GITHUB_ENV
echo "DISCORD_STATUS=Unknown" | tee -a $GITHUB_ENV
fi
- name: Notify
uses: https://github.com/sarisia/actions-status-discord@v1
if: ${{ always() }}
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: "${{ env.DISCORD_STATUS }}"
content: "Hey"
title: "deploy"
description: "Deploy ${{ env.CI_GIT_BRANCH }} done"
color: ${{ env.DISCORD_COLOR }}
url: "https://${{ env.CI_GIT_BRANCH }}.blog.xataz.net"
delete_env
Comme dit précédemment, je souhaites supprimer mon environnement une fois que c’est en production. J’ai donc créer un workflow spécifique à ceci, qui sera exécuté lors de la fermeture et du merge d’une Pull Request.
name: Delete dev environment
run-name: deleteenv
on:
pull_request:
types:
- closed
jobs:
if_merged:
if: github.event.pull_request.merged == true
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get branch to Delete
run: |
echo "CI_BRANCH_TO_DELETE=$(git log --oneline | head -1 | sed -e 's/^.*from \(.*\) into \(.*\)$/\1/')" >> $GITHUB_ENV
- name: Delete environment
uses: https://github.com/appleboy/ssh-action@v0.1.9
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
script: rm -rf /opt/www/blog/${{ env.CI_BRANCH_TO_DELETE }}
Production
Et pour finir, une fois le merge effectué, cela lance mon workflow pour le déploiement en production :
name: Deploy on Prod
run-name: production
on:
push:
branches:
- main
- master
jobs:
deploy:
steps:
- name: Download artifacts
run: |
GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | cut -d"/" -f1) && \
GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d"/" -f2) && \
curl -XGET -H "Authorization: token ${{ secrets.CI_GITEA_PACKAGES_TOKEN }}" $GITHUB_SERVER_URL/api/packages/${GITHUB_REPO_OWNER}/generic/${GITHUB_REPO_NAME}/${GITHUB_SHA}/blog.tar.gz > blog.tar.gz && \
tar xzf blog.tar.gz && rm -f blog.tar.gz
- name: Set branch name
run: |
echo "CI_GIT_BRANCH=$(basename $GITHUB_REF_NAME)" >> $GITHUB_ENV
- name: Deploy blog
uses: https://github.com/appleboy/scp-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
source: "*"
target: "/opt/www/blog/${{ env.CI_GIT_BRANCH }}/"
rm: true
- name: Configure variables for notify
if: ${{ always() }}
run: |
if ${{ success() }}; then
echo "DISCORD_COLOR=0x00ff00" | tee -a $GITHUB_ENV
echo "DISCORD_STATUS=Success" | tee -a $GITHUB_ENV
elif ${{ failure() }}; then
echo "DISCORD_COLOR=0xff0000" | tee -a $GITHUB_ENV
echo "DISCORD_STATUS=Failure" | tee -a $GITHUB_ENV
else
echo "DISCORD_COLOR=0xeca714" | tee -a $GITHUB_ENV
echo "DISCORD_STATUS=Unknown" | tee -a $GITHUB_ENV
fi
- name: Notify
uses: https://github.com/sarisia/actions-status-discord@v1
if: ${{ always() }}
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: "${{ env.DISCORD_STATUS }}"
content: "Hey"
title: "deploy"
description: "Deploy production done"
color: ${{ env.DISCORD_COLOR }}
url: "https://blog.xataz.net"
Quelques limites
Gitea Actions est à ces débuts, donc tout ne fonctionne pas comme sur github, notamment avec les workflow_run
, qui malgré mes efforts, n’a pas voulu fonctionner (un simple copier/coller vers github, et ça fonctionnait). Il y a surement d’autres types d’évènements qui ne fonctionnement pas, certainement pour une future version de gitea. J’ai également eu des soucis avec les variables needs.<jobname>.result
, pour faire des dépendances poussés entre les jobs.
Conclusion
Petite découverte très sympa, et j’avoue que j’aime bien. Malgré que j’adore drone, je trouve que c’est une très bonne alternative. Avec cette nouvelle fonctionnalité, Gitea commence à jouer dans la cours des grands, et propose une bonne alternative à Gitlab ou Github.
Encore une fois, vous lisez cette article qui a été déployé par cette pipeline.