Aller au contenu
CI/CD & Automatisation medium

Lab 13 — Éliminer le copier-coller YAML

10 min de lecture

logo gitlab

Votre pipeline est devenu plus rapide, mais il est aussi devenu plus long. Quand le même bloc cache ou before_script est copié dans 3 ou 4 jobs, la moindre modification devient risquée. Dans ce lab, vous gardez le même résultat fonctionnel, mais vous réduisez la duplication pour rendre votre YAML plus maintenable.

  • Détecter les duplications qui doivent être factorisées dans un pipeline GitLab
  • Construire un job caché réutilisable avec extends:
  • Utiliser correctement les anchors YAML (&, *, <<:)
  • Expliquer la différence entre extends et anchor YAML

Ce type de refactoring intervient quand un pipeline commence à vivre dans la durée. Un ajout simple, comme une option pip ou une nouvelle clé de cache, doit alors être répliqué partout. C'est à ce moment que les incohérences apparaissent : un job est à jour, un autre non.

Ce lab vous aide dans des situations réelles :

  • votre .gitlab-ci.yml dépasse la centaine de lignes ;
  • plusieurs jobs Python répètent les mêmes sections ;
  • l'équipe hésite à modifier le pipeline par peur de casser un job secondaire.
  1. Basculez sur la branche du lab

    Fenêtre de terminal
    cd pipeline-craft
    git checkout starter/lab-13
  2. Ouvrez .gitlab-ci.yml

    Vous devez y voir des blocs dupliqués, notamment sur les jobs Python.

  3. Lancez un pipeline de référence

    Fenêtre de terminal
    git push origin starter/lab-13

    Conservez ce run comme baseline fonctionnelle avant refactoring.

Le pipeline fonctionne, mais il a un problème de maintenabilité : mêmes clés répétées, même before_script, même stratégie de cache, mêmes variables Docker.

Sur le court terme, cela passe. Sur le moyen terme, chaque correction coûte plus de temps et augmente le risque d'oubli.

  1. Ajoutez un job caché (préfixe .)

    Indice : ce job doit centraliser la partie Python partagée entre plusieurs jobs.

  2. Déplacez les éléments communs Python

    Selon votre starter, vous pouvez aussi y mettre un before_script commun.

  3. Appliquez extends sur ruff-lint et pytest

    Indice : gardez les différences métier (script, installation spécifique) dans chaque job final.

👉 Vérifier votre solution (Étape 1)

Le but est de déplacer les éléments communs Python dans un job caché, puis de les réutiliser dans ruff-lint et pytest.

.python-base:
image: python:3.12-slim
cache:
key:
files:
- requirements-dev.txt
paths:
- .pip-cache/
ruff-lint:
extends: .python-base
stage: lint
cache:
policy: pull
before_script:
- pip install ruff
pytest:
extends: .python-base
stage: test
needs: []
before_script:
- pip install -r requirements-dev.txt

ruff-lint conserve policy: pull pour restaurer le cache sans tenter de le republier.

Étape 2 — Introduire une anchor pour Docker-in-Docker

Section intitulée « Étape 2 — Introduire une anchor pour Docker-in-Docker »
  1. Déclarez une anchor de variables Docker

    Indice : l'anchor doit vivre en haut du fichier pour rester visible et réutilisable.

  2. Réutilisez-la dans docker-build

    Indice : utilisez la fusion YAML (<<:) pour injecter le bloc ancré.

    Cette approche est utile quand plusieurs jobs Docker partagent les mêmes variables.

👉 Vérifier votre solution (Étape 2)

Déclarez une anchor YAML réutilisable, puis injectez-la dans docker-build.

.docker-vars: &docker_vars
DOCKER_TLS_CERTDIR: "/certs"
docker-build:
stage: build
image: docker:27
services:
- docker:27-dind
variables:
<<: *docker_vars

Cette factorisation est utile si plusieurs jobs Docker partagent les mêmes variables d'environnement.

Étape 3 — Utiliser default pour le commun global

Section intitulée « Étape 3 — Utiliser default pour le commun global »
  1. Si pertinent, ajoutez un bloc default:

    Indice : commencez avec une clé sûre et globale, sans impacter la logique métier des jobs.

  2. Gardez uniquement les clés vraiment globales

    N'y mettez pas des options qui ne concernent qu'un sous-ensemble de jobs.

👉 Vérifier votre solution (Étape 3)

Dans la solution, le pipeline ajoute un default minimal :

default:
interruptible: true

Ce réglage permet d'annuler les jobs obsolètes quand un nouveau pipeline démarre sur la même branche.

Étape 4 — Valider que le comportement n'a pas changé

Section intitulée « Étape 4 — Valider que le comportement n'a pas changé »
  1. Validez le YAML

    Fenêtre de terminal
    glab ci lint .gitlab-ci.yml
  2. Poussez le refactoring

    Fenêtre de terminal
    git add .gitlab-ci.yml
    git commit -m "ci: refactor pipeline with extends and anchors"
    git push origin starter/lab-13
  3. Comparez avec le run baseline

    Les jobs doivent rester équivalents en résultat, ordre logique et artefacts.

👉 Vérifier votre solution (Étape 4)

Après refactoring, les jobs et résultats doivent rester identiques :

  • même ordre logique des stages (lint, test, build, deploy) ;
  • mêmes scripts exécutés dans chaque job ;
  • mêmes artefacts (report.xml) et même regex de coverage.

Vérifiez avec CI Lint, puis en comparant un pipeline avant/après.

📄 Voir le fichier .gitlab-ci.yml complet
stages:
- lint
- test
- build
- deploy
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
default:
interruptible: true
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
.python-base:
image: python:3.12-slim
cache:
key:
files:
- requirements-dev.txt
paths:
- .pip-cache/
.docker-vars: &docker_vars
DOCKER_TLS_CERTDIR: "/certs"
ruff-lint:
extends: .python-base
stage: lint
cache:
policy: pull
before_script:
- pip install ruff
script:
- ruff check app/ tests/
pytest:
extends: .python-base
stage: test
needs: []
before_script:
- pip install -r requirements-dev.txt
script:
- pytest -v --junitxml=report.xml --cov=app --cov-report=term-missing
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
artifacts:
when: always
paths:
- report.xml
reports:
junit: report.xml
expire_in: 7 days
docker-build:
stage: build
image: docker:27
services:
- docker:27-dind
variables:
<<: *docker_vars
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
deploy-staging:
stage: deploy
image: alpine:3.20
script:
- echo "Deploying $CI_COMMIT_SHORT_SHA to staging..."
- ./scripts/deploy-demo.sh staging
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy-production:
stage: deploy
image: alpine:3.20
script:
- echo "Deploying $CI_COMMIT_SHORT_SHA to production..."
- ./scripts/deploy-demo.sh production
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
  • Les duplications cache et before_script ont été réduites
  • ruff-lint et pytest utilisent extends: .python-base
  • Une anchor YAML est utilisée pour les variables Docker récurrentes
  • Le pipeline passe sans régression fonctionnelle

extends fait un merge par clé, pas un deep merge universel. Si vous redéfinissez une clé composite dans un job, vous pouvez écraser une partie de la base sans le vouloir. Vérifiez le rendu final dans CI Lint.

Les anchors YAML sont locales au fichier courant. Si vous externalisez plus tard votre pipeline avec include, une anchor définie dans un autre fichier ne sera pas forcément disponible comme vous l'imaginez.

  • Un pipeline lisible est plus sûr qu'un pipeline simplement "qui passe"
  • extends factorise la structure métier des jobs
  • Les anchors YAML factorisent des blocs purement YAML
  • Le refactoring CI doit préserver le comportement observable

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.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn