Release-tåg
Ett release-tåg levererar flera repon tillsammans. När tåget kapas låser fremforge commiten för varje repo i det (dess komponenter) i ett oföränderligt, hashat manifest. Sedan tar det releasen genom sina steg, till exempel tst och sedan prd, genom att starta varje repos egna deploy-workflows med de fastlåsta commitarna.
- En release över flera repon. En plattform, dess infrastruktur och en inläsningstjänst levereras som ett tåg med ett manifest, inte som tre driftsättningar som råkar köras nära varandra.
- Kapas enligt en regel. fremforge kapar tåget när den äldsta merge som ännu inte är med i ett tåg är tillräckligt gammal, till exempel 60 minuter. Du kan också kapa för hand.
- Inga merges slinker in. Vid kapningen stoppar en merge-hold merges till komponenterna tills steget du anger har passerats. Merge-knappen, API:t och merge-kön väntar alla.
- Driftsättningsgrindarna gäller oförändrade. Ett tåg startar bara workflow-körningar. Varje körning möter sin miljös grindar (godkännare, tillåtna brancher, tidsfönster, obligatoriska kontroller, samtidighet, pausflaggan) precis som en körning du startar själv.
Release-tåg är avstängda tills en ägare av organisationen slår på dem. När de slås på startar de i endast skugga: de registrerar vad de skulle driftsätta och driftsätter ingenting förrän en ägare stänger av det.
Definiera ett tåg
Ett tåg är en fil, trains/<name>.yaml, i ett repo i din organisation som du väljer (ett litet release-repo fungerar bra). Filnamnet är tågets namn: 1 till 40 gemener, siffror och bindestreck. En ändring av ett tåg är en pull request som vilken som helst, så den granskas, och repots historik är tågets historik.
version: 1
owner: hk # the person the train acts for
description: Platform, infrastructure and ingestion
components:
platform: { repo: platform }
infra: { repo: infrastructure-azure, branch: main }
ingest: { repo: ingestion }
cut:
after_oldest_unreleased_minutes: 60
hold:
release_after: tst # hold merges until stage tst has passed
stages:
- name: tst
timeout_minutes: 90
steps:
- component: platform
workflow: code-deploy.yaml
inputs: { app: migrate, confirm: migrate, sha: "{{ sha }}" }
environment: tst
- component: platform
workflow: code-deploy.yaml
inputs: { app: web, confirm: web, sha: "{{ sha }}" }
environment: tst
- name: prd
steps:
- component: platform
workflow: code-deploy-prd.yaml
inputs: { app: migrate, confirm: migrate, sha: "{{ sha }}", infra_pin: "{{ sha.infra }}" }
environment: prd| Fält | Obligatoriskt | Anmärkning |
|---|---|---|
version | ja | 1. |
owner | ja | Forgejo-inloggningen för den person som tåget agerar för. Personen måste vara medlem i organisationen. Tågets merge-holds är personens. |
description | nej | En rad, upp till 200 tecken. Sätt den inom citattecken om den innehåller #, som YAML läser som början på en kommentar. |
components | ja | 1 till 20, var och en ett namn du väljer som mappas till repo och branch (standard main). Ett repo och en branch kan finnas i ett tåg en gång. |
cut | ja | { after_oldest_unreleased_minutes: N } (0 till 10080), eller manual för att bara kapa för hand. |
hold | nej | release_after: ett stegnamn, cut (håll bara medan manifestet skrivs) eller end (tills tåget är klart). Standard är det första steget. enabled: false sätter ingen merge-hold. |
dispatch | nej | workflows (standard) startar stegens workflow-körningar. none är skuggläge: tåget kapas, håller merges och skriver sitt manifest, men startar ingenting. |
plan_files | nej | En glob för releaseplanfiler, standard .release/*.md. Planfiler som ändrats sedan föregående tåg listas i manifestet med sin sha256. null listar inga. |
stages | ja | 1 till 5, i ordning. Vart och ett har ett name, en valfri timeout_minutes (5 till 2880, standard 120) och 1 till 20 steps. |
Varje delsteg startar en workflow-körning:
| Fält | Obligatoriskt | Anmärkning |
|---|---|---|
component | ja | Vilken komponents repo som kör workflowet, på den komponentens branch. |
workflow | ja | Workflow-filen, till exempel code-deploy.yaml. Den måste ha en workflow_dispatch-trigger. |
inputs | nej | Indata till workflow_dispatch, som strängar. |
environment | nej | Den miljö som körningen måste gå genom. Delsteget passeras bara när körningens driftsättning till den här miljön har passerat sina grindar. Ange den alltid när workflowet driftsätter, så att en körning som aldrig nådde grindarna inte kan räknas som en driftsättning. |
Indatavärden kan använda följande platshållare:
| Platshållare | Värde |
|---|---|
{{ sha }} | Den fastlåsta commiten för delstegets komponent. |
{{ sha.<component> }} | Den fastlåsta commiten för en annan komponent. |
{{ train.name }}, {{ train.number }}, {{ train.id }} | Tågets namn, dess nummer (1, 2, 3, …) och dess id. |
Ett workflow som tar emot commiten som indata ska bygga eller driftsätta exakt den commiten, inte branchens head. Branchen kan gå vidare efter att tågets merge-hold har släppts.
Kontrollera en definition innan den mergas
curl -X POST https://frem.sh/_app/api/v1/orgs/<org>/trains/validate \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg s "$(cat trains/platform.yaml)" '{path: "trains/platform.yaml", source: $s}')"Svaret har valid, errors och warnings. Utöver själva filen kontrollerar fremforge den mot din organisation: att ägaren är medlem, att varje repo och branch finns, att varje workflow finns på sin branch, accepterar workflow_dispatch och de indata som delsteget skickar (och får varje indata som det kräver), och att varje miljö är konfigurerad. En varning säger till när ett workflow aldrig anger delstegets miljö, eller när merge-holden täcker en branch där fremforge/hold inte är en obligatorisk kontroll. Kör den från den pull request som ändrar tåget. Token behöver deployments:read.
Slå på tåg
curl -X PUT https://frem.sh/_app/api/v1/orgs/<org>/trains/settings \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": true, "definitions_repo": "release", "definitions_branch": "main"}'Token behöver policy:write. Så länge tåg är påslagna läser fremforge trains/*.yaml varannan minut, alla vid en och samma commit på branchen.
Svaret, och GET .../trains/settings, har också shadow_only. Det är true tills en ägare stänger av det i konsolen; se Endast skugga.
För att merge-holden ska stoppa merge-knappen slår du på tillämpning av merge-holds för komponenternas brancher. Utan det registreras merge-holden och läses av verktyg, men merge-knappen väntar inte.
Vad en kapning gör
- fremforge kontrollerar definitionen mot organisationen, som
validategör. Ett tåg som inte kan köras kapas inte. - Den sätter merge-holden: en per branch som komponenterna använder, ägd av tågets ägare, med skälet
Release train <name> #<n>: merges held until stage <stage> has passed. - Den läser varje komponents head-commit och jämför den med den commit som föregående tåg låste.
- Den skriver manifestet och tåget tillsammans.
Om något efter merge-holden misslyckas släpper fremforge merge-holden igen. En misslyckad kapning lämnar aldrig en branch spärrad.
En tågdefinition har ett pågående tåg åt gången. Nästa kapning väntar tills det aktuella tåget har avslutats, misslyckats eller avbrutits.
Regeln. För varje komponent är de outgivna commitarna de som tillkommit sedan föregående tågs commit. När den äldsta av dem, på någon komponent, är after_oldest_unreleased_minutes gammal kapas tåget. Åldern är commitens committer-datum, som är tidpunkten för mergen vid en squash-merge. En definitions första tåg räknar från varje branchs head-commit.
För hand. En ägare av organisationen, eller en token som agerar för en person, kan kapa direkt:
curl -X POST https://frem.sh/_app/api/v1/orgs/<org>/trains/<name>/cut \
-H "Authorization: Bearer $FREMFORGE_TOKEN"Token behöver deployments:write och måste agera för en person: en kapning för hand registreras som den personens.
Manifestet
Manifestet är det som tåget levererade, och det ändras aldrig: databasen vägrar varje uppdatering av det. Det innehåller:
- varje komponents repo, branch och fastlåsta commit, den commit som föregående tåg låste och hur många commits som är nya;
- de planfiler som ändrats sedan föregående tåg, var och en med sha256 för sitt innehåll vid den fastlåsta commiten;
- tågdefinitionen som den såg ut vid kapningen, och commiten i definitions-repot som den lästes från, så att en senare ändring av filen aldrig ändrar ett tåg som redan körs.
manifest_sha256 är sha256 för manifestet skrivet som kanonisk JSON: objektnycklar sorterade på varje nivå, inga blanktecken. Du kan räkna om den från det lagrade manifestet:
curl -s https://frem.sh/_app/api/v1/orgs/<org>/trains/runs/<id> \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
| jq -cS '.manifest' | tr -d '\n' | sha256sumSteg
Ett kapat tåg startar sitt första steg vid jobbets nästa körning, inom två minuter. Varje steg kör sina delsteg ett efter ett:
- fremforge startar delstegets workflow med
workflow_dispatchpå komponentens branch, med indata ifyllda. - Körningen möter sin miljös grindar. Medan den väntar på ett godkännande säger tåget det (
waiting for approval in environment tst). Godkännarna godkänner driftsättningen som vanligt, under Bygg och driftsätt → Driftsättningar. - När körningen lyckas, och (om delsteget anger en
environment) dess driftsättning till den miljön har passerat grindarna, är delsteget passerat och nästa startar.
När varje delsteg i ett steg har passerats går tåget vidare till nästa steg. Merge-holden släpps efter steget som anges i hold.release_after, och senast när tåget slutar.
fremforge startar körningarna med plattformens Forgejo-konto, som ett schema skulle göra. Därför kan tågets ägare godkänna tågets egna driftsättningar: ett godkännande från den person som startade en körning vägras.
Ett tåg stoppas (failed, med skälet) när:
- en körning misslyckas eller avbryts, eller en miljös grindar vägrar den (skälet anger grinden);
- en körning lyckas utan att gå genom delstegets miljö;
- ett steg kör längre än sin
timeout_minutes; - en dispatch misslyckas eller dess utfall är okänt. fremforge startar aldrig ett delsteg två gånger: om den inte kan avgöra om en körning startade stoppar den tåget och ber dig titta på repots Actions.
När ett tåg stoppas släpps dess merge-holds. Commits som redan har mergats förblir mergade; nästa kapning tar med rättningen.
Avbryt ett tåg som inte är klart:
curl -X POST https://frem.sh/_app/api/v1/orgs/<org>/trains/runs/<id>/cancel \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason": "Re-cut after the hotfix"}'Merge-holdsen släpps. Workflow-körningar som tåget redan har startat fortsätter att köras.
I konsolen ser organisationens ägare samma sak under Bygg och driftsätt → Release-tåg (https://frem.sh/<org>/_admin/release-trains): varje tåg, det pågående med vad det väntar på, de senaste tågen med sina fastlåsta commits, och knappar för att kapa nu eller avbryta.
Skuggläge
Sätt dispatch: none för att prova ett tåg innan det driftsätter något, till exempel parallellt med den releaseprocess du använder i dag:
version: 1
owner: hk
dispatch: none
# components, cut, hold and stages as usualEtt tåg i skuggläge:
- kapas enligt sin regel, sätter sin merge-hold och skriver sitt manifest, precis som ett tåg som driftsätter;
- registrerar vid kapningen, för varje delsteg, vad det skulle starta: repo, branch, workflow och indata med de fastlåsta commitarna (
would_dispatchpå varje delsteg, med statusshadow); - går sedan igenom sina steg utan att starta en enda workflow-körning, släpper sin merge-hold efter steget som anges i
hold.release_afteroch avslutas; - markeras
shadow: truei API:t, med märket Skugga i konsolen, och i varje händelse och granskningspost.
Jämför vad det skulle ha startat med vad du levererade. När de stämmer överens tar du bort dispatch: none. Nästa kapning driftsätter när organisationen inte längre är i endast skugga.
Endast skugga
dispatch: none är en rad i en fil, och den som kan merga till definitions-repot kan ta bort den. Endast skugga är organisationens egen inställning, och fremforge tillämpar den oavsett vad en definition säger. Så länge den är på:
- är varje tåg ett skuggtåg: varje delsteg registrerar vad det skulle starta, och ingen workflow-körning startas;
- sätts ingen merge-hold, inte ens när en definition begär en;
- anger en kapning vars definition begärde driftsättning eller merge-hold det i manifestet (
definition.org_shadow_only) och i granskningsloggen (release_train.shadow_only_enforced); - stoppas ett tåg som kapades medan endast skugga var avstängt före sin nästa dispatch, om endast skugga slås på igen. Dess merge-holds släpps.
Endast skugga är påslaget för varje organisation tills en ägare stänger av det. Stäng av det i konsolen, under Bygg och driftsätt → Release-tåg: öppna Låt tågen rulla ut, skriv organisationens namn exakt och bekräfta.
- Det kräver en ägare av organisationen som är inloggad i konsolen.
- Om din organisation har ett eget krav på förnyad inloggning ombeds du logga in igen först, om du inte gjorde det under de senaste 10 minuterna.
- Varje ägare av organisationen får ett mejl om vem som stängde av det, och en händelse
train.shadow_only_clearedhamnar i händelseflödet. - API:t kan slå på endast skugga (
"shadow_only": trueiPUT .../trains/settings) men aldrig stänga av det."shadow_only": falsevägras för varje token.
Att slå på endast skugga igen är en knapp på samma sida. Granskningsloggen registrerar release_train.shadow_only_cleared och release_train.shadow_only_set, med vem som ändrade det.
Läsa tåg
| Anrop | Svar |
|---|---|
GET /orgs/<org>/trains | Varje definition, dess pågående tåg och dess senaste tåg, samt de filer som inte går att tolka med sina fel. MCP: list_release_trains. |
GET /orgs/<org>/trains/runs?train=<name> | Kapade tåg, nyaste först, med status, steg, delsteg och merge-holds. MCP: list_release_train_runs. |
GET /orgs/<org>/trains/runs/<id> | Ett tåg med sitt manifest och manifest_sha256. MCP: get_release_train. |
GET /orgs/<org>/trains/settings | Om tåg är påslagna, och var de definieras. |
Alla kräver deployments:read. Ett tågs status är cut, running, completed, failed eller cancelled, och shadow är true för ett tåg i skuggläge.
Händelser och granskning
Händelseflödet har en händelse per komponent-repo för var och en av dessa, med sha som den commit som tåget låste där:
| Typ | När |
|---|---|
train.cut | Ett tåg kapades. data har train, number, train_id, owner, component och manifest_sha256. |
train.stage_promoted | Ett steg passerades. data.stage och data.next_stage (null efter det sista steget). |
train.completed | Varje steg passerades. |
train.failed | Tåget stoppades. data.stage och data.reason; data.outcome är cancelled när någon avbröt det. |
Händelser från ett tåg i skuggläge har data.shadow: true.
Granskningsloggen registrerar release_train.settings_updated, release_train.shadow_only_set, release_train.shadow_only_cleared, release_train.shadow_only_enforced, release_train.cut, release_train.step_dispatched, release_train.stage_promoted, release_train.completed, release_train.failed och release_train.cancelled, tillsammans med merge-holdens egna merge_hold.created och merge_hold.released. En kapning enligt regeln och varje delsteg registreras som system:release-trains, som agerar för tågets ägare; en kapning eller ett avbrott för hand registreras som den person som gjorde det.
Vad ett tåg inte gör
- Det godkänner ingenting. Godkännanden ges av godkännare på driftsättningarna, precis som utan tåg.
- Det ändrar inte grindarna. En miljö med förbikoppling av grindar, en pausflagga eller obligatoriska granskare beter sig exakt som den gör för vilken körning som helst.
- Det rullar inte tillbaka. Ett misslyckat steg stoppar tåget. Mergade commits förblir mergade; leverera rättningen i nästa tåg.
- Det avbryter inte körningar. När ett tåg avbryts lämnas de körningar det har startat orörda.