
Votre pipeline doit créer des jobs différents selon le contexte ? Les pipelines dynamiques permettent de générer la configuration CI/CD à la volée. Un job crée le YAML, un autre l’exécute.
Ce guide est fait pour vous si…
Section intitulée « Ce guide est fait pour vous si… » Nombre de jobs variable Un job par client, par service, par environnement.
Jobs conditionnels complexes Plus que ce que rules peut faire.
Configuration externe Jobs basés sur un fichier de config, une API.
Monorepo avancé Détecter les services modifiés et générer leurs jobs.
Prérequis
Section intitulée « Prérequis »Avant de continuer, assurez-vous de maîtriser :
Principe des pipelines dynamiques
Section intitulée « Principe des pipelines dynamiques »┌──────────────┐ ┌──────────────┐ ┌──────────────────┐│ generate │───▶│ child.yml │───▶│ pipeline enfant ││ (script) │ │ (artefact) │ │ (exécution) │└──────────────┘ └──────────────┘ └──────────────────┘- Un job génère un fichier YAML de pipeline
- Ce fichier est sauvegardé comme artefact
- Un trigger exécute ce pipeline comme enfant

Configuration de base
Section intitulée « Configuration de base »Pipeline parent
Section intitulée « Pipeline parent »stages: - generate - trigger
generate_pipeline: stage: generate image: alpine:3.18 script: - | cat > child-pipeline.yml << 'EOF' stages: - test
test_job: stage: test script: - echo "Generated job!" EOF artifacts: paths: - child-pipeline.yml
trigger_child: stage: trigger trigger: include: - artifact: child-pipeline.yml job: generate_pipeline strategy: dependCas d’usage : tests multi-clients
Section intitulée « Cas d’usage : tests multi-clients »Imaginons que vous devez tester votre application pour plusieurs clients, chacun avec sa configuration.
Génération avec un script shell
Section intitulée « Génération avec un script shell »stages: - generate - test
generate: stage: generate image: alpine:3.18 script: - | # Liste des clients (pourrait venir d'un fichier ou API) CLIENTS="client1 client2 client3"
echo "stages:" > test-pipeline.yml echo " - test" >> test-pipeline.yml echo "" >> test-pipeline.yml
for client in $CLIENTS; do cat >> test-pipeline.yml << EOF test_${client}: stage: test script: - echo "Testing for ${client}" - ./run-tests.sh --client ${client} variables: CLIENT_NAME: "${client}"
EOF done
cat test-pipeline.yml artifacts: paths: - test-pipeline.yml
run_tests: stage: test trigger: include: - artifact: test-pipeline.yml job: generate strategy: dependRésultat généré
Section intitulée « Résultat généré »stages: - test
test_client1: stage: test script: - echo "Testing for client1" - ./run-tests.sh --client client1 variables: CLIENT_NAME: "client1"
test_client2: stage: test script: - echo "Testing for client2" - ./run-tests.sh --client client2 variables: CLIENT_NAME: "client2"
test_client3: stage: test script: - echo "Testing for client3" - ./run-tests.sh --client client3 variables: CLIENT_NAME: "client3"Générer selon les fichiers modifiés
Section intitulée « Générer selon les fichiers modifiés »Détectez les services modifiés dans un monorepo et générez leurs jobs :
stages: - detect - generate - build
detect_changes: stage: detect script: - | # Gérer le cas du premier push (BEFORE_SHA = 0000...) if [ "$CI_COMMIT_BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then # Premier push : considérer tous les fichiers CHANGED=$(git ls-tree -r --name-only HEAD | cut -d'/' -f1 | sort -u | tr '\n' ' ') else CHANGED=$(git diff --name-only "$CI_COMMIT_BEFORE_SHA" "$CI_COMMIT_SHA" | cut -d'/' -f1 | sort -u | tr '\n' ' ') fi
if [ -z "$CHANGED" ]; then echo "PIPELINE_EMPTY=true" >> detect.env else echo "PIPELINE_EMPTY=false" >> detect.env echo "SERVICES=$CHANGED" >> detect.env fi artifacts: reports: dotenv: detect.env
generate_pipeline: stage: generate needs: ["detect_changes"] rules: - if: '$PIPELINE_EMPTY == "false"' script: - | echo "stages:" > build-pipeline.yml echo " - build" >> build-pipeline.yml
for service in $SERVICES; do # Vérifier que c'est un service avec Dockerfile if [ -f "${service}/Dockerfile" ]; then cat >> build-pipeline.yml << EOF
build_${service}: stage: build script: - cd ${service} - docker build -t ${service}:\$CI_COMMIT_SHA . EOF fi done artifacts: paths: - build-pipeline.yml
trigger_builds: stage: build needs: ["generate_pipeline"] trigger: include: - artifact: build-pipeline.yml job: generate_pipeline strategy: depend rules: - if: '$PIPELINE_EMPTY == "false"'Générer depuis une configuration
Section intitulée « Générer depuis une configuration »Utilisez un fichier de configuration pour définir les jobs :
Fichier de configuration
Section intitulée « Fichier de configuration »environments: - name: staging url: https://staging.example.com auto_deploy: true - name: production url: https://example.com auto_deploy: false approval_required: trueGénérateur Python
Section intitulée « Générateur Python »#!/usr/bin/env python3import yaml
with open('config/environments.yml') as f: config = yaml.safe_load(f)
pipeline = { 'stages': ['deploy'],}
for env in config['environments']: job_name = f"deploy_{env['name']}" job = { 'stage': 'deploy', 'script': [ f"./deploy.sh {env['name']}" ], 'environment': { 'name': env['name'], 'url': env['url'] } }
if not env.get('auto_deploy', True): job['when'] = 'manual'
if env.get('approval_required'): job['allow_failure'] = False
pipeline[job_name] = job
with open('deploy-pipeline.yml', 'w') as f: yaml.dump(pipeline, f, default_flow_style=False)Pipeline parent
Section intitulée « Pipeline parent »stages: - generate - deploy
generate_deploy: stage: generate image: python:3.11 script: - pip install pyyaml - python scripts/generate-deploy-pipeline.py artifacts: paths: - deploy-pipeline.yml
deploy: stage: deploy trigger: include: - artifact: deploy-pipeline.yml job: generate_deploy strategy: dependGénération avec Ansible
Section intitulée « Génération avec Ansible »Pour des cas complexes, utilisez des templates Jinja2 avec Ansible :
Template Jinja2
Section intitulée « Template Jinja2 »stages: - test
{% for host in groups['all'] %}test_{{ host }}: stage: test script: - pytest --host {{ host }} variables: TARGET_HOST: "{{ host }}" HOST_NAME: "{{ hostvars[host]['name'] }}"
{% endfor %}Playbook de génération
Section intitulée « Playbook de génération »---- hosts: localhost gather_facts: false tasks: - name: Generate pipeline from template template: src: templates/test-pipeline.yml.j2 dest: ./generated-pipeline.ymlPipeline GitLab
Section intitulée « Pipeline GitLab »generate: stage: generate image: ansible/ansible-runner script: - ansible-playbook -i inventory generate-pipeline.yml artifacts: paths: - generated-pipeline.yml
run_tests: stage: test trigger: include: - artifact: generated-pipeline.yml job: generate strategy: dependBonnes pratiques
Section intitulée « Bonnes pratiques »- Validez le YAML généré avant de le passer au trigger :
generate: script: - ./generate.sh > pipeline.yml - python -c "import yaml; yaml.safe_load(open('pipeline.yml'))" # Valide artifacts: paths: - pipeline.yml- Loggez le pipeline généré pour le debug :
generate: script: - ./generate.sh > pipeline.yml - echo "=== Generated pipeline ===" && cat pipeline.yml- Gérez le cas “aucun job” :
# Option A : générer un pipeline minimal (recommandé)generate: script: - | if [ -z "$JOBS_TO_CREATE" ]; then # Pipeline vide = job noop echo 'noop: { script: ["echo No jobs to run"] }' > pipeline.yml else ./generate.sh > pipeline.yml fi
# Option B : conditionner avec une variable dotenvtrigger: trigger: include: - artifact: pipeline.yml job: generate rules: - if: '$PIPELINE_EMPTY == "false"'- Utilisez des templates réutilisables dans le pipeline généré.
Erreurs fréquentes
Section intitulée « Erreurs fréquentes »| Erreur | Cause | Solution |
|---|---|---|
invalid YAML | Erreur de syntaxe dans le généré | Valider avec un parser YAML |
artifact not found | Job generate a échoué | Vérifier les logs de génération |
| Pipeline vide | Aucun job généré | Générer un job noop par défaut |
circular dependency | Le généré référence le parent | Vérifier les dépendances |
rules: exists ne fonctionne pas | exists teste le repo, pas les artefacts | Utiliser une variable dotenv |
| Diff vide sur schedule/web | CI_COMMIT_BEFORE_SHA absent ou invalide | Prévoir un fallback |
À retenir
Section intitulée « À retenir »- Génération : un job crée un fichier YAML
- Artefact : le fichier est sauvegardé
- Trigger :
include: artifact:exécute le pipeline strategy: depend: le parent attend la fin- Validez toujours le YAML généré
- Limites : 1 Mo max, 3 fichiers include par child pipeline
- Piège
exists: ne teste pas les artefacts, utilisez des variables dotenv - Piège
HEAD~1: préférezCI_COMMIT_BEFORE_SHA→CI_COMMIT_SHA
Prochaines étapes
Section intitulée « Prochaines étapes » Pipelines parent-enfant Base des pipelines dynamiques.
Rules avancées Conditions sans génération dynamique.