**⚠️ STATUS: OSITTAIN VANHENTUNUT** — Statusraportointi (7) ja exit-koodit (8) on formalisoitu ADR:iin 0007 ja 0008. Loput osiot validioitu POC-ajossa. # CI Pipeline Practices ## 1. Error Taxonomy | Taso | Esimerkki | Status URL | Job status | |---|---|---|---| | Tool failure | npx ei löydy, python3 puuttuu | Gitea Actions logi | fail | | Test failure | assertio feilaa, testi palauttaa nollasta poikkeavan | Raportti git-pagesissa | fail | | Suite failure | Build ei päässyt ajoon | Raportti git-pagesissa | fail | - Tool ja test erotetaan omiin steppeihin - Tool check: `--help` ei riitä, lataa `--dry-run` moduulit - Tool fail → linkki Gitea logiin; test fail → linkki raporttiin - Jobin status tulee exit-koodista (`exit $?`), ei raporttitiedostojen olemassaolosta ## 2. Job Reporting - Jokainen suite julkaisee raporttinsa omaan alihakemistoonsa: `reports/{SHA8}/{suite}/` - Commit status URL osoittaa suoraan suitelinkkiin - Raportit tuotetaan ja julkaistaan aina (`if: always()`) — testin lopputuloksesta riippumatta - Linkkejä summary-sivuille ei tarvita — kukaan ei sinne pääse ilman suoraa URLia - Index.html suitetason raporttiin generoidaan aina, linkittää kaikkiin suiten tiedostoihin ## 3. Docker-in-Docker Volume Mount Runner-kontti ja Docker daemon näkevät eri polut. `-v "$PWD":/path` ei toimi — runner-näkökulman polku ei ole daemonin näkökulman polku. Kolme toimivaa vaihtoehtoa: - `container:` keyword — runner hoitaa mountin oikein - `docker volume create -- docker run -v volume:/data -- docker run -v volume:/data` - `tar c . | docker run --rm -i -v volume:/data alpine tar x -C /data` ## 4. Env variable scope (validated 2026-06-13) `env:` — oli se workflow- tai job-tasolla — toimii vain **natiiveissa shell-stepeissä** ja `docker run -e VAR` -komennoissa. `docker run` ilman `-e`-lippua **ei peri** `env:`-muuttujia. Tämä on validioitu POC-ajolla: `tmp/poc-env-scope.yml` | Sijainti | Native shell | `docker run` ilman `-e` | `docker run -e VAR` | |----------|-------------|------------------------|---------------------| | workflow `env:` | ✅ perii | ❌ tyhjä | ✅ perii | | job `env:` | ✅ perii | ❌ tyhjä | ✅ perii | | `GITHUB_ENV` | ✅ perii | ❌ tyhjä | ✅ perii | Käytäntö: - Workflow-tason `env:` sopii arvoille, joita tarvitaan natiivistepeissä (publish, status, reportointi) - Jos `docker run` tarvitsee env-arvoja, välitä ne eksplisiittisesti `-e VAR`-lipulla - `GITHUB_ENV` on validi tapa välittää arvoja stepien välille samassa jobissa, mutta ei leviä `docker run`-kontteihin ilman `-e`-lippua ### Cross-job config propagation (validated 2026-06-13) Config-arvojen vienti kaikkiin jobeihin ilman toistoa vaatii kahden mekanismin ketjuttamista: 1. **`needs` + `with:`** — `jobs..with.` tukee `needs`-kontekstia. Tämä mahdollistaa sen, että yhden jobin outputit voidaan välittää toiselle reusable workflowille inputeina. 2. **Workflow `env:`** — ainoa natiivi mekanismi, joka tekee arvoista näkyviä kaikissa jobeissa automaattisesti (POC validioitu). Ketju toimii näin: ``` gitea-env.conf → config-provider.yml → env_json (yksi JSON-string) (1) (2) ↓ ci.yml with: env_json ${{ needs.load-config.outputs.env_json }} (3) ↓ build-feature.yml workflow env: GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }} (4) ↓ kaikki jobit → $GITEA_API_URL, $GIT_PAGES_URL jne. (5) ``` Vaiheet: 1. Consumer määrittelee arvot `gitea-env.conf`:ssä (KEY=VALUE) 2. `config-provider.yml` lukee confin ja tuottaa yhden JSON-stringin outputina 3. `ci.yml` välittää JSONin `needs` + `with:` -ketjulla 4. `build-feature.yml` purkaa arvot workflow `env:`-tasolle `fromJson()`:lla 5. Kaikki jobit käyttävät valmiita env-muuttujia (`$GIT_PAGES_URL` jne.) Avainkomponentit: - **config-provider.yml** — reusable workflow, joka muuntaa conf-tiedoston yhdeksi JSON-outputiksi. Yksi output riittää, ei per-key outputteja. - **`jobs..with`** — tukee `needs`-kontekstia (Gitea Actions, kuten GitHub Actions). Tämä on kriittinen yksityiskohta: ilman tätä config-arvoja ei voi välittää reusable workflowille dynaamisesti. - **workflow `env:`** — ainoa tapa jakaa arvot kaikkiin jobeihin. `fromJson(inputs.env_json).KEY` purkaa yksittäiset arvot ilman toistoa. - **Per-job `env:`** — sisältää vain secretit (`GITEA_TOKEN`, `GIT_PAGES_PUBLISH_TOKEN`), ei config-arvoja. ## 5. Pipeline Provides All Dependencies - Ei luottamusta runnerin esiasennettuihin työkaluihin - `apk add`, `npm install`, `apt-get install` — kaikki pipelinesta - Erityisesti: `curl`, `lsof`, `jq`, `python3` unohtuvat helposti - Node-version päivitettävä jos paketti vaatii uudempaa (`node:20` → `node:22`) - Jos kontin entrypoint on `sh` (Alpine ash), käytä `--entrypoint bash` ## 6. Rakenne - Rinnakkaiset jobit (bats + cucumber) — tuloksia saa heti kun valmistuu - Jokainen testisetti omassa jobissaan - Finalize/build voi kerätä yhteenvedon (ei julkaista summarya jos kenelläkään ei ole linkkiä) ## 7. Commit Status Before Exit Commit status (`ci-bats`, `ci-cucumber`) on asetettava **ennen** stepin `exit`-komentoa, samassa shell-prosessissa. Ei `GITHUB_ENV`-propagointiin luottamista stepien välillä — Gitea Actions ei välttämättä prosessoi `GITHUB_ENV`-tiedostoa epäonnistuneen stepin jälkeen. Käytäntö testi-stepissä: ``` testit_ajoon EXIT=$? STATE="success" [ "${EXIT}" != "0" ] && STATE="failure" # Jos raportti on kirjoitettu levylle → linkki git-pagesiin if [ -f "reports/${SHA8}/sute/index.html" ]; then bash .ci/scripts/report-status.sh "${STATE}" "Kuvaus" ci-{suite} {suite} else # Muuten linkki Gitea Actions logiin bash .ci/scripts/report-status.sh "${STATE}" "Kuvaus" ci-{suite} fi exit ${EXIT} ``` Tämä takaa: - Aina commit status riippumatta siitä, onko kyseessä tool- vai test error - Oikea URL: raportti git-pagesissa (jos tiedosto on kirjoitettu) tai Gitea Actions logeissa - PR merge-esto toimii luotettavasti: branch protection näkee statuksen aina, koska se kirjoitetaan ennen stepin failaamista Julkaisu (`publish-git-pages.sh`) jää edelleen omaksi stepikseen `if: always()`:lla. ## 8. Pipeline Exit Code Safety (validated 2026-06-14) Pipeline (`cmd1 | cmd2 | cmd3`) asettaa `$?`:ksi **viimeisen** komennon exit-koodin. Jos `tee` tai muu aina-onnistuva komento on viimeisenä, testin todellinen exit-koodi katoaa. ### Dangerous patterns | Pattern | `$?` captures | Result | |---------|---------------|--------| | `docker run … \| tee file` | `tee`:n exit (0) | ❌ test error kadotettu | | `tar \| docker \| tee` | `tee`:n exit (0) | ❌ test error kadotettu | | `docker run … 2>&1 \| tee file` | `tee`:n exit (0) | ❌ stderr-ohjaus ei auta | | `set -o pipefail` + pipeline | viimeisen epäonnistuneen exit | ⚠️ pipefail riippuu bash-versiosta ja PIPESTATUS resetoituu helposti | ### Safe patterns | Pattern | `$?` captures | Verified | |---------|---------------|----------| | `docker run … > file 2>&1` | suoraan kontin exit | ✅ lokaali + CI | | `docker volume` + `tar \| alpine tar x` (data transfer) + `docker run > file` | suoraan kontin exit | ✅ lokaali + CI | ### Why volume-based approach works Kolmen erillisen komennon ketju ilman testiä putkittavaa pipeä: ``` docker volume create ws # 1. volyymi tar c . | docker run … alpine … # 2. data volyymiin (tämä on pipe, mutta data transfer) docker run -v ws:/data … > file # 3. testit → exit koodi $?:iin puhtaana ``` Vaihe 2 on pipe, mutta se on **data transfer** — sen exit-koodilla ei ole väliä. Vaihe 3 on suora `docker run > file` ilman pipeä, joten `$?` on aina kontin exit. ### Debug-näkyvyys ilman tee:tä `tee` antaa real-time logit, mutta tappaa exit-koodin. Ratkaisu: jaa kahteen steppiin: ```yaml - name: Run tests run: | docker run … > results.txt 2>&1 EXIT=$? echo "EXIT=${EXIT}" >> "${GITHUB_ENV}" exit ${EXIT} - name: Publish reports if: always() run: publish.sh - name: Set commit status if: always() run: | [ "${EXIT}" = "0" ] && report-status.sh success … || report-status.sh failure … ``` `if: always()` julkaisee raportit ja asettaa commit statuksen riippumatta testin lopputuloksesta. `GITHUB_ENV`:iin talletettu exit-koodi on luettavissa myöhemmissä stepeissä. ### Oppitunti Pienikin muutos — kuten `| tee` lisääminen debug-näkyvyyttä varten — voi murtaa error propagationin huomaamatta. Ainoa tapa varmistua on testata lokaalisti kontissa: ```bash docker run … > results.txt 2>&1 EXIT=$? echo $EXIT # pitää olla 1 jos testit failaa ``` ## 9. Inline Logic Threshold Logiikka workflow YAML:ssa on hauras: YAML:n sisennys, heredocit ja kenoviivat tuottavat helposti toimimattomia steppejä. **Kynnys siirtää scriptiksi:** heti kun steppiin tulee ehtoja, silmukoita, tai yli 3 riviä inline-koodia, siirrä omaksi scriptikseen `.gitea/scripts/`- kansioon. Esimerkki: coverage-datan purku ja navigointi-indexin luonti oli aluksi inline-heredocina workflow YAML:ssa. Siirto omaan `bats-coverage.sh`-scriptiin teki siitä luettavan, testattavan ja muokattavan ilman YAML-muotoiluriskejä.