Aller au contenu

Créer un tag de release

Vous êtes mainteneur·se de Kirexo et vous voulez déclencher une release : créer un tag Git qui enchaîne automatiquement le build des images Docker, le déploiement de la documentation sur GitLab Pages et la création de la release GitLab avec changelog.

Le workflow recommandé met à jour le CHANGELOG.md dans le même mouvement, via le skill /creer-tag. Cette recette présente d'abord ce workflow, puis les trois façons de déclencher la pose du tag à plus bas niveau (Méthodes A, B, C) — utiles si vous voulez taguer sans toucher au changelog.

Workflow recommandé : le skill /creer-tag

Le skill /creer-tag est l'enrobage de référence autour de la Méthode C. Il garantit que le commit taggé contient déjà sa propre section de CHANGELOG.md : le nom du tag est calculé d'avance et imposé au job CI (TAG_NAME), au lieu d'être calculé côté serveur au moment de la pose. La section de changelog peut donc être écrite avant la pose du tag.

Le découpage est volontairement en deux scripts — une partie locale sans effet, une partie qui pousse :

prepare.sh  →  branche release-<tag> + CHANGELOG.md généré   (LOCAL, sans effet remote)
release.sh  →  commit + MR auto-merge sur main + tag:create   (REMOTE, lancé par vous)

1. Préparer (local)

Depuis main à jour et arbre propre :

bash .claude/skills/creer-tag/bin/prepare.sh

Le script calcule TAG_NAME=$(date -u +%Y%m%d%H%M), vérifie qu'au moins un commit existe depuis le dernier tag de release, crée la branche release-<TAG_NAME>, puis lance castor changelog:prepare --name <TAG_NAME> — qui insère la section générée en tête de CHANGELOG.md. La section est affichée ; la dernière ligne de sortie est TAG_NAME=<valeur>. Notez ce TAG_NAME.

La génération du changelog est déterministe : les commits depuis le dernier tag sont classés par rubrique selon leur préfixe Conventional Commits (cf. Convention du CHANGELOG.md). Aucune rédaction par un LLM.

2. Relire (optionnel)

La section est déjà affichée par prepare.sh. Pour ajuster un libellé, éditez CHANGELOG.md à la main avant l'étape suivante — la génération est volontairement brute (sujets de commits nettoyés, classés).

3. Ship (remote)

bash .claude/skills/creer-tag/bin/release.sh <TAG_NAME>

Le script enchaîne, dans l'ordre : castor audit (filet CVE) → commit du CHANGELOG.md → push → glab mr create + auto-merge sur main → attente du merge → attente d'une pipeline verte sur maincastor tag:create --name <TAG_NAME>. À la fin, la pipeline $CI_COMMIT_TAG (build images, doc, release GitLab) se déclenche automatiquement, comme pour les autres méthodes.

Pourquoi attendre le vert avant de taguer

Le job tag:create exige une pipeline success sur le dernier commit de main (cf. garde-fou). release.sh attend donc le vert post-merge avant d'appeler tag:create --name — sinon le tag serait refusé.

Le TAG_NAME étant imposé (validé ^[0-9]{12}$ côté Castor et côté job CI), la section de CHANGELOG.md écrite à l'étape 1 porte exactement le nom du tag qui sera posé : pas de décalage entre le contenu commité et le tag.

Le détail bas niveau de la pose du tag (garde-fou, chaîne de jobs déclenchée) est ci-dessous.

Prérequis

  • Vous avez les droits Maintainer ou Owner sur le projet GitLab.
  • La variable CI/CD GITLAB_TOKEN est configurée (cf. Variables CI/CD GitLab) — un Personal Access Token avec le scope write_repository (les project access tokens sont réservés à GitLab Premium/Ultimate).
  • Les images kirexo-base, kirexo-dev et kirexo-prod ont déjà été buildées au moins une fois — sinon les jobs docker:build-* sur tag échoueraient car il n'y a rien à mettre en cache (et la pipeline qualité, qui dépend de ces images, ne tournerait pas non plus). L'image kirexo-docs ne nécessite pas ce bootstrap : son job docker:build-docs est needs: [] et tourne automatiquement avant pages sur tag.

Pourquoi passer par une pipeline et pas par git tag en local

git push --tags fonctionnerait, mais ça impose d'avoir une clé SSH ou un PAT configurés sur la machine qui pousse, et ça ne marche pas depuis un environnement où le repo n'est pas cloné en écriture (ex. depuis Claude Code en mode bypassPermissions qui n'a pas la clé de la personne). La pipeline tag:create crée le tag côté serveur via l'API GitLab — l'auteur·rice de la release n'a besoin que de son accès UI ou de glab.

Méthode A — Depuis l'UI GitLab

  1. Aller sur Build → Pipelines → Run pipeline (bouton en haut à droite).
  2. Choisir la branche main.
  3. Ajouter une variable :
  4. Key : CREATE_TAG
  5. Value : true
  6. Type : Variable (pas File).
  7. Cliquer sur Run pipeline.

La pipeline créée ne contient que le job tag:create (stage tag). Tous les autres jobs sont filtrés par leurs rules: — la pipeline CREATE_TAG=true est volontairement isolée pour ne pas dédoubler la pipeline qualité.

Garde-fou : une pipeline verte est exigée sur le dernier commit de main

Avant de créer le tag, tag:create vérifie qu'au moins une pipeline success existe pour le dernier commit de main ($CI_COMMIT_SHA) — typiquement la pipeline de validation jouée au merge. Si aucune pipeline verte n'est trouvée, le job échoue et le tag n'est pas créé. Le but : ne jamais figer une release sur un commit dont la CI est rouge ou encore en cours.

Pour outrepasser ce garde-fou dans un cas exceptionnel, ajouter une seconde variable FORCE_TAG à true (en plus de CREATE_TAG) :

Key Value
CREATE_TAG true
FORCE_TAG true

Avec FORCE_TAG=true, le statut de pipeline n'est plus vérifié : le tag est créé inconditionnellement. À réserver aux situations où l'on sait que le commit est sain mais que la pipeline n'a pas (re)tourné.

Méthode B — Depuis le CLI avec glab

glab ci run --branch main --variables "CREATE_TAG:true"

Le retour de glab affiche l'ID et l'URL de la pipeline créée — suivez-la via glab ci get -p <id> ou directement dans l'UI.

Pour outrepasser le garde-fou de pipeline verte (cf. Méthode A), ajouter --variables "FORCE_TAG:true" :

glab ci run --branch main --variables "CREATE_TAG:true" --variables "FORCE_TAG:true"

Méthode C — Avec Castor

La cible castor tag:create est un raccourci autour de l'appel glab de la Méthode B — c'est la voie la plus courte pour poser un tag à la main. Le workflow recommandé (skill /creer-tag) s'appuie sur cette cible pour la pose finale du tag, après avoir mis à jour le CHANGELOG.md.

castor tag:create

Pour outrepasser le garde-fou de pipeline verte (cf. Méthode A), ajouter --force — équivalent de FORCE_TAG=true. La cible affiche alors un warning avant de déclencher la pipeline :

castor tag:create --force

Pour imposer le nom du tag au lieu de le laisser calculer côté serveur, passer --name (12 chiffres) — c'est ce que fait le skill /creer-tag une fois le CHANGELOG.md mergé :

castor tag:create --name 202606220820

La valeur est transmise au job via la variable CI TAG_NAME (cf. Ce que fait tag:create). Un nom mal formé lève une erreur côté Castor avant même de déclencher la pipeline.

Un simple raccourci, pas une mécanique différente

castor tag:create exécute exactement glab ci run --branch main --variables "CREATE_TAG:true" (plus --variables "TAG_NAME:<nom>" avec --name, et --variables "FORCE_TAG:true" avec --force). Le tag est créé côté serveur GitLab par le job tag:create, à l'identique des méthodes A et B — voir Ce que fait tag:create pour le détail. Prérequis : un glab authentifié (glab auth status), puisque la cible s'appuie sur lui.

Ce que fait tag:create

Le job tourne sur alpine:3 avec GIT_STRATEGY: none (pas besoin de cloner le code — l'API agit côté serveur GitLab) et exécute :

  1. Garde-fou (sauf si FORCE_TAG=true) : interroge GET /projects/:id/pipelines?sha=$CI_COMMIT_SHA&status=success et refuse de taguer (exit 1) si aucune pipeline success n'existe pour le dernier commit de main. Le contrôle est fail-safe : si l'appel API échoue (token invalide, réseau), le compteur retombe à 0 et le job bloque plutôt que de risquer une release non validée.
  2. Détermine le nom du tag :

    • si la variable TAG_NAME est fournie au déclenchement (cas du skill /creer-tag / castor tag:create --name), elle est utilisée telle quelle après validation du format YYYYMMDDHHMM (12 chiffres) — un format invalide fait échouer le job ;
    • sinon, le nom est calculé côté serveur : TAG_NAME=$(date -u +%Y%m%d%H%M) — format YYYYMMDDHHMM en UTC.

    Imposer TAG_NAME permet au commit taggé de contenir déjà sa section de CHANGELOG.md, écrite sous ce même nom avant la pose du tag (cf. workflow recommandé). 3. Vérifie l'absence d'un tag homonyme (un GET .../repository/tags/${TAG_NAME} ; un 200 arrête le job pour éviter un 409 cryptique au POST). 4. Appelle POST /projects/:id/repository/tags avec ${GITLAB_TOKEN} :

    curl --fail \
      --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
      --data "{\"tag_name\": \"${TAG_NAME}\", \"ref\": \"${CI_DEFAULT_BRANCH}\"}" \
      "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/repository/tags"
    
  3. Affiche la réponse JSON via jq '{name, target, message}'.

Le --fail de curl fait échouer le job si l'API renvoie un statut HTTP ≥ 400 — typiquement 403 Forbidden si GITLAB_TOKEN n'a pas le scope write_repository, ou 409 Conflict si un tag du même nom existe déjà (très peu probable avec le format YYYYMMDDHHMM).

Ce qui se déclenche ensuite

Dès que GitLab détecte le tag créé, il démarre automatiquement une nouvelle pipeline $CI_COMMIT_TAG. Cette pipeline ne rejoue ni les vérifications qualité ni les tests : ceux-ci ont déjà tourné sur le même SHA dans la pipeline main que tag:create a exigée verte (cf. Optimisation pipeline de tag). Seuls les jobs qui produisent un livrable ou dont le résultat dépend de l'instant T du tag s'exécutent :

Stage Job Effet
quality quality:composer-audit Filet de sécurité rejoué sur le SHA du tag — capture une CVE Composer éventuellement publiée entre le merge et le tag. Si rouge, la pipeline du tag échoue et les jobs docker:* / release:* ne tournent pas.
docker docker:build-base, docker:build-dev, docker:build-prod Build + push automatique. kirexo-base et kirexo-dev reçoivent :php8.5 + :latest ; kirexo-prod reçoit en plus le tag de version immuable :$CI_COMMIT_TAG (cf. Déployer Kirexo en production).
docker docker:build-docs, docker:build-supply-chain Build + push des images d'outillage CI consommées au stage suivant (mkdocs pour pages, cosign + syft pour docker:sign-prod et release:sbom).
docker docker:sign-prod Signature keyless cosign des trois références poussées par docker:build-prod (cf. Supply chain et signatures).
release pages Build de la documentation mkdocs en mode strict (via l'image kirexo-docs) et déploiement sur GitLab Pages.
release release:sbom Génération du SBOM SPDX + CycloneDX de kirexo-prod:$CI_COMMIT_TAG.
release release:create Création de la release GitLab avec changelog des commits depuis le tag précédent + attachement des SBOM en assets.
maintenance container_scanning + security:container-issue Scan CVE de kirexo-prod:$CI_COMMIT_TAG fraîchement poussée — signal au moment de la release, pas H+24. Ouvre une issue uniquement si au moins une CVE Critical ou High est remontée (cf. Jobs container_scanning et security:container-issue).

Le tout sans intervention manuelle supplémentaire. docker:build-docs étant dans le stage docker (avant release), l'image de build de la doc est toujours fraîche au moment où pages la consomme — aucun bootstrap manuel n'est nécessaire pour la doc (contrairement à kirexo-base / kirexo-dev, cf. prérequis).

Pourquoi la qualité ne rejoue pas

tag:create refuse de poser le tag si aucune pipeline success n'existe pour le commit ciblé. Le code du tag est donc, par construction, strictement le même SHA qu'une pipeline qualité verte. Rejouer cs-fix, phpstan, tests unitaires ou E2E donnerait exactement le même résultat — pure duplication de minutes runner. Seul quality:composer-audit est conservé sur tag, parce que la base de vulnérabilités évolue indépendamment du code et qu'une CVE peut être publiée entre le merge et le tag.

Vérifier qu'une release est partie

  1. La pipeline CREATE_TAG=true doit être verte. Si le job tag:create échoue avec 403, vérifier le scope write_repository du token (cf. Variables CI/CD GitLab).
  2. La pipeline $CI_COMMIT_TAG apparaît dans la liste des pipelines avec le nom du tag (202605231742) — cliquer dessus pour suivre les jobs Docker et release.
  3. La release apparaît dans Deploy → Releases avec le changelog généré.
  4. La doc est mise à jour sur https://doc.kirexo.app/ (cf. Configurer le domaine GitLab Pages).
  5. Les images Docker sont visibles dans Deploy → Container Registry : kirexo-base et kirexo-dev avec les tags :php8.5 et :latest ; kirexo-prod avec :php8.5, :latest et le tag de version :202605231742 (le nom du tag Git) ; kirexo-docs avec le seul tag :latest.

Cas d'erreur

Le job tag:create échoue avec 401 Unauthorized

Le GITLAB_TOKEN est absent ou expiré. Régénérer le token (Settings → Access Tokens), mettre à jour la variable CI/CD (Settings → CI/CD → Variables) et relancer le job.

Le job tag:create échoue avec 403 Forbidden

Le token n'a pas le scope write_repository, ou son rôle est inférieur à Maintainer. Régénérer un token avec les bons droits.

Le job tag:create échoue sans appeler l'API des tags (garde-fou)

Le job s'arrête avant la création du tag parce qu'aucune pipeline success n'existe pour le dernier commit de main. Causes typiques : la pipeline de validation est encore en cours, a échoué, ou n'a jamais tourné sur ce commit. Attendre qu'une pipeline passe au vert sur main, puis relancer CREATE_TAG=true. En cas exceptionnel (commit que l'on sait sain mais sans pipeline rejouée), outrepasser avec FORCE_TAG=true (cf. Méthode A / Méthode B) ou castor tag:create --force (cf. Méthode C).

Pipeline $CI_COMMIT_TAG jamais déclenchée

Vérifier que le tag a bien été créé : glab repo view --web puis onglet Tags, ou directement via l'API. Si le tag existe mais aucune pipeline n'est partie, vérifier le workflow:rules dans .gitlab-ci.yml — la règle $CI_COMMIT_TAG doit y figurer.

Le job pages échoue en mode strict

mkdocs détecte un lien cassé, un snippet manquant ou un avertissement de plugin. Reproduire localement avec castor docs:build (qui passe les mêmes options) pour identifier la cause exacte avant de retagger.

Voir aussi