Aller au contenu

Pages⚓︎

J’utilise de plus en plus Gitlab Pages (et ses dérivés, par exemple Framagit). C’est moins intuitif que ce que j’ai pu expérimenter avec Microsoft Github Pages, aussi voici quelques configurations de base et astuces collectées/expérimentées ici et là car je me perds régulièrement dans la documentation officielle.

Servir un dossier qui contient du HTML⚓︎

🐣 2022-11

Il faut commencer par créer un dépôt puis par mettre tous les fichiers HTML (et autres !) dans un dossier public/. Ensuite, on ajoute le fichier .gitlab-ci.yml à la racine du dépôt.

Warning

Il faut bien un point . au début du nom du fichier !

.gitlab-ci.yml
1
2
3
4
5
6
7
8
pages:
  stage: deploy
  script: echo "Youpi"
  artifacts:
    paths:
      - public  # (1)!
  rules:
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH  # (2)!
  1. Vous pouvez changer cette ligne si vous ne mettez pas tous vos fichiers dans le dossier public/.
  2. Ici on restreint le déploiement à la branche principale (en général master ou main).

Une fois tout cela commité/pushé sur le dépôt, vous devriez pouvoir accéder à :

https://NOM-DU-COMPTE.gitlab.io/NOM-DU-DEPOT/

Si vous ne voyez rien apparaître au bout de quelques secondes/minutes, vous pouvez aller voir le détail des erreurs directement au niveau du dépôt dans CI/CD → Pipelines.

Améliorer les performances avec gzip⚓︎

🐣 2022-11

Je découvre grâce à Magentix que les fichiers statiques ne sont pas servis par défaut en étant gzipés.

Je dois avouer que je n’ai pas trop regardé les performances sur ces environnements car en général je m’en sers uniquement pour des prototypes.

Il est possible de convertir les fichiers statiques lors du déploiement avec l’ajout du script suivant :

.gitlab-ci.yml
1
2
3
4
pages:
  # Tout le reste.
  script:
    - find public -type f -regex '.*\.\(html\|js\|css\)$' -exec gzip -f -k {} \;

Info

Notez que vous pouvez adapter la liste des extensions concernées dans la commande (ici le .html, .js et .css).

Générer les pages avec Python⚓︎

🐣 2022-11

Bien souvent on ne veut pas commiter directement les pages en HTML mais plutôt se servir de l’intégration continue pour les générer.

La configuration ci-dessous utilise un générateur de site statique en Python pour générer les pages HTML à partir de fichiers markdown qui sont eux ajoutés au dépôt. On peut imaginer pas mal d’autres usages, ce n’est qu’un exemple.

.gitlab-ci.yml
# Inspired from
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/↩
# lib/gitlab/ci/templates/Python.gitlab-ci.yml
image: python:latest

# Change pip's cache directory to be inside the project directory since we can
# only cache local items.
variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
cache:
  paths:
    - .cache/pip

# The locale part is important for dates rendering (Jinja2).
before_script:
  - apt-get update --quiet --yes
  - apt-get install --yes apt-utils
  - apt-get install --yes locales locales-all

pages:
  stage: deploy
  script:
    - python -m pip install \
      --disable-pip-version-check --quiet \
      --requirement requirements.txt  # (1)!
    - make build  # (2)!
  artifacts:
    paths:
      - public
  rules:
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH  # (3)!
  1. On installe les dépendances avec pip (ou tout autre système)
  2. On lance la commande de build qui est gérée dans un Makefile et qui produit les fichiers HTML dans le dossier public/ (ou modifier la ligne en-dessous)
  3. On ne déploie que la branche principale (main en général de nos jours)

On utilise un cache pour pip afin de rendre les futurs déploiements plus rapides.

Déployer une version du site par branche⚓︎

🐣 2023-02

C’est la suite de la configuration ci-dessus, parfois on veut pouvoir déployer une branche pour des tests tout en gardant la branche principale/de production à la racine de nos Gitlab Pages.

Ce script de déploiement va déployer la nouvelle branche poussée (et les futurs commits) sur :

https://<username>.gitlab.io/<repository>/<branch-name>/

(En plus de conserver https://<username>.gitlab.io/<repository>/ pour la branche principale.)

Warning

Chaque déploiement d’une nouvelle branche va écraser une précédente branche déployée (tout en redéployant la branche principale). Chaque commit sur votre branche principale va aussi supprimer le déploiement de la branche précédemment mise à jour. Ce n’est peut-être pas ce que vous souhaitez !

Il est possible de faire des choses plus avancées avec des patterns de noms de branches pertinentes par exemple à mettre dans la clé rules. À vous de voir ce qui est compatible avec votre fonctionnement.

.gitlab-ci.yml
# Idem au-dessus
# image: python:latest etc

pages:
  stage: deploy
  script:
    # Install and deploy the current branch to /branch-name/ path.
    - python -m pip install \
      --disable-pip-version-check --quiet \
      --requirement requirements.txt
    - dir="$CI_COMMIT_REF_SLUG/"
    - if [ "$CI_COMMIT_REF_SLUG" == "$CI_DEFAULT_BRANCH" ]; then dir=""; fi;
    - baseurl="https://<username>.gitlab.io/<repository>/$dir"
    - echo "BASE_URL='$baseurl'" > .env  # (1)!
    - dir="public/$dir"
    - make build target=$dir  # (2)!
    - echo "Branch '$CI_COMMIT_REF_SLUG' deployed to $baseurl"

    # Install and deploy the default branch at the root of the path.
    - git fetch
    - git checkout $CI_DEFAULT_BRANCH  # (3)!
    - git reset --hard origin/$CI_DEFAULT_BRANCH
    - python -m pip install \
      --disable-pip-version-check --quiet \
      --requirement requirements.txt
    - baseurl="https://<username>.gitlab.io/<repository>/"
    - echo "BASE_URL='$baseurl'" > .env
    # If it is the main branch, it is already deployed.
    - if [ "$CI_COMMIT_REF_SLUG" != "$CI_DEFAULT_BRANCH" ]; then make build; fi;
    - echo "Branch '$CI_DEFAULT_BRANCH' deployed to $baseurl"
  artifacts:
    paths:
      - public
  1. Un exemple de personnalisation de l’environnement (couplé à dotenv c’est plus propre), c’est optionnel mais c’est pour donner une idée
  2. La commande de construction doit accepter un paramètre lui indiquant où générer le site
  3. On revient maintenant sur la branche principale pour déployer à la racine

PS : mettre en cache les installations via apt-get est non trivial, j’ai passé un bon moment avant d’abandonner… la recommandation officielle étant de faire sa propre image Docker.

Mise à jour (octobre 2024)

C’est dorénavant possible nativement avec la version payante de Gitlab, voir la documentation à ce sujet.

Générer des fichiers .webp à partir de fichiers .jpg⚓︎

🐣 2023-02

Ceci n’est pas vraiment une recette mais une inspiration, ça dépend ensuite vraiment de ce que vous voulez faire des images générées ! C’est à adapter aussi si vous avez du png, etc.

.gitlab-ci.yml
# Idem au-dessus
# image: python:latest etc

before_script:
  - apt-get install --yes cpio webp  # (1)!

pages:
  stage: deploy
  script:
    - python -m pip install --disable-pip-version-check --quiet webp-converter  # (2)!
    - find static -type f -iname "*.jpg" | \
      cpio -p --make-directories --preserve-modification-time webp/  # (3)!
    - webpc --r --quality-ratio 50 webp  # (4)!
  artifacts:
    paths:
      - public
  1. On installe les dépendances système
  2. On installe l’utilitaire Python webp-converter
  3. On crée une arborescence dans webp/ avec les fichiers jpg depuis le dossier static/
  4. On convertit sur place cette nouvelle arborescence pour avoir des fichiers webp

Il est possible de modifier le ratio de qualité en fonction de vos besoins, on peut descendre assez bas selon les cas mais ça finit par ternir les images.

C’est à vous ensuite de mettre cette arborescence là où c’est pertinent pour votre produit.

Pro-tip!

Toujours mettre des options en version étendue dans les scripts (d’intégration continue).

Bloquer le déploiement (ou les tests) avec un mot-clé⚓︎

🐣 2023-04

Mise à jour (juillet 2024)

Depuis, j’ai découvert qu’en mettant [skip ci] dans un message de commit, Gitlab se chargeait de ne pas lancer les jobs de la CI. La méthode ci-dessous reste valable si vous voulez du plus granulaire.

Vous voulez parfois ne pas exécuter l’une des commandes définies dans votre intégration continue (typo qui ne nécessite pas un redéploiement, fichier de configuration qui n’impacte pas les tests, etc).

.gitlab-ci.yml
1
2
3
4
5
6
7
8
9
pages:
  stage: deploy
  script:
    - make build
  artifacts:
    paths:
      - public
  rules:
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH && $CI_COMMIT_MESSAGE !~ /nopages/  # (1)!
  1. On ajoute une règle pour que le lancement ne s’effectue que lorsque le message (titre ou description) de commit ne contient pas (!~) le mot-clé « nopages ».

Lorsqu’il s’agit d’une tâche qui ne comporte pas de rules, c’est encore plus facile/explicite avec le bout de configuration suivant :

.gitlab-ci.yml
1
2
3
4
5
6
test:
  script:
    - make test
  except:
    variables:
      - $CI_COMMIT_MESSAGE =~ /notest/  # (1)!
  1. La combinaison du except et du =~ permet de ne pas lancer la tâche lorsque le message (titre ou description) de commit contient le mot-clé « notest ».

Préservation des ressources

Vous avez beau squatter les ressources matérielles des autres, ça n’en reste pas moins des CPU qui tournent de l’autre côté de la planète et qui réchauffent de l’eau (au mieux). Avoir un moyen de passer outre cette consommation continue est quand même pas mal.

Commiter des fichiers générés par la CI⚓︎

🐣 2024-07

J’ai passé un temps non négligeable pour réussir à faire tourner un script qui génère des fichiers que je voulais ensuite ajouter au dépôt. Voici le code complet auquel je suis arrivé, c’est plus qu’il n’en faut mais ça peut donner d’autres idées :

.gitlab-ci.yml
# Inspired from
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
image: python:latest

generate-files:
  stage: deploy
  before_script:
    # The locale part is important for date/numbers rendering.
    - apt-get update --quiet --yes
    - apt-get install --yes apt-utils
    - apt-get install --yes locales locales-all
    # If you want to know all available Gitlab CI variables, uncomment below.
    # - export
    # If you want to know which locales are currently available, uncomment below.
    # - locale -a

    # Setup gitlab git configuration to commit generated files.
    # https://docs.gitlab.com/ee/ci/variables/
    # `CI_TOKEN_NAME_VALUE` is of the form `token-name:glpat-loooong-id
    # and must be defined as a CI/CD variable, with a free account you are
    # limited to personal (vs. project/team) token and they are only valid
    # for one year max.
    - git remote set-url origin https://${CI_TOKEN_NAME_VALUE}@gitlab.com/${CI_PROJECT_PATH}.git
    - git remote -v
    - git remote show origin
    - git config --global user.email ${GITLAB_USER_EMAIL}
    - git config --global user.name ${GITLAB_USER_NAME}
  script:
    # Setup the Python venv and install dependencies.
    - python3 -m venv venv
    - source venv/bin/activate
    - make install

    # By default, you are in a detached HEAD branch.
    - git switch ${CI_DEFAULT_BRANCH}
    - git branch

    # Run the script.
    - make build

    # Commit new and modified files.
    - git status --porcelain
    # -z will test for empty string and early exit if no file is added/modified.
    - if [[ -z "$(git status --porcelain=v1 2>/dev/null)" ]]; then exit 0; fi
    - git add -A .
    - git commit --all -m '[skip ci] Update files from CI'
    - git push --set-upstream origin ${CI_DEFAULT_BRANCH}

On essaye de réutiliser au maximum les variables d’environnement qui sont déjà accessibles dans la CI de Gitlab. Il faut néanmoins créer un token personnel (à moins d’avoir un compte payant) qui ne peut avoir une durée de vie de plus d’un an puis aller ajouter une variable d’environnement que j’ai intitulée CI_TOKEN_NAME_VALUE et qui contient nom-du-token:glpat-suivi-par-un-id. Je recommande de mettre cette variable en Masked et à vous de voir pour l’option Protected selon votre gestion des branches.

On s’assure de sortir du script s’il n’y a pas de nouveaux fichiers générés et on ajoute le mot-clé [skip ci] dans le message de commit sinon on fait tourner la CI en boucle vu que le job est déclenché au commit !

Warning

Si vous voulez uniquement mettre à jour des fichiers déjà versionnés mais ne pas ajouter de nouveaux fichiers générés (et/ou artefacts de CI), il faudra adapter les commandes git (option --untracked-files=no). Il est aussi possible de jouer avec le .gitignore pour ça.

Utiliser une clé SSH depuis la CI Gitlab⚓︎

🐣 2024-10

Préalable

Il faut avoir généré une clé SSH sur sa machine (sans mot de passe…) et avoir mis la clé publique sur la machine vers laquelle on veut communiquer. Puis saisir deux variables d’environnement pour votre CI Gitlab :

  1. SSH_PRIVATE_KEY de type File (très important !) qui contient le contenu de la clé privée avec une ligne vide à la fin (très important aussi !)
  2. SSH_KNOWN_HOSTS de type File qui contient le résultat de ssh-keyscan -t ed25519 host-ou-nom-de-domaine vers lequel vous voulez communiquer (toujours avec une ligne vide à la fin)
.gitlab-ci.yml
image: python:latest

generate-files:
  stage: deploy
  before_script:
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    - chmod 400 "$SSH_PRIVATE_KEY"
    - ssh-add "$SSH_PRIVATE_KEY"
    - touch ~/.ssh/known_hosts
    - cat "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    # Une commande qui utilise rsync par exemple, à vous de voir.
    - apt-get update -y && apt-get install rsync -y
    - make deploy-dev

Le plus difficile est de ne pas se louper sur les droits… ni sur les variables de la CI. Rassurez-moi, vous aussi vous avez besoin d’au moins 10 pushs pour mettre une CI sur pieds ?!

Warning

Il y a des erreurs dans la doc officielle et dans la plupart des tutoriels sur lesquels je me suis cassé les dents…