Monday Short: Dynamische Pipeline in GitLab CI

Monday Short: Dynamische Pipeline in GitLab CI

Benjamin Kaup

22. April 2024

Problem

Vor einiger Zeit sind wir in einem Projekt von Jenkins auf GitLab CI migiriert. Hintergrund war das betrieblich geführte GitLab CI und damit einhergehende Vorteile wie Wartung, Skalierung etc. Ein Feature in Jenkins, welches wir nun nicht mehr hatten war das Scripting in den einzelnen Steps der Pipeline und dynamische Erzeugung von zB Variablen. Lange Zeit hatte GitLab hierfür keine Lösung, da die Pipeline hier deskriptiv in YAML definiert wird. Die Steps der Pipeline werden in einem Runner gestartet und bekommen genau die Variablen mit, welche Ihnen in der YAML File zum Start der Pipeline (nicht des Steps) zur Verfügung stehen.

Zwischenlösung

Nach einiger Zeit hat man sich mit den damals zur Verfügung stehenden Features seitens GitLab abgefunden und angefangen gewisse Features über Bash-Skript und ähnliches nachzubauen.

Ein Beispiel: Die Image-Version eines erzeugten und anschließend deployten Images.

IMAGE_VERSION=${POM_VERSION%.*}.$(git show -s --format=%cd_%h --date=format:'%Y%m%d_%H%M%S')_$(date +%Y%m%d_%H%M%S)

Zwischenlösung: Erzeugung einer Env-File, welches die benötigten Variablen welche erst nach Start der Pipeline erzeugt werden in jedem Step verfügbar macht.

generate:version:
  script:
    - IMAGE_VERSION=${POM_VERSION%.*}.$(git show -s --format=%cd_%h --date=format:'%Y%m%d_%H%M%S')_$(date +%Y%m%d_%H%M%S)
    - PIPELINE_PROCESS_IDENTIFIER=$(uuidgen)
    - echo "IMAGE_VERSION=${IMAGE_VERSION}" >> version.env
    - echo "PIPELINE_PROCESS_IDENTIFIER=${PIPELINE_PROCESS_IDENTIFIER}" >> version.env
  artifacts:
    reports:
      dotenv: version.env

Nachteil an dieser Lösung: Man kann zwar Variablen während der Laufzeit der Pipeline erzeugen und innerhalb der Steps nutzen, nicht aber für die Variablen die außerhalb des Containers in einem Step verfügbar sein müssen.
Dadurch geht folgendes Snippet in einer Pipeline leider nicht.

Lösung

Inzwischen wurde durch GitLab die Parent-Child Pipeline als Feature hinzugefügt. Die nach und nach einige Teilfeatures dazu erhalten hat. Funktionsweise ist, dass eine Pipeline (Parent) eine Sub-Pipeline anstößt, welche nun als eigenständige Pipeline (Child) läuft. Wie im ersten Absatz erwähnt werden Konfigurationen außerhalb des Pipeline-Steps immer am Start einer Pipeline genutzt. Vortreffliche Chance, hier eine Lücke für unseren Feature-Wunsch zu finden, und tatsächlich besteht inzwischen die Möglichkeit, eine Pipeline-Definition in der Parent-Pipeline zu erzeugen und in der Child-Pipeline zu nutzen. Somit kann man Variablen abhängig von Steps machen, solange die nutzenden Steps allesamt in einer Child-Pipeline verpackt sind.

Im konkreten Fall nutzen wir dies nun um ein Deployment zeitgesteuert von Automatisch auf Manuell zu stellen.

Das Script für den Step in der Parent-Pipeline:

generate:deployment:
	script:
 	- git clone git@ssh.git.xx:pipeline/defintion --branch main ./${STAGE} && cd ./${STAGE}/steps
 	- DAY_OF_THE_WEEK=$(TZ=":Europe/Berlin" date +%u)
 	- HOUR_OF_THE_DAY=$(TZ=":Europe/Berlin" date +%-H)
 	- |
    	if [ $DAY_OF_THE_WEEK -gt 4 ]; then
      	echo "Deployment is not allowed between Friday and Sunday."
      	sed "s|###VERSION###|${IMAGE_VERSION}|g ; s|###IDENTIFIER###|${PIPELINE_PROCESS_IDENTIFIER}|g ; s|\s\s//to-sed|    - when: manual|g" deployment.yaml >> deploy.yaml
      	exit 0
    	fi

Das eigentliche Template, welches per ‘sed’  manipuliert wird:

deploy:
  stage: Deployment
  environment:
    name: $NAMESPACE
    url: $ENV_URL
  variables:
    IMAGE_VERSION: ###VERSION###
    PIPELINE_PROCESS_IDENTIFIER: ###IDENTIFIER###
  needs:
    - project: $PARENT
      job: $JOB
      ref: $REF
      artifacts: true
          
  rules:
  //to-sed

  script:
    - do-some-deployment-things.sh

Als letztes müssen wir die eben erzeugte Pipeline nur noch triggern:

deploy:trigger:
  stage: Deployment
  variables:
    PARENT: $CI_PROJECT_PATH
    JOB: generate:deployment
    REF: $CI_COMMIT_REF_NAME
  needs: ["generate:deployment"]
  trigger:
    include:
     - artifact: deploy.yaml
       job: generate:deployment
    strategy: depend

Als Tipp möchte ich noch mitgeben, besonderes Augenmerk auf evtl. genutzte Caches der Steps zu legen. Hier kann es schnell passieren, dass durch überlappende Pipelines die erzeugten Child-Pipeline-Yamls überschrieben werden und es schnell zu äußerst kuriosen Fehlern führt, welche nicht gleich darauf zurückzuführen sind.