228 lines
9.5 KiB
Markdown
228 lines
9.5 KiB
Markdown
**⚠️ STATUS: ALERT DRAFT** — Ei ole validoitu. Voi sisältää virheellisiä tai puutteellisia käytäntöjä.
|
|
|
|
|
|
# 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.<job_id>.with.<with_id>` 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.<job_id>.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ä.
|