BlueGit

Help blue-ci-cd.yml

This page is not available in your language yet; it is shown in another language.

blue-ci-cd.yml

Het bestand ligt in de root van de commit die de pipeline start. De vorm volgt GitLab CI zo ver dat een bestaand .gitlab-ci.yml meestal hernoemd kan worden naar blue-ci-cd.yml. Jobs mogen los op het hoogste niveau staan. Een ouder bestand met een jobs:-map blijft geldig.

stages: [test, build]
variables:
  CGO_ENABLED: "0"
image: alpine:3.20
jobs:
  test:
    stage: test
    image: golang:1.22
    script:
      - go test ./...
    rules:
      - if: $CI_COMMIT_BRANCH == "master"
      - if: $CI_COMMIT_BRANCH =~ /^release\/.*/
  build:
    stage: build
    needs: [test]
    script:
      - go build -o bluegit ./cmd/bluegit
    artifacts:
      paths: [bluegit]
      expire_in: 14d
    allow_failure: false

Velden

VeldBetekenis
stagesVolgorde. Weggelaten: de stages in de volgorde waarin de jobs ze noemen. Een job zonder stage valt in test.
imageStandaardimage, als tekst of als name:. default.image wint als die gezet is. Een preset zoals bluegit/base mag. node:22 blijft een gewoon image.
defaultimage, before_script, after_script, tags, timeout, cache voor jobs die het zelf niet zetten.
includeAlleen local. Een job met dezelfde naam wordt samengevoegd; het buitenste bestand wint per veld.
variablesTekst, of {value: ...}. Een job overschrijft dezelfde naam.
before_script, script, after_scriptafter_script draait ook als script mislukt.
workflowrules zoals bij een job. Geen passende rule, of when: never, maakt geen pipeline.
needsNamen, of {job, optional}. needs: [] start meteen. Weggelaten: wacht op eerdere stages. Een needs op een parallel-job wacht op elke instantie.
rulesDe eerste rule die klopt wint. if, changes, exists en when. Zonder rules gelden only en except.
only, exceptTaknaam, branches, tags, of /regex/.
whenon_success (standaard), on_failure, always, manual. In een rule ook never.
allow_failureEen mislukking houdt de rest niet tegen. De job zelf wordt een waarschuwing.
artifactspaths en expire_in (30s, 30m, 12h, 14d, 2w, never).
cachepaths en een tekstuele key. Lokaal per repository, niet gedeeld met een andere machine.
resource_groupJobs met dezelfde naam in één repository draaien niet tegelijk.
timeoutOverschrijft ci.timeout_seconds voor die job. De runner stopt de container als de tijd om is.
retryEen getal, of {max, when}. max is 0 tot 2; hoger wordt afgekapt. retry: 2 herhaalt elke mislukking behalve een annulering. when noemt script_failure, runner_system_failure, job_execution_timeout, stuck_or_timeout_failure of always.
parallelEen getal, hoogstens 50. De jobs heten naam 1/N en krijgen CI_NODE_INDEX en CI_NODE_TOTAL.
parallel:matrixPer lijstitem het cartesisch product, daarna aan elkaar. De job heet naam: [waarde, waarde]. De sleutels worden variabelen, in de volgorde uit het bestand.
servicesSidecar-containers op een eigen Docker-netwerk met de job. Een tekst, of name, alias, command, entrypoint en variables. Zonder alias is dat het imagenaam zonder registry en tag.
environmentEen naam, of {name, url}. Zet CI_ENVIRONMENT_NAME en CI_ENVIRONMENT_URL. Geen deployment en geen on_stop.
interruptibleStandaard uit. Een nieuwe pipeline op dezelfde ref annuleert oudere jobs die dit aan hebben en nog in de wachtrij staan of lopen. Zo'n job heet in de UI Obsolete (tooltip: canceled because a newer pipeline started for the same ref).
doNotInterruptStandaard false. Een lopende job met doNotInterrupt: true stopt BlueGit nooit automatisch. Een nieuwere pipeline op dezelfde ref wacht (status Wacht, waiting_for_job) tot de job klaar is; handmatig annuleren vraagt om bevestiging (API/bg: force=true / --force). Zie hieronder.
tagsBewaard bij de job. Tags kiezen geen runner; welke runner een job krijgt staat in runner.md.
extendsEén naam of een lijst. Een job waarvan de naam met . begint is een sjabloon en draait niet.

Een if volgt de GitLab-vorm. && bindt sterker dan ||. Haakjes groeperen. Een lege variabele is null.

$NAAM == "waarde" && ($ANDERE == "x" || $ANDERE == null)
$NAAM != "waarde"
$NAAM =~ /reguliere-expressie/
$NAAM =~ /^pad\/met\/slash/
$NAAM =~ /hoofdletters/i
$NAAM !~ /reguliere-expressie/
$NAAM =~ $PATROON

=~ en !~ gebruiken RE2, zonder anker tot je ^ en $ zelf zet. Een / in het patroon schrijf je als \/. De vlag i staat direct achter de sluithaak. Een patroon in een variabele moet bij het beoordelen een geldige expressie zijn.

changes kijkt naar de diff van de push. Is die diff niet te bepalen, dan geldt de voorwaarde. exists eist dat elk pad in de commit staat.

Voorgedefinieerd, in rules en in de container: CI, CI_COMMIT_SHA, CI_COMMIT_SHORT_SHA, CI_COMMIT_BRANCH, CI_COMMIT_TAG, CI_COMMIT_REF, CI_COMMIT_REF_NAME, CI_COMMIT_REF_SLUG, CI_COMMIT_TITLE, CI_COMMIT_TIMESTAMP, CI_DEFAULT_BRANCH, CI_PIPELINE_ID, CI_PIPELINE_SOURCE, CI_PIPELINE_URL, CI_PROJECT_PATH, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_PROJECT_URL, CI_JOB_ID, CI_JOB_NAME, CI_JOB_STAGE, CI_JOB_URL, CI_JOB_TOKEN, CI_API_URL, CI_CONFIG_PATH, CI_SERVER_NAME. CI_COMMIT_TIMESTAMP is de committer-tijd in ISO 8601. Op een tag is CI_COMMIT_BRANCH leeg. CI_PIPELINE_SOURCE is push, web of merge_request_event.

Een merge-request-pipeline start bij het openen van de merge request, bij een push naar de bronbranch, en opnieuw wanneer iemand naar de doelbranch pusht en de doelcommit daardoor verandert. Die draait op de commit van de bronbranch, niet op een samengevoegde boom. Dezelfde combinatie van ref, sha, bron, merge request en doelcommit start geen tweede pipeline. Kiezen de rules voor de push en voor de merge request dezelfde jobs, dan blijft één pipeline over en wordt die aan de merge request gekoppeld. Kiezen ze andere jobs, dan blijven beide bestaan en staat op de merge-request-pipeline het label Merge request met het nummer. CI_PIPELINE_SOURCE is merge_request_event op een pipeline die als merge request is gestart. Daarbij horen CI_MERGE_REQUEST_ID, CI_MERGE_REQUEST_IID, CI_MERGE_REQUEST_TITLE, CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, CI_MERGE_REQUEST_SOURCE_BRANCH_SHA, CI_MERGE_REQUEST_TARGET_BRANCH_NAME, CI_MERGE_REQUEST_TARGET_BRANCH_SHA, CI_MERGE_REQUEST_PROJECT_PATH en CI_MERGE_REQUEST_PROJECT_URL, in de rules én in de container. De pipeline draait op de commit van de bronbranch. Jobs zonder rules en zonder only: merge_requests doen daar niet aan mee. only: merge_requests en except: merge_requests werken wel. Zodra zo'n bestand jobs voor de merge request kiest, mag er pas gemerged worden als de nieuwste merge-request-pipeline geslaagd is. Een job met allow_failure mag mislukken, maar moet wel af zijn. Een job die nog loopt, in de wachtrij staat, op manual staat of geannuleerd is, houdt de merge tegen.

CI_PIPELINE_ID is tijdens het beoordelen van rules nog 0. CI_JOB_ID, CI_JOB_NAME, CI_JOB_STAGE, CI_JOB_URL, CI_JOB_TOKEN, CI_NODE_INDEX, CI_NODE_TOTAL, CI_ENVIRONMENT_NAME en CI_ENVIRONMENT_URL bestaan pas in de container.

Steve zet in elke job GIT_CONFIG_COUNT, GIT_CONFIG_KEY_0=safe.directory en GIT_CONFIG_VALUE_0=*, tenzij de job safe.directory zelf al op * of /workspace zet. De checkout is van de host-gebruiker en de container-user is dat niet. Git 2.35+ weigert die repo anders (dubious ownership, exit 128). Scripts in de checkout geven git -c safe.directory mee, zodat dit ook werkt op een runner die die variabelen nog niet zet.

Bewust niet

pages, spec, een include van een ander project of van een URL, trigger- en child-pipelines, coverage, id_tokens, inherit en hooks. Die sleutels geven een waarschuwing in de editor en houden de rest van het bestand niet tegen. Er is één runner, in het BlueGit-proces, die Docker op dezelfde host aanroept. Zijn naam is runner.name in de configuratie, standaard Steve. In Preparing the docker executor staat altijd Using docker image …, en de image-id met digest als het image al lokaal aanwezig is. Een preset schrijft daar ook Using image preset … based on … en, na een bouw, Underlying image … with digest …. In Preparing environment staan de runnernaam, de host, de werkmap /workspace en het pipeline- en jobnummer. Die naam staat bij de runner op de jobpagina, op de pipelinedetails en boven het joblog. tags kiezen die runner niet. services delen één netwerk met de job; er is geen healthcheck verder dan een gestarte container.

Image-presets

Steve heeft geen aparte runners per taal. image: bluegit/base en de andere aliassen zijn Dockerfiles in deze repository. Bij het eerste gebruik bouwt Steve ze lokaal en hergebruikt hij het resultaat zolang de Dockerfile hetzelfde blijft.

PresetBasis
bluegit/baseAlpine 3.20, met git, curl en bash
bluegit/nodeNode.js 22
bluegit/pythonPython 3.12
bluegit/goGo 1.26, plus git-daemon zodat git http-backend aanwezig is
bluegit/rustRust 1.81
bluegit/javaJava 21 (Temurin)
bluegit/dotnet.NET SDK 8

Elk image krijgt daar bash, ca-certificates, curl en git bij. bluegit/go installeert ook git-daemon: daar zit git-http-backend in. Zonder die binary valt git push over HTTP terug op dumb HTTP en eindigt dat met code 22. ci.presets in config.yml voegt een alias toe of vervangt een ingebouwde: image mapt direct, dockerfile wijst naar een bestand, from herschrijft de eerste FROM.

/-/info toont deze specificatie en de presets. GET /api/v1/ci/spec geeft dezelfde inhoud als JSON. De pagina zet die JSON ook in <script type="application/json" id="bluegit-ci-spec">.

Editor

/-/ci/editor opent blue-ci-cd.yml van een branch. De pagina kleurt YAML en zet fouten en waarschuwingen op het regelnummer. Ontbreekt het bestand, dan opent de editor een uitgecommentarieerd voorbeeld. Opslaan maakt het bestand aan met één commit op de gekozen branch. Bestaat die branch nog niet, dan wordt hij vanaf de geopende branch gemaakt. Een beschermde branch accepteert alleen een beheerder voor een directe commit, dezelfde regel als een gewone push. Een ontwikkelaar krijgt dan de keuze om op een nieuwe branch te committen en een merge request te openen.

Mail bij een mislukte pipeline

Komt een pipeline op failed, dan gaat er één mail naar de gebruiker in actor (wie pushte, of wie de run met de hand startte). Dat loopt via POST /api/email op auth.timfalken.com, met de clientgegevens van BlueGit, niet met het token van de gebruiker. Onderwerp en tekst volgen users.language. De mail noemt de repository, het pipeline-nummer, de branch of tag, de jobs die zonder allow_failure faalden, en links naar de pipeline en het joblog. Zonder client_secret (de dev-modus) gaat er niets weg. Een mailfout laat de pipeline met rust.

Geheimen

Repositorygeheimen komen als omgevingsvariabele mee, zoals CI/CD-variabelen. Bij een groepsrepository eerst de groep, daarna de repository; dezelfde naam laat de repository winnen. Een geheim dat als beschermd is gemarkeerd, komt alleen mee als de branch of tag op een beschermd patroon past. Waarden van acht tekens of meer worden in het joblog vervangen door [MASKED]. CI_JOB_TOKEN ook.

Release vanuit een job

curl -fsS -X POST "$CI_API_URL/repos/$CI_PROJECT_PATH/releases" \
  -H "Authorization: Bearer $CI_JOB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tag":"v1.2.3","commit":"'"$CI_COMMIT_SHA"'","name":"1.2.3","assets":[{"job":"build","path":"bluegit","name":"bluegit"}]}'

Het commit moet in de repository bestaan. Het asset moet een artifact zijn van een geslaagde job in dezelfde pipeline. Een beheerder kan met een persoonlijk token een release zonder assets maken. Een tag is A-Za-z0-9._-, tot 80 tekens, en wordt ook een annotated git-tag.

Eigen release vervangen

Een release bestaat maar één keer: dezelfde tag nog eens publiceren geeft 409. Een job kan zijn eigen release wel vervangen met "replace": true in de body, bijvoorbeeld voor een doorlopende experimentele release. BlueGit verwijdert dan de oude release, de assets en de git-tag en maakt ze opnieuw op het nieuwe commit. Er komt nooit een tweede release met dezelfde tag. Dat mag alleen als al deze regels gelden:

  • Het verzoek komt met CI_JOB_TOKEN van een lopende job in hetzelfde project. Een persoonlijk token of een job uit een ander project kan niet vervangen (403 of 401).
  • De pipeline draait op een branch (refs/heads/…) die niet beschermd is. Een beschermde branch zoals master, een tag-pipeline en forks/* krijgen dit recht nooit (403). Een stabiele release vanaf master blijft daardoor altijd staan.
  • De tag past niet op een beschermd tagpatroon.
  • De pipeline is veroorzaakt door een maintainer of hoger van het project (de actor: wie pushte, mergede of de run startte), of door een instance-admin. Een ontwikkelaar, een onbekende of lege actor krijgt 403.
  • De bestaande release is door een job gemaakt in een pipeline op dezelfde branch. Een release die met de hand of vanaf een andere ref is gemaakt, blijft staan (409).

De assets worden eerst gecontroleerd, pas daarna gaat de oude release weg. Elke vervanging schrijft een regel release audit met action=replace, repository, tag, oud en nieuw commit, job, pipeline, ref en actor in het serverlog. Zonder replace blijft alles zoals het was.

Wanneer een job start

Gate in internal/ci geeft ready, wait, skip of fail. skip volgt op een harde mislukking bij when: on_success, of op succes bij when: on_failure. when: always wacht tot de voorgangers klaar zijn en draait dan toch. Een job met allow_failure telt niet als harde mislukking. De runner verwerkt pipelines elke twee seconden, met ci.concurrency tegelijk (standaard 1). Jobs die al lopen vullen die slots. Een startklare job die nog wacht toont In wachtrij (#n) in de pipelines-lijst, de stage-popover en op de jobpagina. Het nummer is de plek in Steves globale wachtrij, over alle repositories; de eerstvolgende wachtende job is #1.

Zolang er een pipeline of job pending of running is, halen de pipelines-lijst en de pipelinedetails elke drie seconden JSON op en werken de rijen bij zonder de pagina te herladen. Open popovers en de scrollpositie blijven staan. Zijn er geen actieve runs meer, dan stopt dat pollen. Een lopende job is een blauw icoon met een spinner; pending en created zijn een holle grijze cirkel. De commitbalk op het repo-overzicht toont diezelfde status.

Een commit die BlueGit zelf maakt loopt door één post-receive-route, dezelfde als een git push. Dat geldt voor de Pipeline Editor, het toepassen van een suggestie, het mergen van een merge request en een tag die in de interface of via de release-API ontstaat. Die route start de pipeline, de push-webhook en de activity.

Ontbreekt Docker, dan faalt de job met een regel in het log. De time-out is ci.timeout_seconds (standaard 1800).

Live log

De jobpagina vraagt nieuwe regels op zolang de job in de wachtrij staat of loopt, met ?offset= op het log-adres. Diezelfde poll werkt de wachtrijplek bij zolang de job nog queued is. De console scrolt mee zolang je onderaan staat; scrol je omhoog, dan blijft de plek staan. ANSI-kleuren worden classes. Geheimen van acht tekens of meer zijn [MASKED] op het moment dat de regel wordt weggeschreven, ook in die stream.

Canceled, Obsolete en timeouts

  • Obsolete: een interruptible job die stopte omdat een nieuwere pipeline op dezelfde ref startte. De API houdt status: canceled (achterwaarts compatibel) en geeft cancel_reason: superseded en display_status: obsolete. Een pipeline waarvan alle geannuleerde jobs obsolete zijn, toont ook Obsolete.
  • Canceled: handmatig gestopt (knop of POST …/cancel), cancel_reason: manual. Het joblog eindigt met canceled by a user.
  • Timeout: status failed met failure_reason: job_execution_timeout. Het log eindigt met Job timed out after <duur> (limit: <bron>). De limiet is de timeout: van de job, anders ci.timeout_seconds van de server (standaard 30 minuten); de runnerinstelling Maximum job duration kapt beide af.
  • Webhook-jobs hebben dezelfde cancel_reason en failure_reason. Pipeline-events gaan alleen bij geslaagd of mislukt; een geannuleerde of obsolete pipeline stuurt niets.
  • Bestaande jobs zijn bij de upgrade omgezet: alleen een interruptible job met de melding in het log wordt obsolete. Vóór deze versie schreef de runner die melding ook bij handmatig annuleren.

doNotInterrupt

Voor jobs die je niet halverwege wilt stoppen, zoals een FTP-upload of een databasemigratie:

deploy:
  stage: deploy
  doNotInterrupt: true
  script: ./upload.sh
  • Mag ook in default:, via extends: en templates. Een waarde die geen boolean is, geeft een configuratiefout.
  • Alleen een job die loopt wordt beschermd. Wachtende jobs van een oude pipeline kunnen gewoon geannuleerd of obsolete worden, zoals nu.
  • Nieuwere pipeline op dezelfde ref: die start geen jobs zolang een oudere pipeline op die ref een lopende doNotInterrupt-job heeft. De UI toont Wacht op niet-onderbreekbare job X in pipeline #N, de API status: waiting_for_job plus waiting_for. Daarna gaat de oude pipeline af (Obsolete als de rest door de nieuwe pipeline werd geannuleerd) en start de nieuwe. Een tussenliggende pipeline wordt gewoon obsolete (als zijn jobs interruptible zijn). Er ontstaat geen deadlock: alleen oudere pipelines houden nieuwere tegen, en de job-timeout blijft gelden. Faalt of timet de job uit, dan gaat de nieuwe pipeline gewoon verder.
  • Handmatig annuleren (job of pipeline): in de UI een waarschuwingsvenster met de naam van de job(s). Via de API POST …/cancel?force=true, via bg pipeline cancel|cancel-job … --force. Zonder force: HTTP 409 met uitleg.

Alle paden die een lopende job kunnen stoppen

PadGedrag met doNotInterrupt
Nieuwe pipeline op dezelfde ref (interruptible)Lopende job loopt uit; de nieuwe pipeline wacht.
Job of pipeline annuleren (UI, API, bg)Waarschuwing in de UI; API/bg alleen met force=true/--force.
Repository verwijderenGeweigerd zolang de job loopt (UI: wachten; API: force=true).
Aparte runner intrekken (account, groep, globaal; UI en API)Geweigerd zolang die runner de job draait (API: force=true).
Aparte runner geeft geen hartslag meerJob wordt niet na 5 minuten als mislukt afgerond; alleen na timeout + 5 minuten.
BlueGit stoppen of herstarten (systemd, reboot)BlueGit wacht met afsluiten tot de job klaar is (TimeoutStopSec=49h); de website blijft zolang bereikbaar.
Self-updateWachtte al op nul lopende jobs en breekt nooit iets af.
bluegit-runner stoppen (SIGTERM)De runner wacht tot de job klaar is (TimeoutStopSec=49h).
Runner pauzeren, instellingen wijzigenStopte lopende jobs al niet.
Branch verwijderen, MR sluiten of mergen, zichtbaarheid wijzigenBlueGit annuleert hierbij geen jobs.

Uitzonderingen (bewust): 1. Job-timeout: blijft gelden, anders kan een vastgelopen job alles eeuwig blokkeren. Een timeout is geen annulering maar failed met job_execution_timeout. 2. Crash, kill -9, stroomuitval van BlueGit of de runnerhost: het proces is weg, dus de job ook. Na de herstart staat de job op failed met de melding dat BlueGit herstartte. 3. Expliciet forceren door een gebruiker (force=true, of Toch stoppen in het venster). 4. Aparte runner die langer dan timeout + 5 minuten onbereikbaar is: dan wordt de job als runner_system_failure afgerond, omdat BlueGit niet kan weten of hij nog loopt.

Verlopen handmatige jobs

Een handmatige job (when: manual) die nog wacht, verloopt automatisch als zijn pipeline verouderd is:

  • Nieuwere pipeline: zodra er een nieuwere pipeline is op dezelfde ref (en hetzelfde merge request) in dezelfde repository. Een pipeline die alleen een kapotte blue-ci-cd.yml meldt, telt niet mee. De handmatige job in de nieuwste pipeline blijft gewoon startbaar.
  • Leeftijd: een pipeline die alleen nog op handmatige jobs wacht en ouder is dan N dagen (Beheer > Pipelines, standaard 7, 0 = uit; per project te overschrijven in de projectinstellingen, leeg = instance-instelling).

Een verlopen job krijgt status canceled met cancel_reason superseded of stale, display_status obsolete en een regel in het log. Jobs die via needs alleen op die handmatige job wachtten, verlopen mee. Lopende of wachtende niet-handmatige jobs blijven ongemoeid. Log en artifacts blijven staan. De pipelinestatus volgt de bestaande regels: een verlopen job zonder allow_failure maakt de pipeline canceled (getoond als obsolete), een verlopen job met allow_failure telt niet mee. Starten of opnieuw starten van een verlopen job geeft 409. De controle draait één minuut na de start en daarna elk uur; POST /api/v1/admin/pipelines/expire-stale draait hem direct.