Aller au contenu
CI/CD & Automatisation medium

extends et anchors YAML GitLab CI/CD

17 min de lecture

logo gitlab

Vous répétez les mêmes lignes dans plusieurs jobs ? extends et les anchors YAML permettent de factoriser votre configuration. Définissez une fois, réutilisez partout.

À la fin de ce module, vous saurez :

  • Utiliser extends : faire hériter un job d’un template caché
  • Créer des jobs cachés : la convention . pour les templates
  • Surcharger les propriétés : personnaliser l’héritage sans tout réécrire
  • Comprendre la fusion : dictionnaires fusionnent, listes remplacent
  • Utiliser les anchors YAML : &, * et <<: pour les cas avancés
  • Choisir le bon outil : extends vs anchors vs !reference

Avant de continuer, assurez-vous de maîtriser :

Imaginez une recette de famille transmise de génération en génération. La recette de base reste la même, mais chaque génération peut y apporter sa touche personnelle : ajouter une épice, changer un ingrédient.

extends fonctionne pareil : un job “enfant” hérite de toutes les propriétés d’un job “parent”, mais peut surcharger (remplacer) certaines d’entre elles.

Héritage avec extends : le job enfant hérite des propriétés du parent et peut les surcharger

Un job peut hériter d’un autre job avec extends. Le job parent est généralement caché (préfixé par .) pour ne pas s’exécuter lui-même :

# Job caché (ne s'exécute pas)
.base_job:
image: python:3.11
before_script:
- pip install -r requirements.txt
# Job qui hérite
test:
extends: .base_job
script:
- pytest

Que se passe-t-il concrètement ?

GitLab “fusionne” le job parent et le job enfant. C’est comme si vous aviez écrit :

test:
image: python:3.11 # Hérité de .base_job
before_script: # Hérité de .base_job
- pip install -r requirements.txt
script: # Défini par test
- pytest

Le job test hérite automatiquement de :

  • image: python:3.11
  • before_script: [pip install -r requirements.txt]

Et définit son propre script.

Les jobs dont le nom commence par . sont des templates — ils ne s’exécutent jamais. Leur seul rôle est de servir de modèle pour d’autres jobs.

.template: # ❌ Ne s'exécute pas (commence par .)
image: alpine
my_job: # ✅ S'exécute (nom normal)
extends: .template
script: echo "Hello"

Pourquoi utiliser des jobs cachés ?

  1. Éviter la pollution : Sans le ., GitLab essaierait d’exécuter le template comme un vrai job
  2. Clarté : En voyant .nodejs_base, on comprend immédiatement que c’est un template
  3. Convention : Tout le monde utilise cette notation, votre code est lisible par d’autres

Un job peut hériter de plusieurs templates à la fois. C’est utile pour composer des comportements :

.base:
image: node:18
variables:
ENV: production
.with_cache:
cache:
paths:
- node_modules/
build:
extends:
- .base # Premier : fournit image + variables
- .with_cache # Second : ajoute le cache
script: npm run build

build hérite de .base ET .with_cache. Le résultat final :

build:
image: node:18 # De .base
variables:
ENV: production # De .base
cache:
paths:
- node_modules/ # De .with_cache
script: npm run build # Défini localement

L’héritage serait limité si on ne pouvait pas personnaliser. Le job enfant peut surcharger (remplacer) n’importe quelle propriété héritée du parent :

.node_base:
image: node:18
script:
- npm test
variables:
NODE_ENV: development
# Surcharge plusieurs propriétés
build_prod:
extends: .node_base
image: node:20 # 🔄 Remplace node:18 par node:20
script:
- npm run build # 🔄 Remplace npm test par npm run build
variables:
NODE_ENV: production # 🔄 Remplace development par production
# Garde presque tout, change juste le script
test:
extends: .node_base
script:
- npm run lint # 🔄 Remplace le script
- npm test

Ce qui est surchargé vs ce qui est hérité :

Propriété dans build_prodValeurSource
imagenode:20Surchargée (était node:18)
scriptnpm run buildSurchargée (était npm test)
variables.NODE_ENVproductionSurchargée (était development)

Voici le piège le plus fréquent avec extends : les tableaux (listes) sont complètement remplacés, pas fusionnés.

.base:
script:
- echo "step 1"
- echo "step 2"
job:
extends: .base
script:
- echo "only this" # ⚠️ REMPLACE complètement, ne s'ajoute pas !

Résultat : job exécute uniquement echo "only this". Les étapes “step 1” et “step 2” ont disparu.

Comment ajouter des étapes sans remplacer ?

Utilisez before_script et after_script qui s’ajoutent autour de script :

.base:
before_script:
- echo "setup" # Toujours exécuté en premier
script:
- echo "main" # Le script principal
job:
extends: .base
after_script:
- echo "cleanup" # S'ajoute à la fin, ne remplace rien

Ordre d’exécution : setupmaincleanup

Les dictionnaires fusionnent en profondeur :

.base:
variables:
VAR1: "value1"
VAR2: "value2"
job:
extends: .base
variables:
VAR2: "overridden" # Surcharge VAR2
VAR3: "value3" # Ajoute VAR3
# Résultat : VAR1=value1, VAR2=overridden, VAR3=value3

Les anchors sont une fonctionnalité native de YAML — elles fonctionnent dans n’importe quel fichier YAML, pas seulement GitLab CI.

Imaginez que vous surlignez un bloc de texte et lui donnez un nom (“bloc A”). Ensuite, partout où vous voulez ce même texte, vous tapez juste “coller bloc A”. C’est exactement ce que font les anchors.

SymboleCe qu’il faitAnalogie
&nameDéfinit une ancre (comme “copier”)“Je nomme ce bloc ‘config‘“
*nameRéférence l’ancre (comme “coller”)“Colle le bloc ‘config’ ici”
<<:Fusionne le contenu dans un dictionnaire”Fusionne ce bloc dans mes propriétés”

Voici comment utiliser les anchors pour éviter la répétition :

# 1️⃣ Définir une ancre avec &
.job_template: &job_config # &job_config = "je nomme ce bloc"
image: ruby:3.0
services:
- postgres:14
# 2️⃣ Utiliser l'ancre avec << et *
test1:
<<: *job_config # "Fusionne job_config ici"
script: ruby test1.rb
test2:
<<: *job_config # Même configuration, autre script
script: ruby test2.rb

Équivalent sans anchors (ce que vous auriez dû écrire sinon) :

test1:
image: ruby:3.0
services:
- postgres:14
script: ruby test1.rb
test2:
image: ruby:3.0
services:
- postgres:14
script: ruby test2.rb

Les anchors vous ont économisé 4 lignes de duplication.

variables:
DATABASE_URL: &db_url "postgres://localhost:5432/app"
job1:
variables:
DB: *db_url
job2:
variables:
DATABASE: *db_url
.base_config: &base
image: node:18
.cache_config: &cache
cache:
paths:
- node_modules/
build:
<<: [*base, *cache] # Fusionne les deux
script: npm run build
variables: &global_vars
VAR1: "value1"
VAR2: "value2"
job:
variables:
<<: *global_vars
VAR3: "value3" # Ajoute une variable
AspectextendsAnchors!reference
Lisibilité✅ Plus clair❌ Syntaxe cryptique✅ Explicite
Merge mappings✅ Oui⚠️ Shallow❌ Non (insertion)
Réutiliser des listes❌ Écrase❌ Écrase✅ Oui
Multi-fichiers✅ Avec include❌ Même fichier✅ Avec include
Spécifique GitLabOuiNon (YAML)Oui
Recommandé pourJobs completsValeurs simplesFragments (rules, script)

GitLab propose !reference pour réutiliser des fragments de configuration (listes, mappings) sans les limitations des anchors :

.security_rules:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
.test_script:
script:
- npm test
- npm run coverage
build:
script:
- npm run build
rules:
- !reference [.security_rules, rules] # Réutilise les rules
test:
script:
- !reference [.test_script, script] # Réutilise le script
- npm run e2e # Ajoute une commande
rules:
- !reference [.security_rules, rules]
variables:
NODE_VERSION: "18" # Valeur par défaut, surchargeable
.node_base:
image: "node:${NODE_VERSION}"
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
before_script:
- npm ci
build:
extends: .node_base
script: npm run build
artifacts:
paths:
- dist/
test:
extends: .node_base
script: npm test
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
lint:
extends: .node_base
script: npm run lint
allow_failure: true
.deploy_base:
image: alpine:3.18
before_script:
- apk add --no-cache curl
script:
- ./deploy.sh "$ENVIRONMENT"
deploy_staging:
extends: .deploy_base
variables:
ENVIRONMENT: staging
environment:
name: staging
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
deploy_production:
extends: .deploy_base
variables:
ENVIRONMENT: production
environment:
name: production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual

Symptôme : Votre job n’apparaît pas dans le pipeline.

Cause : Son nom commence par . — c’est un job caché (template).

Solution : Retirez le . du nom, ou créez un job qui extends ce template.

# ❌ Ne s'exécutera jamais
.build:
script: npm run build
# ✅ S'exécutera
build:
script: npm run build

Symptôme : Les commandes du parent ont disparu.

Cause : Les tableaux (listes) ne fusionnent pas, ils sont remplacés.

Solution : Utilisez before_script / after_script, ou redéfinissez tout le script.

Symptôme : GitLab refuse de créer le pipeline.

Cause : Le job parent n’existe pas (faute de frappe, fichier non inclus).

Solution : Vérifiez :

  • Le nom exact du job parent (avec le . si c’est un template)
  • Que le fichier contenant le parent est bien inclus (include:)

Symptôme : Erreur YAML à la création du pipeline.

Cause : Vous référencez une ancre (*name) avant de l’avoir définie (&name).

Solution : Définissez toujours l’ancre avant de l’utiliser dans le fichier.

# ❌ Erreur : ancre utilisée avant d'être définie
job:
<<: *config # Erreur ! config n'existe pas encore
.template: &config
image: alpine
# ✅ Correct : ancre définie d'abord
.template: &config
image: alpine
job:
<<: *config # OK, config existe
  1. extends permet l’héritage de jobs — préférez-le aux anchors
  2. Jobs cachés (.name) ne s’exécutent pas, servent de templates
  3. Mappings fusionnent, scalaires et tableaux sont écrasés
  4. Héritage multiple : le dernier gagne en cas de conflit
  5. Anchors (&, *, <<:) pour les cas avancés dans le même fichier
  6. !reference pour réutiliser des fragments (rules, script) entre jobs
  7. Combinez avec include pour partager entre projets

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
5 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.