POC: test reusable workflow job visibility in Gitea Actions (#5)
CI / feature (push) Has been skipped
CI / main (push) Failing after 0s

Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #5
This commit is contained in:
2026-06-13 09:37:47 +03:00
parent 8f1bf7e347
commit dacb8b4ef7
52 changed files with 3887 additions and 645 deletions
+200
View File
@@ -0,0 +1,200 @@
name: Build Feature
on:
workflow_call:
inputs:
bats-image:
required: false
type: string
default: bats/bats:latest
cucumber-node-image:
required: false
type: string
default: node:22
jobs:
bats:
runs-on: ubuntu-latest
env:
GITEA_API_URL: https://gitea.app.keskikuja.site
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
PAGES_HOST: ci-reports.helm-dev.keskikuja.site
GIT_PAGES_PUBLISH_URL: https://ci-reports.helm-dev.keskikuja.site
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Run bats tests
id: bats-tests
shell: bash
run: |
docker volume create bats-workspace
tar c . | docker run --rm -i -v bats-workspace:/data alpine tar x -C /data
mkdir -p "reports/${GITHUB_SHA:0:8}/bats"
set +e
docker run --rm -v bats-workspace:/data \
--entrypoint bash ${{ inputs.bats-image }} \
-c 'apk add -q lsof python3 jq curl && \
cd /data && bats tests/ ' \
> "reports/${GITHUB_SHA:0:8}/bats/results.txt" 2>&1
BATS_EXIT=$?
docker volume rm bats-workspace > /dev/null 2>&1
{
echo "<html><body><h1>Bats tests</h1><ul>"
for f in reports/${GITHUB_SHA:0:8}/bats/*; do
b="$(basename $f)"
[ "$b" = "index.html" ] && continue
echo "<li><a href=\"$b\">$b</a></li>"
done
echo "</ul></body></html>"
} > "reports/${GITHUB_SHA:0:8}/bats/index.html"
echo "BATS_EXIT=${BATS_EXIT}" >> "${GITHUB_ENV}"
exit ${BATS_EXIT}
- name: Publish bats reports
if: always()
shell: bash
run: |
bash .ci/scripts/publish-git-pages.sh "reports/${GITHUB_SHA:0:8}/bats"
- name: Set bats commit status
if: always()
shell: bash
run: |
if [ "${BATS_EXIT}" = "0" ]; then
STATUS="success"
DESC="Bats tests"
URL="https://${PAGES_HOST}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/bats/"
else
STATUS="failure"
DESC="Bats tests FAILED"
URL="https://${PAGES_HOST}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/bats/"
fi
bash .ci/scripts/report-status.sh "$STATUS" "$DESC" "$URL" ci-bats
cucumber:
runs-on: ubuntu-latest
container:
image: ${{ inputs.cucumber-node-image }}
env:
GITEA_API_URL: https://gitea.app.keskikuja.site
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
PAGES_HOST: ci-reports.helm-dev.keskikuja.site
GIT_PAGES_PUBLISH_URL: https://ci-reports.helm-dev.keskikuja.site
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Prepare cucumber
id: prepare-cucumber
shell: bash
run: |
apt-get update -qq && apt-get install -y -qq --no-install-recommends lsof jq
if npm install @cucumber/cucumber > /dev/null 2>&1 && \
npx --package @cucumber/cucumber cucumber-js --dry-run tests/features/ > /dev/null 2>&1; then
echo "TOOL_OK=true" >> "${GITHUB_ENV}"
else
echo "TOOL_OK=false" >> "${GITHUB_ENV}"
fi
- name: Run cucumber tests
if: always()
id: cucumber-tests
shell: bash
run: |
if [ "${TOOL_OK}" != "true" ]; then
echo "CUCUMBER_EXIT=1" >> "${GITHUB_ENV}"
exit 0
fi
mkdir -p "reports/${GITHUB_SHA:0:8}/cucumber"
set +e
npx cucumber-js \
--format json:"reports/${GITHUB_SHA:0:8}/cucumber/report.json" \
--format html:"reports/${GITHUB_SHA:0:8}/cucumber/index.html" 2>&1
CUCUMBER_EXIT=$?
echo "CUCUMBER_EXIT=${CUCUMBER_EXIT}" >> "${GITHUB_ENV}"
exit ${CUCUMBER_EXIT}
- name: Publish cucumber reports
if: always()
shell: bash
run: |
if [ "${TOOL_OK}" = "true" ]; then
bash .ci/scripts/publish-git-pages.sh "reports/${GITHUB_SHA:0:8}/cucumber"
fi
- name: Set cucumber commit status
if: always()
shell: bash
run: |
if [ "${TOOL_OK}" != "true" ]; then
STATUS="failure"
DESC="Cucumber tool unavailable"
URL="https://gitea.app.keskikuja.site/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
elif [ "${CUCUMBER_EXIT}" = "0" ]; then
STATUS="success"
DESC="Cucumber tests passed"
URL="https://${PAGES_HOST}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/cucumber/"
else
STATUS="failure"
DESC="Cucumber tests FAILED"
URL="https://${PAGES_HOST}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/cucumber/"
fi
bash .ci/scripts/report-status.sh "$STATUS" "$DESC" "$URL" ci-cucumber
build:
runs-on: ubuntu-latest
needs: [bats, cucumber]
env:
GITEA_API_URL: https://gitea.app.keskikuja.site
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
PAGES_HOST: ci-reports.helm-dev.keskikuja.site
GIT_PAGES_PUBLISH_URL: https://ci-reports.helm-dev.keskikuja.site
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Generate report index
shell: bash
run: |
SHA8="${GITHUB_SHA:0:8}"
mkdir -p "reports/${SHA8}"
BATS_PASS=$(grep -c 'ok' "reports/${SHA8}/bats/results.txt" 2>/dev/null || echo 0)
BATS_FAIL=$(grep -c 'not ok' "reports/${SHA8}/bats/results.txt" 2>/dev/null || echo 0)
CUCUMBER_PASS=$(jq '.summary.passed // 0' "reports/${SHA8}/cucumber/report.json" 2>/dev/null || echo 0)
CUCUMBER_FAIL=$(jq '.summary.failed // 0' "reports/${SHA8}/cucumber/report.json" 2>/dev/null || echo 0)
{
echo "<!DOCTYPE html><html><head><meta charset='utf-8'>"
echo "<title>CI report ${SHA8}</title>"
echo "<style>body{font-family:sans-serif;margin:2em}a{color:#2563eb}table{border-collapse:collapse}"
echo "th,td{border:1px solid #ccc;padding:8px;text-align:left}"
echo ".pass{color:#059669}.fail{color:#dc2626}</style></head><body>"
echo "<h1>CI report <code>${SHA8}</code></h1>"
echo "<p>Commit: ${GITHUB_SHA}<br>Branch: ${GITHUB_REF_NAME}<br>Run: ${GITHUB_RUN_ID}</p>"
echo "<table><tr><th>Suite</th><th>Passed</th><th>Failed</th><th>Report</th></tr>"
echo "<tr><td>Bats</td><td class='pass'>${BATS_PASS}</td><td class='fail'>${BATS_FAIL}</td>"
echo "<td><a href='bats/results.txt'>results.txt</a>"
echo " | <a href='bats/junit.xml'>junit.xml</a></td></tr>"
echo "<tr><td>Cucumber</td><td class='pass'>${CUCUMBER_PASS}</td><td class='fail'>${CUCUMBER_FAIL}</td>"
echo "<td><a href='cucumber/index.html'>report</a>"
echo " | <a href='cucumber/report.json'>json</a></td></tr>"
echo "</table></body></html>"
} > "reports/${SHA8}/index.html"
- name: Set build commit status
run: |
bash .ci/scripts/report-status.sh success \
"Build complete" \
"https://gitea.app.keskikuja.site/niko/gitea-ci-library/actions/runs/${GITHUB_RUN_ID}" \
ci-build
+27
View File
@@ -0,0 +1,27 @@
name: CI Engine
on:
workflow_call:
inputs:
config-file:
required: true
type: string
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
required: true
jobs:
publish:
runs-on: ubuntu-latest
env:
GITEA_API_URL: https://gitea.app.keskikuja.site
PAGES_HOST: ci-reports.helm-dev.keskikuja.site
GIT_PAGES_PUBLISH_URL: https://ci-reports.helm-dev.keskikuja.site
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Publish reports
run: bash scripts/publish.sh reports
+8 -21
View File
@@ -1,33 +1,20 @@
# .gitea/workflows/ci.yml — Kirjaston oma CI (eat your own dogfood) name: CI
#
# Tämä workflow on kirjaston ENSIMMÄINEN kuluttaja.
# Jokainen push ajaa kirjaston omat reusable workflowt:
# - feature-branch → ci-feature.yml
# - master-branch → ci-master.yml
#
# Näin kirjasto testaa itse itsensä — sama mekanismi
# kuin mitä mikropalvelut käyttävät.
name: CI — gitea-ci-library
on: on:
push: push:
branches: ["**"] branches: ["**"]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
# --- Feature-branch: testit + raportit, ei konttia ---
feature: feature:
if: github.ref != 'refs/heads/master' if: github.ref != 'refs/heads/main'
uses: ./.gitea/workflows/ci-feature.yml uses: niko/gitea-ci-library/.gitea/workflows/build-feature.yml@v1
secrets: inherit secrets: inherit
with: with:
config-file: ci-flow-values.yaml bats-image: bats/bats:latest
# --- Master-branch: build + kontti + test flow --- main:
master: if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/master' uses: niko/gitea-ci-library/.gitea/workflows/build-feature.yml@v1
uses: ./.gitea/workflows/ci-master.yml
secrets: inherit secrets: inherit
with: with:
config-file: ci-flow-values.yaml bats-image: bats/bats:latest
+1
View File
@@ -3,3 +3,4 @@
AGENTS.md AGENTS.md
.ai .ai
node_modules/ node_modules/
tmp/
+85 -2
View File
@@ -2,6 +2,65 @@
Reusable workflow -kirjasto Gitea Actionsille. Lisätietoja: [docs/](docs/) Reusable workflow -kirjasto Gitea Actionsille. Lisätietoja: [docs/](docs/)
## Provider-binding — miten consumer löytää providerin
Consumer kutsuu provideria `uses:`-viittauksella. Ei discoveryä — polku kovakoodataan consumerin
`ci.yml`:ään. Gitea hakee workflow YAML:n **samalta Gitea-palvelimelta** annetusta reposta ja tagista.
### Polun muodostus
```
{owner}/{repo}/.gitea/workflows/ci-engine.yml@{ref}
```
| Osa | Mistä | Esimerkki (homelab) |
|-----|-------|---------------------|
| `owner` | Repopolun ensimmäinen osa — **käyttäjänimi tai org** | `niko` |
| `repo` | Repon nimi | `gitea-ci-library` |
| tiedosto | Providerin workflow | `.gitea/workflows/ci-engine.yml` |
| `@ref` | Tag tai branch provider-repossa | `@v1` (tuotanto) |
**Owner ei ole org-pakotettu.** Homelabissa ei välttämättä ole organisaatiotasoa — silloin owner on
käyttäjänimi (`niko/...`), ei keksitty `org/`-prefiksi.
Polku löytyy repostasi:
```bash
git remote get-url origin
# → ssh://git@gitea.app.keskikuja.site:30009/niko/gitea-ci-library.git
# owner = niko, repo = gitea-ci-library
```
Consumerin `ci.yml`:
```yaml
jobs:
call-engine:
uses: niko/gitea-ci-library/.gitea/workflows/ci-engine.yml@v1
secrets: inherit
```
### Usea Gitea-palvelin
`uses:` resolvoi **vain sen Gitea-instanssin** repohakemistosta, jolla runner ajaa työn.
Toisella palvelimella oleva repo ei näy — cross-server-viittaus ei toimi.
Jokaisella Gitea-palvelimella, jossa mikropalvelut ajavat CI:tä, **tämä kirjasto-repo pitää olla
läsnä** samalla `owner/repo`-polulla:
```
Gitea A (kehitys) Gitea B (tuotanto/homelab 2)
niko/gitea-ci-library ←→ niko/gitea-ci-library (mirror tai push-mirror)
↑ ↑
consumer uses: consumer uses:
niko/gitea-ci-library/... niko/gitea-ci-library/...
```
Mirror pitää `ci-engine.yml`:n ja tagit (`v1`) saatavilla kulloisellakin palvelimella. Tämä korvaa
provider-repon checkout-hackit workflowissa — binding hoituu Gitean natiivilla `uses:`-mekanismilla.
Periaatteet: [tmp/data-flow-design.md](tmp/data-flow-design.md)
## Main-haaran suojaus ## Main-haaran suojaus
Jokaisessa tätä kirjastoa käyttävässä repossa `main`-haara suojataan — koodi päätyy sinne vain PR:n kautta: Jokaisessa tätä kirjastoa käyttävässä repossa `main`-haara suojataan — koodi päätyy sinne vain PR:n kautta:
@@ -39,7 +98,11 @@ Ilman disablointia käyttäjä voi valita merge-commitin PR:n merge-napista —
## Gitea Actions runner (K8s / Helm) ## Gitea Actions runner (K8s / Helm)
Act runner suorittaa Gitea Actions workflowt. Asennus Kubernetes-klusteriin Helm chartilla: Act runner suorittaa Gitea Actions workflowt. **IaC-lähde:** alla oleva Helm-snippet on
klusterin totuus — muutokset vain snippetiin, sitten `helm upgrade --install` (ei käsin muokattuja
arvoja klusterissa).
Asennus Kubernetes-klusteriin Helm chartilla:
### 1. Rekisteröintitoken ### 1. Rekisteröintitoken
@@ -67,6 +130,7 @@ helm upgrade --install act-runner gitea/actions \
--set giteaRootURL="$GITEA_URL" \ --set giteaRootURL="$GITEA_URL" \
--set existingSecret=act-runner-token \ --set existingSecret=act-runner-token \
--set existingSecretKey=token \ --set existingSecretKey=token \
--set statefulset.dind.tag=29.5.2-dind \
--set-string 'statefulset.runner.config=log: --set-string 'statefulset.runner.config=log:
level: info level: info
cache: cache:
@@ -78,13 +142,28 @@ container:
--create-namespace --create-namespace
``` ```
path escapes from parent -bugi korjattiin Docker 29.5.2:ssa. Tämän teko aikana default on 29.5.1 — juuri tämän alle jäävä versio.
Oletus-lokitaso on `debug` — suositeltu `info`. Näkee jobien aloitukset ja valmistumiset ilman konttikerrosten purkua (Downloading/Extracting-spämmiä). `debug` on tarpeen vain vianselvityksessä. Oletus-lokitaso on `debug` — suositeltu `info`. Näkee jobien aloitukset ja valmistumiset ilman konttikerrosten purkua (Downloading/Extracting-spämmiä). `debug` on tarpeen vain vianselvityksessä.
#### Docker (DinD)
Helm chart deployaa DinD:n init-sidecarina (`docker:dind` samassa podissa).
`require_docker: true` kytkee jobit siihen — erillistä DinD-asennusta ei tarvita.
**DinD-tag pinottu:** `29.5.2-dind` (ei chart-oletusta). Docker 29.5.1 aiheuttaa act-runnerissa
`path escapes from parent` -virheen job-kontin käynnistyksessä.
Maven/npm-ajot käyttävät vain workflow'n `container:`-imagea; DinD tarvitaan vasta Docker-buildissä.
### 3. Varmista ### 3. Varmista
```bash ```bash
kubectl get pods -n gitea-actions kubectl get pods -n gitea-actions
# → act-runner-runner-0 2/2 Running # → act-runner-runner-0 Running
kubectl exec -n gitea-actions act-runner-runner-0 -c dind -- docker version
# → Server Version: 29.5.2 (tai uudempi)
``` ```
Gitean puolella runner ilmestyy Active-tilaan pienellä viiveellä: Gitean puolella runner ilmestyy Active-tilaan pienellä viiveellä:
@@ -96,6 +175,9 @@ Site Admin → Actions → Runners (tai Org → Settings → Actions → Runner
Tämän jälkeen `.gitea/workflows/ci.yml` triggeröityy automaattisesti pushista. Tämän jälkeen `.gitea/workflows/ci.yml` triggeröityy automaattisesti pushista.
Tarkista ennen ensimmäistä ajoa: [Provider-binding](#provider-binding--miten-consumer-löytää-providerin)
(owner/repo, `@ref` pushattu, Repo → Settings → Actions enabled).
Lisätietoa runnerin toiminnasta, konteista ja DinD:stä: [docs/runner.md](docs/runner.md) Lisätietoa runnerin toiminnasta, konteista ja DinD:stä: [docs/runner.md](docs/runner.md)
### Muuta ### Muuta
@@ -105,4 +187,5 @@ Lisätietoa runnerin toiminnasta, konteista ja DinD:stä: [docs/runner.md](docs/
| `giteaRootURL` | Gitea-palvelimen osoite (esim. `https://gitea.example.com`) | | `giteaRootURL` | Gitea-palvelimen osoite (esim. `https://gitea.example.com`) |
| `existingSecret` | Kubernetes secretin nimi, jossa token | | `existingSecret` | Kubernetes secretin nimi, jossa token |
| `existingSecretKey` | Avain secretin sisällä | | `existingSecretKey` | Avain secretin sisällä |
| `statefulset.dind.tag` | DinD-image tag (`29.5.2-dind` minimi) |
| `statefulset.runner.labels` | Mukautetut labelit | | `statefulset.runner.labels` | Mukautetut labelit |
+3
View File
@@ -0,0 +1,3 @@
GITEA_API_URL: https://gitea.app.keskikuja.site
PAGES_HOST: ci-reports.helm-dev.keskikuja.site
GIT_PAGES_PUBLISH_URL: https://ci-reports.helm-dev.keskikuja.site
+25
View File
@@ -0,0 +1,25 @@
# 4. Commit-statusviestit — periaate
## Päätös
Gitea Actions näyttää jobien tilan (checkmark, risti, spinner) commit-näkymässä
**automaattisesti**. Tämä on ensisijainen tapa, eikä sitä korvata.
Commit-status API:a (`/api/v1/repos/{owner}/{repo}/statuses/{sha}`) käytetään
**vain** kun natiivi toiminta ei riitä — ensisijaisesti custom-raporttilinkin
välittämiseen commit-näkymään.
## Periaatteet
1. Gitea Actionsin automaattinen commit-status on ensisijainen.
2. API:a kutsutaan vain tarpeeseen: linkki ulkoiseen raporttiin.
3. Jokainen API-kutsussa käytettävä `context`-avain on uniikki.
4. State-arvojen on oltava Gitea API:n valideja (`success`, `failure`,
`pending`, `error`, `warning`).
## Tausta
Jenkins-versiossa jokainen build-vaihe raportoi APIin, koska Jenkins ei
tarjonnut natiivia commit-statusnäkymää. Gitea Actionsissa tämä tulee
automaattisesti — sama tieto kahdesta paikasta aiheuttaa melua eikä lisää
arvoa.
+66
View File
@@ -0,0 +1,66 @@
# 5. Provider & Consumer -malli
## Päätös
Provider (gitea-ci-library) ja Consumer (mikropalveluprojekti) erotetaan
selkeällä rajapinnalla: `.gitea/workflows/ci-engine.yml` on ainoa pinta,
jota consumer kutsuu.
Kaikki muu providerin koodi (scriptit, git-pages-helmi, retention) on
sisäistä toteutusta, johon consumerilla ei ole suoraa pääsyä eikä
riippuvuutta.
## Rajapinnat
### Consumer → Provider (pakollinen)
```yaml
# .gitea/workflows/ci.yml — consumerin repo
jobs:
ci:
uses: niko/gitea-ci-library/.gitea/workflows/ci-engine.yml@v1
secrets: inherit
```
Consumer:
- Omistaa pipeline-logiikan (mitä testejä ajetaan, missä järjestyksessä)
- Luo raportit omiin polkuihinsa (`reports/{sha8}/{step}/`)
- Luo `.meta`-tiedostot per step
- Määrittelee Gitea-secretit (`GIT_PAGES_PUBLISH_TOKEN`, `GITEA_TOKEN`)
### Provider → Consumer (mitä provider tarjoaa)
- **Raporttien julkaisu**: consumerin tuottamat raportit viedään
git-pages-palveluun osoitteeseen, josta ne ovat selaimella luettavissa.
- **Commit-status linkillä**: jokaiselle raportille luodaan commit-status,
jonka kautta käyttäjä pääsee suoraan raporttiin Gitean commit-näkymästä.
- **Orkestrointi**: build-ketjun ylittäessä reporajat, provider huolehtii
workflown käynnistyksestä ja tilan seurannasta.
- **Raporttien elinkaari**: vanhat raportit poistetaan automaattisesti
retention-sääntöjen mukaan.
### Provider (sisäinen toteutus, ei consumerin rajapinta)
- Git-pages Helm-chartti
- Retention sidecar
- Scriptit ja työkalut (toteutus avoin, uudelleenkirjoitettavissa)
- Kaikki paitsi `ci-engine.yml` on sisäistä toteutusta ja voi muuttua
ilman versiopäivitystä
## Periaatteet
1. `ci-engine.yml` on **lukittu rajapinta**. Consumer kutsuu tätä, ei
koskaan providerin scriptejä suoraan. `ci-engine.yml` voi muuttua vain
version vaihtuessa.
2. Consumer omistaa pipeline-logiikan. Provider ei tiedä mitä testejä
ajetaan, missä järjestyksessä tai millä työkaluilla.
3. Providerin versiointi: tag (`v1`, `v2`, ...). Branchit ovat kehitystä
varten, consumerit käyttävät tageja.
## Tausta
Jenkins-versiossa kaikki logiikka oli yhdessä kirjastossa. Gitea Actionsin
reusable workflow -mekanismi pakottaa selkeämpään erotteluun: consumer
kutsuu provideria, mutta omistaa oman pipeline-logiikkansa. Tämä vähentää
providerin kompleksisuutta ja antaa consumerille vapauden päättää mitä
ajetaan.
+81 -50
View File
@@ -1,67 +1,98 @@
# AI Context: Gitea Actions CI -kirjasto # AI Context: Gitea Actions CI -kirjasto
**Updated**: 2026-06-08 **Updated**: 2026-06-12 (POC-vaihe, suunniteltu uudelleenkirjoitus)
## Project Overview ## Project Overview
Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-, raportointi-, deployment- ja test flow -prosessien orkestrointiin. Korvaa `ci-jenkins-library`:n Gitea-natiivilla toteutuksella. Mikropalvelut käyttävät kirjastoa `uses:`-direktiivillä ja konfiguroivat itsensä `ci-flow-values.yaml`:lla. Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-,
raportointi-, deployment- ja test flow -prosessien orkestrointiin. Korvaa
`ci-jenkins-library`:n Gitea-natiivilla toteutuksella. Mikropalvelut
käyttävät kirjastoa `uses:`-direktiivillä.
## Architecture POC on valmis: raporttien julkaisu git-pagesiin ja commit-status linkillä
- 4 reusable workflow'ta: `ci-feature.yml`, `ci-master.yml`, `deploy.yml`, `test.yml` toimii.
- 4 jaettua bash-skriptiä (`scripts/`): `report-status.sh`, `dispatch-workflow.sh`, `push-reports.sh`, `tag-commit.sh`
- Raportit MinIO:ssa (S3 + static web), OIDC-autentikointi Traefikin kautta ## Monorepo: kaksi erillistä kokonaisuutta
- Cross-repo commit traceability Gitea REST API:n kautta
- `report-service/`-moduuli samassa repossa: raporttiskriptit, retention CronJob, index.html-generointi Tämä repo on käytännössä monorepo, jossa on kaksi itsenäistä osaa:
- Normatiivinen arkkitehtuuri: `docs/architecture.md`, perustelut `docs/design-rationale.md`
### 1. Juuri (`gitea-ci-library`)
Provider-kirjasto: reusable workflowt, scriptit, ADRt, dokumentaatio.
Rajapinta: `.gitea/workflows/ci-engine.yml` — ainoa pinta, jota consumerit
kutsuvat.
### 2. `git-pages/` — oma kokonaisuus
Helm-chartti Codeberg git-pagesille. Täysin itsenäinen — oma dokumentaatio,
omat tekniset valinnat, oma design-rationale. Kohdeltava kuten se olisi jo
oma reponsa: kaikki git-pages-spesifi tieto kuuluu `git-pages/docs/`- alle,
ei juuren `docs/`-kansioon.
### Rajapinta juuren ja git-pagesin välillä
Ohut ja yksiselitteinen:
```
scripts/publish-git-pages.sh <report-dir>
→ PATCH tar osoitteeseen PAGES_HOST
→ palauttaa BASE URL:n
git-pages tarjoaa:
- HTTP endpoint (GET/PATCH/PUT)
- retention (automaattinen)
- TLS, BasicAuth (Traefik)
```
Juuri ei tiedä git-pagesin sisäisestä toiminnasta (storage v2, .index,
blob-arkkitehtuuri). Git-pages ei tiedä workflowista, scripteistä tai
provider-logiikasta.
## Architecture (POC-tila)
- **Provider & Consumer -malli**: `ci-engine.yml` on lukittu rajapinta.
ADR 0005.
- **Raporttien hostaus**: git-pages Helm-chartilla (`git-pages/`).
- **Retention**: sidecar samassa podissa, HTTP API localhost:3000,
Gitea API branch-check.
- **Commit-status**: Gitea Actions näyttää automaattisesti. API vain
custom-linkkiin. ADR 0004.
- **Julkaisu**: `publish-git-pages.sh` → PATCH tar git-pagesiin.
## Repository Structure ## Repository Structure
| Path | Purpose | | Path | Purpose |
|---|---| |---|---|
| `.gitea/workflows/` | Reusable workflow -tiedostot (`ci-feature.yml`, `ci-master.yml`, `deploy.yml`, `test.yml`) | | `.gitea/workflows/` | `ci-engine.yml` (ainoa reusable workflow POC-vaiheessa) |
| `scripts/` | Jaetut bash-skriptit | | `scripts/` | `publish-git-pages.sh`, `report-status.sh`, `dispatch-workflow.sh` |
| `report-service/` | Raporttipalvelun koodi (retention, indeksigenerointi) | | **`git-pages/`** | **Oma kokonaisuus: Helm-chartti + docs + retention** |
| `docs/` | Arkkitehtuuri-, vaatimus- ja konfiguraatiodokumentaatio | | `docs/` | Root-tason arkkitehtuuri, ADRt (00010005) |
| `docs/tickets/` | Toteutustiketit (00010012), yksi per feature-branch | | `docs/adr/` | Architecture Decision Records |
| `docs/test-plan/` | TDD-opas: 3-kerrosmalli, kehityslooppi, mock, kontekstikuratointi | | `tests/` | Bats-testit skripteille |
| `docs/test-plan/tdd-guide.md` | Testivetoisen kehityksen menetelmädokumentti | | `.gitea/workflows/ci.yml` | Dogfood — kutsuu `ci-engine.yml`:a |
| `tests/` | Bats-testit skripteille ja workflow-validointi |
| `tests/features/` | Cucumber `.feature`-tiedostot (yksi per tiketti, tägätty @mock/@real/@ticket-NNNN) | **Tarkemmat git-pages-asiat:** `git-pages/docs/` (implementation-notes,
| `tests/helpers/` | Jaettu mock-palvelin (Gitea API + MinIO) | architecture, design-rationale, secrets, tech-stack).
| `.gitea/workflows/ci.yml` | Kirjaston oma CI — dogfood (käyttää itse itseään) |
## Key Technical Decisions ## Key Technical Decisions
- **Vain Gitea.** Ei multi-platform-tukea (GitLab, BitBucket, GitHub) - **Provider & Consumer**: `ci-engine.yml` lukittu rajapinta, muu koodi
- **Ei omaa runtimea.** Reusable workflowt, ei Docker custom actioneita (ellei pakko) vapaasti muutettavissa
- **Konfiguraatio repoon.** `ci-flow-values.yaml` projektin juuressa, infra-asetukset Gitea org secrets/variables - **Vain Gitea, vain reusable workflowt**: ei custom actioneita, ei
- **Vaiheittainen test flow.** Ei rinnakkaista suoritusta — deterministinen, debuggattava multi-platform
- **Raportit MinIO:ssa.** Gitea artifact-järjestelmä ei tue HTML-selailtavuutta - **Raportit git-pagesissa**: HTML selailtavissa, retention automaattinen
- **Docker-rekisterit:** MVP:ssä vain Gitea Packages. Factory/adapter-pattern valmiina Artifactorylle/Nexukselle - **Git-pages omana kokonaisuutena**: voi erottaa omaksi repokseen
- **MVP-scope:** `doNotDowngrade` ei mukana, vain Gitea Packages docker-rekisterinä tulevaisuudessa
## Tech Stack ## Tech Stack (POC)
- **Runtime:** Bash 4.0+, curl 7.0+, jq 1.6+, git 2.30+, MinIO client (`mc`) - **Runtime:** Bash, curl, jq, python3 (retention whiteout)
- **Alusta:** Gitea Actions 1.21+, Gitea act runner 0.2+ - **Alusta:** Gitea Actions, Gitea act runner
- **Integraatiot:** Gitea REST API (`/api/v1/`), MinIO S3 API, SonarQube REST API - **Hostaus:** git-pages 0.9.1 (Codeberg), Traefik, cert-manager
- **Tuetut build-ekosysteemit:** Java/Maven, Java/Gradle, Node.js/npm - **Integraatiot:** Gitea REST API, Gitea Packages
- **Tuetut testikehykset:** Cucumber, JUnit, JaCoCo, Maven Site, custom HTML
## Common Commands ## Common Commands
- Workflow-triggerit: `push` branchiin tai `workflow_dispatch` - Helm-asennus: `helm upgrade --install git-pages ./git-pages -n <ns> -f <values>`
- Skriptien kutsuminen tapahtuu workflow-stepeistä, ei paikallisesti - Julkaisu: `bash scripts/publish-git-pages.sh <report-dir>`
- `ci-flow-values.yaml` validointi: skeema `docs/config-model.md`:ssa - Status: `bash scripts/report-status.sh <state> <desc> <url> <context>`
- Bats-testit: `bats tests/` — ajaa kaikki skripti- ja workflow-testit
- Testit vaativat: `bats` (Bash Automated Testing System), `jq`, `yq`
## Development Process
- TDD: Red-Green-Refactor jokaiselle tiketille. Testit ensin, toteutus vasta kun testi epäonnistuu.
- Ominaisuusbranchit: `feature/NNNN-tiketin-nimi` (esim. `feature/0001-report-status-sh`)
- Yksi tiketti per sessio. Tiketit riippuvuusjärjestyksessä 0001 → 0012.
- Ennen jokaista tikettiä: lataa `docs/test-plan/tdd-guide.md` + tiketin Required context -skillin lisäksi
- Dogfood: `.gitea/workflows/ci.yml` — kirjaston oma CI käyttää omia reusable workflow'itaan
## What NOT to Do ## What NOT to Do
- Älä lisää tukea muille Git-alustoille (GitLab, BitBucket, GitHub) - Älä lisää tukea muille Git-alustoille
- Älä lisää Docker custom actioneita ilman pakottavaa syytä - Älä lisää Docker custom actioneita ilman pakottavaa syytä
- Älä siirrä konfiguraatiota pois reposita (`ci-flow-values.yaml`) - Älä kirjoita git-pages-spesifiä tietoa juuren `docs/`-kansioon —
- Älä lisää rinnakkaista test flow -suoritusta kuuluu `git-pages/docs/`-alle
- Älä lisää ulkoista orkestraattoria — Gitea REST API riittää - Älä POSTaa commit-status APIin jokaiselle vaiheelle — natiivi riittää
- Älä käytä `repository_dispatch`-webhookia test flow -ketjutukseen
@@ -0,0 +1,163 @@
# Miksi oma CI-kirjastoprojekti — ei perus-Gitea-tiedostoja
> Liittyy: [architecture.md](../architecture.md), [config-model.md](../config-model.md), [design-rationale.md](../design-rationale.md)
---
## Taso 1: Miksi kirjastoprojekti, ei repo-kohtaisia workflow'ta
Kirjasto (`gitea-ci-library`) on oma projektinsa, koska sen tarjoamat palvelut
ovat **cross-cutting** — samat kaikissa mikropalveluissa. Ilman kirjastoa
jokainen repo kopioisi identtisen CI-logiikan.
### Mitä kirjasto tarjoaa "ilmaiseksi"
| Palvelu | Tiedosto/skripti | Rivimäärä | Jos 20 projektia kopioisi |
|---|---|---|---|
| Commit-statusraportointi | `report-status.sh` | ~45 | 900 riviä |
| Workflow-ketjutus + pollaus | `dispatch-workflow.sh` | ~80 | 1 600 riviä |
| HTML-raporttien MinIO-pushaus | `push-reports.sh` | ~50 | 1 000 riviä |
| GitOps-deploy (YAML-muokkaus + commit + cross-repo-trace) | `deploy.yml` | ~100 | 2 000 riviä |
| Test flow -ketjutus (dispatch + poll + cross-repo-status) | `ci-master.yml` + `test.yml` | ~150 | 3 000 riviä |
| Feature-branch CI (build-ekosysteemin valinta, concurrency) | `ci-feature.yml` | ~80 | 1 600 riviä |
**Yhteensä ilman kirjastoa: ~10 000 riviä identtistä CI-koodia 20 repossa.**
Yksi bugikorjaus (esim. `push-reports.sh`:n retry-logiikka) = 20 PR:ää, 20 review'tä, 20 mergeä.
Osa jää tekemättä → eri projektit käyttäytyvät eri tavalla → CI-epäluotettavuus leviää.
**Kirjaston kanssa:** Yksi PR, yksi review, yksi merge. Kaikki projektit saavat korjauksen
`uses: org/gitea-ci-library/.gitea/workflows/ci-feature.yml@v1`-viittauksen kautta.
### Miksi juuri nämä asiat ansaitsevat oman projektin
Nämä kolme huolta ovat **pitkälle mietitty kokonaisuus**, jonka toteuttaminen
repo-kohtaisesti olisi massiivista tautologiaa:
1. **Dynaaminen test plan K8s-ympäristössä** — Deploy developmentiin → integraatiotestit
→ deploy stagingiin → e2e-testit. Jokainen steppi dispatchaa toisen repon workflow'n,
pollaa sen valmistumista, ja raportoi cross-repo-statuksen takaisin root-committiin.
2. **HTML-testiraporttien tallennus** — Cucumber, JaCoCo, Maven Site. Pushataan MinIO:hon
deterministisellä URL:llä. URL linkitetään commit-statusviestiin. Retention CronJob
siivoaa vanhat. Tämä on kokonainen alijärjestelmä, ei yksi steppi.
3. **GitOps-deployment** — Päivitä YAML-arvo Helm-repossa, committaa, pushaa,
raportoi cross-repo-status molempiin suuntiin. Sama mekaniikka jokaisessa
mikropalvelussa — vain `projectFolder` ja `fileName` vaihtelee.
Näiden toteuttaminen per repo olisi kuin jokainen mikropalvelu toteuttaisi oman
tietokantakerroksensa sen sijaan että käyttäisi jaettua kirjastoa.
---
## Taso 2: Miksi `ci-flow-values.yaml` — Gitean natiivikonfiguraatio ei riitä
Gitean natiivimekanismit hoitavat salaisuudet ja infra-arvot.
`ci-flow-values.yaml` hoitaa rakenteisen, versioidun, projekti-spesifin konfiguraation.
Ne täydentävät toisiaan, eivät kilpaile.
### Mitä Gitea tarjoaa natiivisti
| Mekanismi | Tyyppi | Sijainti | Rajoite |
|-----------|--------|----------|---------|
| `workflow_call` `inputs` | Skalaari (`string`, `boolean`, `number`) | Kutsuvan workflow'n parametrit | **Ei tue listoja, objekteja, nested-rakenteita** |
| Org/repo secrets | `string` (salattu) | Gitea UI | Flat key-value, ei rakennetta |
| Org/repo variables | `string` | Gitea UI | Flat key-value, ei rakennetta |
| `.gitea/workflows/*.yml` | YAML | Projektin repo | Täysi workflow-logiikka, ei dataa |
### Ongelma 1: skalaarityypit eivät kanna rakenteista konfiguraatiota
`ci-flow-values.yaml` sisältää mm:
```yaml
test-flow:
- deploy: development
wait: true
- test:
name: "integration fast"
repo: tests/integration
tags: "@temperature and not @slow"
- test:
name: e2e
repo: tests/e2e
```
Tämän ilmaiseminen Gitea `inputs`:na vaatisi jokaisen kentän erillisenä parametrina:
```yaml
with:
test_flow_0_deploy: "development"
test_flow_1_test_name: "integration fast"
test_flow_1_test_repo: "tests/integration"
test_flow_1_test_tags: "@temperature and not @slow"
# …hajoaa heti kun array pituus vaihtelee
```
**Gitean `inputs` ei tue dynaamisen pituisia listoja.** Projekteilla on 0n test flow -steppiä.
### Ongelma 2: konfiguraatio kuuluu repoon, ei Gitean UI:hin
Jos kaikki asetukset vietäisiin Gitea org/repo variables -mekanismiin:
- **Versionhallinta katoaa.** Konfiguraation historiaa ei voi tarkastella (`git log`), reviewata (PR), tai rollbackata (`git revert`).
- **Konfiguraatio irtoaa koodista.** Kun koodi muuttuu ja tarvitsee uuden konfiguraatioavaimen, muutos tehdään Gitean UI:ssa — eri ajankohtana kuin koodimuutos. CI hajoaa välissä.
- **Ei PR-prosessia konfiguraatiolle.** Gitean variables/secrets eivät kulje review'n läpi.
- **Siirtäminen ympäristöjen välillä vaikeutuu.** Staging- ja production-Gitealla on eri variablet. Ei `git cherry-pick` -mekanismia.
### Miksi kaksi tiedostoa, ei yksi
Eli miksi projektilla on **sekä** `.gitea/workflows/ci.yml` **että** `ci-flow-values.yaml`?
```
.gitea/workflows/ci.yml → MITEN ajetaan (workflow-valinta + parametrit)
ci-flow-values.yaml → MITÄ ajetaan (projektin data)
```
| `.gitea/workflows/ci.yml` | `ci-flow-values.yaml` |
|---------------------------|----------------------|
| Sama kaikissa projekteissa | Uniikki per projekti |
| Asuu projektin repossa (ohut kutsuja) | Asuu projektin repossa |
| Kopioitavissa, mutta EI sisällä logiikkaa | Versioituu projektin mukana |
| Välittää parametrit `with:` → kirjastolle | Sisältää: docker-nimi, sonar-projekti, test-flow-array |
### Työnjako Gitea natiivin ja `ci-flow-values.yaml`:n välillä
| Vaatimus | Gitea natiivi | `ci-flow-values.yaml` |
|----------|--------------|----------------------|
| Nested-rakenteet (listat, objekit) | Ei (`inputs` = skalaari) | Kyllä (YAML) |
| Versionhallinta + PR-review | Ei (UI-pohjainen) | Kyllä (`git`) |
| Projekti omistaa konffinsa | Osittain (repo variables) | Kyllä |
| Infra-tason salaisuudet | Kyllä (org secrets) | **Ei** |
| Jaetut infra-arvot | Kyllä (org variables) | **Ei** |
---
## Yhteenveto: moottori + consumer-tiedostot
```
gitea-ci-library (MOOTTORI) Jokainen consumer-repo
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Reusable workflowt │ │ .gitea/workflows/ci.yml │
│ ci-feature.yml │◄─────────│ uses: .../ci-feature.yml@v1 │
│ ci-master.yml │ kutsuu │ │
│ deploy.yml │ │ ci-flow-values.yaml │
│ test.yml │◄─────────│ build.ecosystem: maven │
│ │ lukee │ docker.imageName: ... │
│ Jaetut skriptit │ │ test-flow: [...] │
│ report-status.sh │ └──────────────────────────────┘
│ dispatch-workflow.sh │
│ push-reports.sh │ Gitea org
│ tag-commit.sh │ ┌──────────────────────────────┐
└──────────────────────────────┘ │ secrets: tokenit, salasanat │
│ variables: MinIO URL, Sonar │
Kirjasto EI sisällä ci.yml:ää └──────────────────────────────┘
consumerille — se on moottori,
joka syö consumerin tuomia tiedostoja. ci.yml on kopioitavissa,
identtinen kaikilla consumereilla.
```
Salaisuudet (tokenit, salasanat) ja jaetut infra-arvot (MinIO URL, SonarQube URL)
kuuluvat Gitean natiivimekanismeihin (org secrets/variables).
CI-logiikka kuuluu kirjastoon (moottori).
Projektikohtainen data (`ci-flow-values.yaml`) ja ohut kutsuja (`ci.yml`) kuuluvat consumer-repoon.
+32 -196
View File
@@ -1,216 +1,52 @@
# Architecture — Gitea Actions CI -kirjasto # Architecture — Gitea Actions CI -kirjasto
> Hub-dokumentti. Komponentit, niiden roolit ja rajapinnat. Yksityiskohtaiset kuvaukset linkitetyissä detail-dokumenteissa. > ⚠️ POC-vaihe. Tämä dokumentti kuvaa suunniteltua arkkitehtuuria. Toteutus
> on edelleen kehitysvaiheessa (`ci-engine.yml` on ainoa reusable workflow).
> Odota uudelleenkirjoitusta ennen kuin luotat tähän dokumenttiin.
> >
> Tämä dokumentti on **normatiivinen** — se määrittelee mikä on rakennettava. `design-rationale.md` kertoo miksi. > Normatiivinen lähde: ADR 0004, ADR 0005, `docs/design-rationale.md`.
--- ---
## Yleiskuvaus ## Yleiskuvaus
Kirjasto on kokoelma **Gitea Actions reusable workflow** -tiedostoja, jotka orkestroivat mikropalveluiden build-, testaus-, raportointi-, deployment- ja test flow -prosessit. Projekti käyttää kirjastoa `uses:`-direktiivillä `.gitea/workflows/*.yml`-tiedostossaan ja määrittelee konfigurationsa `ci-flow-values.yaml`-tiedostossa. Kirjasto on kokoelma **Gitea Actions reusable workflow** -tiedostoja, jotka
orkestroivat mikropalveluiden build-, testaus-, raportointi-, deployment- ja
test flow -prosessit. Projekti käyttää kirjastoa `uses:`-direktiivillä.
Kirjasto on Gitea-spesifi. Se hyödyntää Gitean REST API:a commit-statusraportointiin, workflow-dispatchiin ja run-pollaukseen. Raportit tallennetaan MinIO:hon, josta ne ovat selailtavissa HTML-muodossa. Kirjasto on Gitea-spesifi. Raportit hallinnoidaan git-pages Helm-chartilla
(`git-pages/`).
--- ## Provider & Consumer -malli
## Komponentit | Rooli | Kuvaus |
|-------|--------|
| **Provider** | `gitea-ci-library` — tarjoaa `ci-engine.yml`:n (lukittu rajapinta) sekä scriptit |
| **Consumer** | Mikropalveluprojekti — kutsuu `uses:`-direktiivillä, omistaa pipeline-logiikan |
### Reusable workflowt (4 kpl) Tarkemmin: ADR 0005.
| Workflow | Tiedosto | Rooli | ## Komponentit (POC)
|----------|----------|-------|
| **Feature flow** | `ci-feature.yml` | Testaa feature-branchin, generoi raportit, raportoi statuksen committiin. Ei buildaa konttia. |
| **Master flow** | `ci-master.yml` | Testaa, buildaa kontin, pushaa rekisteriin, tagittaa commitin, ketjuttaa test flow'n. |
| **Deployment flow** | `deploy.yml` | GitOps-deployment: päivittää YAML-arvoa, committaa, pushaa, raportoi cross-repo-statuksen. |
| **Test flow** | `test.yml` | Vastaanottaa dispatchin, ajaa testit, generoi raportit, raportoi statuksen. |
> Yksityiskohtaiset kuvaukset: [workflows.md](workflows.md) | Komponentti | Tila |
|-------------|------|
| `ci-engine.yml` | Toimii POC-tasolla. Ainoa reusable workflow. |
| `publish-git-pages.sh` | Toimii. PATCH tar git-pagesiin. |
| `report-status.sh` | Toimii. POSTaa commit-status (vain custom-linkkiin). |
| `dispatch-workflow.sh` | Suunniteltu, ei toteutettu POCissa. |
| `git-pages/` | Helm-chartti raporttien hostaukseen. Oma kokonaisuus, tarkemmin: `git-pages/docs/`. |
### Jaetut skriptit ## Ulkoiset palvelut
| Skripti | Rooli |
|---------|-------|
| **`report-status.sh`** | POSTaa build-statuksen Gitea `/api/v1/repos/{owner}/{repo}/statuses/{sha}` |
| **`dispatch-workflow.sh`** | Dispatchaa workflow'n toisessa repossa ja pollaa sen valmistumista |
| **`push-reports.sh`** | Puskaa testiraportit MinIO:hon ja generoi URL:n |
| **`tag-commit.sh`** | Tagittaa commitin versiolla Gitea REST API:n kautta |
> Yksityiskohtaiset kuvaukset: [shared-scripts.md](shared-scripts.md)
### Konfiguraatio
| Artefakti | Sijainti | Rooli |
|-----------|----------|-------|
| **`ci-flow-values.yaml`** | Projektin repo | Projektikohtainen konfiguraatio (docker, sonar, deployment, test-flow) |
| **Gitea org secrets** | Gitea organization | Tokenit ja salasanat |
| **Gitea org variables** | Gitea organization | Infra-tason asetukset (MinIO URL, SonarQube URL) |
> Yksityiskohtaiset kuvaukset: [config-model.md](config-model.md)
### Ulkoiset palvelut
| Palvelu | Rooli | | Palvelu | Rooli |
|---------|-------| |---------|-------|
| **Gitea REST API** | Commit-statusraportointi, workflow-dispatch, run-pollaus, taggaus | | **Gitea REST API** | Commit-status, workflow-dispatch, run-pollaus |
| **Gitea Packages** | Docker-imagen ja NPM-paketin säilytys | | **Gitea Packages** | Docker-imagen säilytys |
| **Gitea act runner** | Suorittaa workflowt. Konteista, DinD:stä ja label-järjestelmästä: [runner.md](runner.md) | | **git-pages** | Raporttien hostaus |
| **MinIO** | Testiraporttien tallennus ja staattinen web-hosting | | **SonarQube** | Koodin laadun analyysi (suunniteltu) |
| **SonarQube** | Koodin laadun analyysi ja quality gate |
--- ## Arkkitehtuuriset rajoitteet
## Järjestelmäkaavio - `ci-engine.yml` on ainoa consumerin kutsuma rajapinta (ADR 0005)
- Gitea Actionsin natiivi commit-status on ensisijainen (ADR 0004)
```mermaid - Raportit ovat julkisia URL:lla (osoite tunnettava)
%%{init: {'theme': 'base', 'flowchart': {'arrowheadScale': 2}}}%%
flowchart TB
subgraph PROJ["Projektin repo"]
WF[".gitea/workflows/ci.yml
uses: gitea-ci-library/ci-master@v1"]
CONF["ci-flow-values.yaml"]
end
subgraph LIB["gitea-ci-library (reusable workflowt)"]
FEATURE["ci-feature.yml"]
MASTER["ci-master.yml"]
DEPLOY["deploy.yml"]
TEST["test.yml"]
end
subgraph SCRIPTS["Jaetut skriptit"]
REPORT["report-status.sh"]
DISPATCH["dispatch-workflow.sh"]
PUSHREP["push-reports.sh"]
TAG["tag-commit.sh"]
end
subgraph EXT["Ulkoiset palvelut"]
GITEA["Gitea API
+ Packages"]
MINIO["MinIO
(S3 + static web)"]
SONAR["SonarQube"]
end
subgraph HELM["Helm-repo"]
VALUES["values-{env}.yaml"]
end
subgraph TESTREPO["Testi-repo"]
TESTWF[".gitea/workflows/test.yml"]
end
WF -- "kutsuu" --> MASTER
WF -- "lukee" --> CONF
MASTER -- "käyttää" --> SCRIPTS
DEPLOY -- "käyttää" --> SCRIPTS
TEST -- "käyttää" --> SCRIPTS
REPORT -- "POST status" --> GITEA
DISPATCH -- "dispatch + poll" --> GITEA
PUSHREP -- "S3 upload" --> MINIO
TAG -- "POST tag" --> GITEA
MASTER -- "quality gate" --> SONAR
DEPLOY -- "muokkaa" --> VALUES
DEPLOY -- "commit + push" --> HELM
DISPATCH -- "dispatch" --> TESTWF
TESTWF -- "raportoi" --> GITEA
MINIO -- "URL commit-statusviestiin" --> GITEA
style PROJ fill:#1e3a5f,color:#f9fafb,stroke:#64748b
style LIB fill:#1f2937,color:#f9fafb,stroke:#64748b
style SCRIPTS fill:#064e3b,color:#f9fafb,stroke:#64748b
style EXT fill:#5c1a1a,color:#f9fafb,stroke:#64748b
style HELM fill:#4a1a6b,color:#f9fafb,stroke:#64748b
style TESTREPO fill:#4a1a6b,color:#f9fafb,stroke:#64748b
style WF fill:#2563eb,color:#ffffff
style CONF fill:#f59e0b,color:#111827
style FEATURE fill:#2563eb,color:#ffffff
style MASTER fill:#2563eb,color:#ffffff
style DEPLOY fill:#2563eb,color:#ffffff
style TEST fill:#2563eb,color:#ffffff
style REPORT fill:#059669,color:#ffffff
style DISPATCH fill:#059669,color:#ffffff
style PUSHREP fill:#059669,color:#ffffff
style TAG fill:#059669,color:#ffffff
style GITEA fill:#dc2626,color:#ffffff
style MINIO fill:#dc2626,color:#ffffff
style SONAR fill:#dc2626,color:#ffffff
style VALUES fill:#9333ea,color:#ffffff
style TESTWF fill:#9333ea,color:#ffffff
linkStyle default stroke:#9ca3af,stroke-width:3px
```
---
## Tietovuot
### 1. Commit-statusraportointi
```
Workflow-steppi → report-status.sh → POST Gitea API → commitin status päivittyy
↘ URL → raporttiin / buildiin
```
Jokainen vaihe (test, build, push) POSTaa oman statusviestinsä uniikilla `key`-arvolla. Samaan committiin kertyy useita rinnakkaisia statuksia.
### 2. Cross-repo traceability
```
Mikropalvelu (root-build) → Helm (deployment) → Testi (integraatio)
↓ ↓ ↓
Status omaan committiin Status omaan committiin Status omaan committiin
↑ ↑ ↑
Status root-committiin ← Status root-committiin ← Status root-committiin
(deployattu, testattu) (mistä kontti tuli) (mitä testattiin)
```
Root-build-viite kulkee `workflow_dispatch`in `inputs`-parametrina koko ketjun läpi.
### 3. Raporttien URL-generointi
```
push-reports.sh → mc cp ./reports/ minio/bucket/{repo}/{commit_short}/{report}/
→ URL = {MINIO_BASE}/{repo}/{commit_short}/{report}/index.html
→ report-status.sh POSTaa URL:n commit-statusviestiin
```
### 4. Test flow -ketjutus
```
ci-master.yml → test-flow-taulukko (ci-flow-values.yaml)
→ for each step:
dispatch-workflow.sh {test-repo} {inputs}
poll Gitea API run status
success? → next step
failure? → stop
```
---
## Laajennuspisteet
| Piste | Mekanismi | Tila |
|-------|-----------|------|
| **Docker-rekisterit** | Factory/adapter — `ci-flow-values.yaml``docker.type` → valitsee pusherin | MVP: Gitea Packages. Artifactory, Nexus myöhemmin |
| **Testikehykset** | `push-reports.sh` tukee mitä tahansa hakemistoa → MinIO | Cucumber, JUnit, JaCoCo, Maven Site, custom |
| **Build-ekosysteemit** | Workflow'n `container:` määrittelee projektin itse | Maven, Gradle, npm, mikä tahansa |
| **SonarQube** | Rajapinta vaihdettavissa — parempi kuin pollaus tutkitaan | Quality gate -tarkistus |
---
## Top-level rajoitteet
- Kaikki integraatio Gitea REST API:n kautta — ei suoria tietokantakytkentöjä, ei jaettua filesysteemiä
- Workflowt eivät jaa tilaa keskenään paitsi `workflow_dispatch`in `inputs`-parametrien kautta
- Raporttien URL on deterministinen: `{MINIO_BASE}/{repo_slug}/{commit_short}/{report}/`
- Cross-repo-statusraportoinnissa root-build on aina se mikropalvelun commit, josta ketju käynnistyi
## Repo-jako
| Repo | Sisältö |
|------|---------|
| **`gitea-ci-library`** | Reusable workflowt, jaetut skriptit, `report-service/`-moduuli (raporttiskriptit, retention CronJob, index.html-generointi) |
| **Deployment / infra -repo** (olemassa oleva) | MinIO Kubernetes-manifestit, Traefik OIDC middleware, ConfigMap, ingress |
`gitea-ci-library/report-service/` on oma moduulinsa samassa repossa — ei erillistä repositoriota, mutta selkeä sisäinen raja workflow-skriptien ja raporttipalvelun koodin välillä. Moduuli sisältää `docs/`-hakemiston deploy-esimerkeillä, mutta varsinainen deployment hoidetaan GitOps/Helm-repon kautta kuten muutkin palvelut.
+62
View File
@@ -0,0 +1,62 @@
# 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
## 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ä)
+23 -32
View File
@@ -1,6 +1,14 @@
# Konfiguraatiomalli — `ci-flow-values.yaml` # Konfiguraatiomalli — `ci-flow-values.yaml`
> Kuuluu arkkitehtuuriin: [architecture.md](architecture.md). Tämä dokumentti määrittelee projektikohtaisen konfiguraation skeeman, `isContainerBuild()`-mekanismin ja version check -skriptin. > ⚠️ **POC-vaihe.** Tämä dokumentti on peritty Jenkins-versiosta ja sisältää
> Docker-spesifistä legacyä (isContainerBuild, Docker-labelit). POC osoitti,
> että provider on agnostinen consumerin build-ekosysteemille.
>
> Uusi ajattelu: `isContainerBuild()` → `isArtifactBuild()`. Provider ei
> tiedä eikä tarvitse tietää, buildaako consumer kontin, JARin, npm-paketin
> vai mitään muuta. Dokumentti odottaa uudelleenkirjoitusta.
>
> Normatiivinen lähde: `docs/design-rationale.md`, ADR 0005.
--- ---
@@ -116,11 +124,20 @@ Array testi-steppejä. Jokainen steppi on joko `deploy` tai `test`.
--- ---
## `isContainerBuild()`-mekanismi ## `isArtifactBuild()`-mekanismi (POC: suunniteltu, ei toteutettu)
**Ongelma:** Samaa committia vasten voidaan ajaa master-workflow useita kertoja. On mieletöntä buildata kontti uudestaan, koska commit on jo tagätty versiolla ja kontti on olemassa rekisterissä. Uudelleenbuildaus aiheuttaa versiokonflikteja ja tuhlaa CI-aikaa. > **Jenkins-legacy:** Jenkins-versiossa tämä oli `isContainerBuild()`. POC
> osoitti, että provider ei tiedä eikä tarvitse tietää, buildaako consumer
> kontin, JARin vai npm-paketin. Siksi `isContainerBuild()` →
> `isArtifactBuild()`.
**Ratkaisu:** Workflow'n alussa tarkistetaan, onko tälle commitille jo olemassa versiotagi: **Ongelma:** Samaa committia vasten voidaan ajaa master-workflow useita
kertoja. On mieletöntä buildata artifakti uudestaan, koska commit on jo
tagätty versiolla ja artifakti on olemassa rekisterissä. Uudelleenbuildaus
aiheuttaa versiokonflikteja ja tuhlaa CI-aikaa.
**Ratkaisu:** Workflow'n alussa tarkistetaan, onko tälle commitille jo
olemassa versiotagi.
```bash ```bash
# is-container-built.sh # is-container-built.sh
@@ -156,34 +173,8 @@ jobs:
**Miksi tämä on välttämätöntä:** **Miksi tämä on välttämätöntä:**
- Estää versiokonfliktit: `1.2.3.42` ei voi olla kahdesti - Estää versiokonfliktit: `1.2.3.42` ei voi olla kahdesti
- Säästää CI-aikaa: kontin buildaus on hitain vaihe - Säästää CI-aikaa: artifaktin buildaus on hitain vaihe
- Pitää commitin ja kontin välisen suhteen yksiselitteisenä: `git tag` kertoo suoraan mikä versio vastaa tätä committia - Pitää commitin ja artifaktin välisen suhteen yksiselitteisenä: `git tag` kertoo suoraan mikä versio vastaa tätä committia
---
## Docker-labelit
Jenkins-versiossa konttiin injektoitiin metadataa Docker-labelien kautta. Sama käytäntö jatkuu:
```bash
docker build \
--label "git.commit=${GITHUB_SHA::8}" \
--label "git.commitBy=${GITHUB_ACTOR}" \
--label "build.date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--label "build.run=${GITHUB_RUN_NUMBER}" \
--label "version=${DOCKER_VERSION}" \
-t ${DOCKER_TAG} .
```
| Label | Arvo | Lähde |
|-------|------|-------|
| `git.commit` | 8-merkkinen hash | `GITHUB_SHA::8` |
| `git.commitBy` | Ajon laukaisija | `GITHUB_ACTOR` |
| `build.date` | ISO 8601 UTC | `date -u` |
| `build.run` | Ajon järjestysnumero | `GITHUB_RUN_NUMBER` |
| `version` | Kontin versio | `1.2.3.42` |
Näillä labeleilla kontista näkee suoraan: kuka buildasi, milloin, mistä commitista, millä versiolla. Vianjäljitys kontista koodiin on suora.
--- ---
+44 -24
View File
@@ -12,21 +12,45 @@ Organisaatiolla on tuotannossa Jenkins-pohjainen CI-järjestelmä (`ci-jenkins-l
Jenkins on kuitenkin raskas ylläpitää Kubernetesissa, ja organisaatio on siirtymässä Giteaan. Tavoitteena on **sama toiminnallisuus, pienemmällä ylläpitotaakalla**, hyödyntäen Gitea Actionsin natiiveja ominaisuuksia. Jenkins on kuitenkin raskas ylläpitää Kubernetesissa, ja organisaatio on siirtymässä Giteaan. Tavoitteena on **sama toiminnallisuus, pienemmällä ylläpitotaakalla**, hyödyntäen Gitea Actionsin natiiveja ominaisuuksia.
Kirjasto ei ole Jenkins-migraatiotyökalu. Se on Gitea Actions -natiivi uudelleensuunnittelu, joka säilyttää Jenkins-version todistetut patternit mutta hylkää ne osat, jotka olivat sidottuja Jenkinsin arkkitehtuuriin. Kirjasto ei ole Jenkins-migraatiotyökalu. Se on Gitea Actions -natiivi
uudelleensuunnittelu, joka säilyttää Jenkins-version todistetut patternit
mutta hylkää ne osat, jotka olivat sidottuja Jenkinsin arkkitehtuuriin.
Gitea Actionsin ja Gitean natiiveja ominaisuuksia hyödynnetään aina kun
mahdollista — uutta kilpailevaa toteutusta ei kirjoiteta, jos toimiva
ratkaisu on jo olemassa.
--- ---
## Suunnitteluperiaatteet ## Suunnitteluperiaatteet
### 1. Git-commit on universaali statusnäkymä ### 1. Hyödynnä natiivia
Gitea Actionsin ja Gitean natiiveja ominaisuuksia käytetään aina kun ne
riittävät. Uutta kilpailevaa toteutusta ei kirjoiteta, jos toimiva ratkaisu
on jo olemassa.
**Miksi:** Oma toteutus on aina ylläpidettävä, testattava ja
dokumentoitava. Natiivi ominaisuus tulee ilmaiseksi, kehittyy alustan
mukana ja on käyttäjälle tuttu.
**Esimerkkejä:**
- Gitea Actions näyttää jobien statuksen automaattisesti — omaa
commit-status API -kutsua ei tarvita jokaiselle vaiheelle
- Gitea organization secrets/variables korvaa erillisen credential-hallinnan
- Reusable workflow -mekanismi korvaa custom action -runtimen
### 2. Git-commit on universaali statusnäkymä
Buildin jokainen vaihe raportoi tilansa Git-committiin. Kehittäjä näkee yhdellä silmäyksellä, missä vaiheessa build on — ei tarvitse navigoida CI-järjestelmän UI:hun. Buildin jokainen vaihe raportoi tilansa Git-committiin. Kehittäjä näkee yhdellä silmäyksellä, missä vaiheessa build on — ei tarvitse navigoida CI-järjestelmän UI:hun.
**Miksi:** Jenkins-versio osoitti, että commit-statusviestit poistavat tarpeen CI-dashboardille. Kehittäjä työskentelee Gitissä, joten status kuuluu Gitiin. Statusviestien `url`-kenttä linkittää suoraan raportteihin — Cucumber-tulokset, SonarQube-tulokset, Docker-rekisteri — ilman että URL tarvitsee etsiä erikseen. **Miksi:** Jenkins-versio osoitti, että commit-statusviestit poistavat tarpeen CI-dashboardille. Kehittäjä työskentelee Gitissä, joten status kuuluu Gitiin. Statusviestien `url`-kenttä linkittää suoraan raportteihin — Cucumber-tulokset, SonarQube-tulokset, Docker-rekisteri — ilman että URL tarvitsee etsiä erikseen.
**Mitä tarkoittaa käytännössä:** Jokainen `testBegin/End`, `buildBegin/End`, `pushBegin/End`-vaihe POSTaa Gitean REST APIin (`/api/v1/repos/{owner}/{repo}/statuses/{sha}`). Uniikki `key` per vaihe estää duplikaatit ja mahdollistaa rinnakkaiset statukset samassa commitissa. **Mitä tarkoittaa käytännössä:** Gitea Actions näyttää jobien tilan
(checkmark/risti/spinner) commit-näkymässä automaattisesti. API:a
(`/api/v1/repos/{owner}/{repo}/statuses/{sha}`) käytetään vain
custom-raporttilinkin välittämiseen. ADR 0004.
### 2. Reusable workflow — ei omaa runtimea ### 3. Reusable workflow — ei omaa runtimea
Kirjasto jaetaan Gitea Actionsin reusable workflow -mekanismilla. Ei Docker-pohjaisia custom actioneita, ei erillistä ajonaikaista palvelinta. Kirjasto jaetaan Gitea Actionsin reusable workflow -mekanismilla. Ei Docker-pohjaisia custom actioneita, ei erillistä ajonaikaista palvelinta.
@@ -34,15 +58,17 @@ Kirjasto jaetaan Gitea Actionsin reusable workflow -mekanismilla. Ei Docker-pohj
**Mitä tämä ei ole:** Tämä ei ole monorepo-työkalu, joka asennetaan projekteihin. Tämä on joukko `.gitea/workflows/`-tiedostoja, joihin mikropalvelut viittaavat `uses:`-direktiivillä. **Mitä tämä ei ole:** Tämä ei ole monorepo-työkalu, joka asennetaan projekteihin. Tämä on joukko `.gitea/workflows/`-tiedostoja, joihin mikropalvelut viittaavat `uses:`-direktiivillä.
### 3. Konfiguraatio kuuluu repoon ### 4. Konfiguraatio kuuluu repoon
Projektikohtainen konfiguraatio (`ci-flow-values.yaml`-tyyppinen tiedosto) asuu mikropalvelun omassa repossa. Reusable workflow lukee sen, ei toisinpäin. Projektikohtainen konfiguraatio (`ci-flow-values.yaml`-tyyppinen tiedosto) asuu mikropalvelun omassa repossa. Reusable workflow lukee sen, ei toisinpäin.
**Miksi:** Mikropalvelun kehittäjä omistaa buildinsa. Hän tietää mitä Dockefileä käytetään, mitä SonarQube-projektia, mitä testi-steppejä tarvitaan. Jos konfiguraatio hajautetaan useaan repoon, muutokset vaativat koordinaatiota, ja yhden totuuden lähteen periaate rikkoutuu. **Miksi:** Mikropalvelun kehittäjä omistaa buildinsa. Hän tietää mitä Dockefileä käytetään, mitä SonarQube-projektia, mitä testi-steppejä tarvitaan. Jos konfiguraatio hajautetaan useaan repoon, muutokset vaativat koordinaatiota, ja yhden totuuden lähteen periaate rikkoutuu.
**Poikkeus:** Infra-tason asetukset (MinIO-URL, Gitea-instanssin URL) ovat organisaatiotasolla Gitean organization secrets/variables -mekanismissa. Ne eivät ole repokohtaisia, koska DNS voi osoittaa eri paikkaan kuin repo, ja usean repossa toistuva sama arvo on ylläpitoriski. **Poikkeus:** Infra-tason asetukset (git-pages host, Gitea-instanssin URL)
ovat organisaatiotasolla Gitean organization secrets/variables
-mekanismissa. Ne eivät ole repokohtaisia.
### 4. Deterministinen testigraafi, vaiheittainen suoritus ### 5. Deterministinen testigraafi, vaiheittainen suoritus
Test flow on tunnettu ennen buildin alkua, ja testit ajetaan yksi kerrallaan. Jos steppi epäonnistuu, koko flow pysähtyy. Test flow on tunnettu ennen buildin alkua, ja testit ajetaan yksi kerrallaan. Jos steppi epäonnistuu, koko flow pysähtyy.
@@ -50,13 +76,18 @@ Test flow on tunnettu ennen buildin alkua, ja testit ajetaan yksi kerrallaan. Jo
**Miten:** Orkestroiva workflow käyttää Gitea REST API:a workflow-dispatchiin ja pollaa ajettavan workflow'n tilaa synkronisesti (`GET /api/v1/repos/{owner}/{repo}/actions/runs/{id}`). Tämä vastaa Jenkinsin `buildJob()`-kutsun semantiikkaa, mutta toteutetaan curl + pollaus -silmukalla. **Miten:** Orkestroiva workflow käyttää Gitea REST API:a workflow-dispatchiin ja pollaa ajettavan workflow'n tilaa synkronisesti (`GET /api/v1/repos/{owner}/{repo}/actions/runs/{id}`). Tämä vastaa Jenkinsin `buildJob()`-kutsun semantiikkaa, mutta toteutetaan curl + pollaus -silmukalla.
### 5. Raportit ovat selailtavia URL:n takana ### 6. Raporttien hallinta erillisellä palvelulla
Testiraportit (Cucumber HTML, Jacoco HTML, JUnit XML) viedään MinIO:hon, jonka staattinen web-hosting renderöi ne selaimessa. URL linkitetään Git-committiin. Raporttien selailtavuudesta ja elinkaaresta vastaa erillinen palvelu, joka
asennetaan git-pages Helm-chartilla. Raportit ovat julkisia URL:lla
(osoite tunnettava). URL linkitetään Git-committiin.
**Miksi:** Jenkins-versiossa linkki Cucumber-raporttiin oli kriittinen feature — kehittäjä klikkasi commitin statusviestistä ja näki heti mitkä testit epäonnistuivat. Gitea Actionsin sisäänrakennettu artifact-järjestelmä ei tue HTML-selailtavuutta (vain ZIP-lataus). MinIO täyttää tämän aukon: se on kevyt, Kubernetes-natiivi, ja sen S3 API on standardi. URL-rakenne `{BASE}/{repo_slug}/{commit_short}/{report_type}/` on ennustettavissa ilman erillistä URL-generaattoria. **Miksi:** Jenkins-versiossa linkki Cucumber-raporttiin oli kriittinen
feature. Gitea Actionsin artifact-järjestelmä ei tue HTML-selailtavuutta
(vain ZIP-lataus). Erillinen palvelu mahdollistaa hallitun retention ja
pääsyn ilman CI-alustan rajoitteita.
### 6. Yksi CI-alusta, yksi integraatiopiste ### 7. Yksi CI-alusta, yksi integraatiopiste
Kirjasto tukee vain Giteaa. Ei GitLab-, BitBucket- tai GitHub-abstraktioita. Kirjasto tukee vain Giteaa. Ei GitLab-, BitBucket- tai GitHub-abstraktioita.
@@ -64,7 +95,7 @@ Kirjasto tukee vain Giteaa. Ei GitLab-, BitBucket- tai GitHub-abstraktioita.
**Mitä tarkoittaa tulevaisuudessa:** Jos toinen alusta tulee ajankohtaiseksi, Gitea-versiota käytetään joko pohjana redesignille tai mallina erilliselle toteutukselle. Rajapintoja ei suunnitella etukäteen alustariippumattomiksi — se on ennenaikaista optimointia. **Mitä tarkoittaa tulevaisuudessa:** Jos toinen alusta tulee ajankohtaiseksi, Gitea-versiota käytetään joko pohjana redesignille tai mallina erilliselle toteutukselle. Rajapintoja ei suunnitella etukäteen alustariippumattomiksi — se on ennenaikaista optimointia.
### 7. Cross-repo commit traceability ### 8. Cross-repo commit traceability
Kun build-ketju ylittää reporajat (mikropalvelu → deployment → integraatiotestit → e2e-testit), jokainen vaihe raportoi kahteen suuntaan: omaan committiinsa ja takaisin root-committiin, josta ketju käynnistyi. Kun build-ketju ylittää reporajat (mikropalvelu → deployment → integraatiotestit → e2e-testit), jokainen vaihe raportoi kahteen suuntaan: omaan committiinsa ja takaisin root-committiin, josta ketju käynnistyi.
@@ -128,18 +159,7 @@ sequenceDiagram
--- ---
## Teknologiavalinnat (seurauksina ylläolevista periaatteista) ## Mitä tietoisesti hylättiin
| Valinta | Miksi |
|---------|------|
| **Gitea Actions reusable workflows** | Periaate 2: natiivein tapa jakaa CI-logiikkaa ilman omaa runtimea |
| **Gitea REST API** (`/api/v1/...`) | Periaate 1: commit-statusraportointi. Periaate 4: workflow-dispatch ja status-pollaus. Periaate 7: cross-repo statusraportointi useaan committiin |
| **MinIO** (S3-yhteensopiva) | Periaate 5: HTML-selailtavat raportit ilman ulkoista palvelinta. Kubernetes-natiivi, yksi binääri |
| **YAML** konfiguraatioformaattina | Periaate 3: repo omistaa konffinsa. YAML on luettava, versioitava ja tuttu Jenkins-versiosta |
| **curl + jq + bash** integraatiokerroksena | Periaate 2: ei custom action -runtimea. Gitea REST API:a kutsutaan suoraan workflow-stepistä |
| **Gitea organization secrets/variables** | Periaate 3: infra-tason asetukset (MinIO-URL, tokenit) eivät kuulu reposuuteen |
---
## Mitä tietoisesti hylättiin ## Mitä tietoisesti hylättiin
+4 -197
View File
@@ -1,199 +1,6 @@
# Raporttivarasto — MinIO # Raporttivarasto
> Kuuluu arkkitehtuuriin: [architecture.md](architecture.md). Tämä dokumentti määrittelee testiraporttien tallennuksen, URL-rakenteen, autentikoinnin ja retention policyn. Raporttien hostaus: `git-pages/`-Helm-chartti. Tarkempi dokumentaatio:
`git-pages/README.md` ja `git-pages/docs/`.
--- Tämä dokumentti on korvattu git-pages-kohtaisella dokumentaatiolla.
## Miksi MinIO
Gitea Actionsin sisäänrakennettu artifact-järjestelmä ei tue HTML-selailtavuutta (vain ZIP-lataus), ja artifactien retentio on aikapohjainen (oletus 90 vrk). Jenkins-versiossa raportit olivat selailtavissa suoraan buildista ja pysyivät build-historian mukana.
MinIO täyttää tämän aukon:
- **S3-yhteensopiva** — standardi API, laaja työkalutuki (`mc`, `s3cmd`, AWS SDK)
- **Staattinen web-hosting** — HTML-raportit renderöityvät selaimessa suoraan bucketista
- **Kubernetes-natiivi** — yksi binääri, helppo deployata samaan klusteriin
- **Ei per-repo-konfiguraatiota** — yksi bucket palvelee kaikkia projekteja
- **Ennustettava URL** — polku rakentuu deterministisesti reposta ja commitista
## URL-rakenne
```
{MINIO_BASE}/{repo_slug}/{commit_short}/{report_type}/index.html
```
| Osa | Lähde | Esimerkki |
|-----|-------|-----------|
| `MINIO_BASE` | Gitea org variable `MINIO_BASE_URL` | `https://reports.smith.keskikuja.site` |
| `repo_slug` | `GITHUB_REPOSITORY` → slug | `temperature-store` |
| `commit_short` | `GITHUB_SHA` → 8 merkkiä | `abc12345` |
| `report_type` | Raportin tyyppi | `cucumber`, `jacoco`, `junit`, `site` |
URL rakennetaan `push-reports.sh`-skriptissä ja POSTataan Gitea-commitin statusviestiin `url`-kenttään.
## Staattinen web-hosting
MinIO-bucket konfiguroidaan staattiseksi web-sivustoksi:
```bash
mc anonymous set download minio/reports
mc website create minio/reports --region us-east-1
```
Bucketin rakenne:
```
reports/
├── temperature-store/
│ ├── abc12345/
│ │ ├── cucumber/
│ │ │ └── overview-features.html
│ │ ├── jacoco/
│ │ │ └── index.html
│ │ └── site/
│ │ └── index.html
│ └── def67890/
│ └── ...
├── user-service/
│ └── ...
```
Jokaisella buildilla on oma `{commit_short}`-hakemistonsa — ei race conditionia rinnakkaisten buildien välillä.
## Autentikointi: OIDC + Traefik middleware
Raporttien tulee olla katseltavissa ilman erillistä kirjautumista (muuten commitin statusviestin URL-linkki ei toimi suoraan). Samalla julkinen bucket halutaan suojata.
**Ratkaisu:** MinIO asetetaan Traefik reverse-proxyn taakse. Traefik middleware hoitaa OIDC-autentikoinnin, jossa:
1. Käyttäjä klikkaa raportin URL:ää commitin statusviestistä
2. Traefik ohjaa OIDC-kirjautumiseen (Gitea OAuth2 provider)
3. Onnistuneen kirjautumisen jälkeen käyttäjä ohjataan raporttiin
4. Session säilyy — ei tarvitse kirjautua jokaista raporttia varten
```
Selain ──→ Traefik ──→ OIDC middleware ──→ Gitea OAuth2
└──→ MinIO (bucket reports, static web)
```
**CI-pusku ohittaa OIDC:n:** Workflow'n `push-reports.sh` käyttää MinIO:n S3 API:a suoraan access key + secret key -parilla (Gitea org secrets). CI-liikenne ei kulje julkisen ingressin kautta — `mc`-työkalu puhuu suoraan MinIO-palvelulle klusterin sisällä.
## Retention policy
Pelkkä aikaperustainen retentio ("poista yli 90 vrk vanhat") ei riitä. Retention policyn pitää huomioida:
| Kriteeri | Kuvaus |
|----------|--------|
| **Aika** | Raportti säilyy X päivää buildin jälkeen |
| **Branch** | `master`-branchin raportit säilyvät pidempään kuin feature-branchien |
| **Tagi** | Version kanssa tagitun commitin raportteja ei poisteta koskaan |
| **Viimeisin N** | Jokaisesta branchista säilytetään vähintään N uusinta raporttia |
**Toteutus:** Retention policy konfiguroidaan **ConfigMap:lla**, jonka siivousskripti lukee. ConfigMap on osa MinIO-deploymentin Kubernetes-manifestia.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: minio-report-retention
data:
retention.yaml: |
rules:
- branch: "master"
maxAgeDays: 365
keepMin: 20
- branch: "feature/*"
maxAgeDays: 90
keepMin: 5
- tagged: true
maxAgeDays: -1 # ei koskaan poisteta
keepMin: -1
- default:
maxAgeDays: 90
keepMin: 3
```
Siivoussripti ajetaan CronJobina (esim. kerran päivässä):
1. Listaa kaikki bucketin objektit
2. Parsii polusta `repo_slug` ja `commit_short`
3. Hakee Gitea API:sta commitin metadata (branch, onko tagattu)
4. Soveltaa ConfigMapin retention-sääntöjä
5. Poistaa vanhentuneet objektit `mc rm`:llä
## Raporttien pushaus workflow'sta
`push-reports.sh`:
```bash
#!/bin/bash
# Käyttö: push-reports.sh <report_type> <source_dir>
# Esim: push-reports.sh cucumber target/cucumber-report/
REPORT_TYPE=$1
SOURCE_DIR=$2
TARGET="minio/reports/${GITHUB_REPOSITORY}/${GITHUB_SHA::8}/${REPORT_TYPE}/"
mc cp --recursive "$SOURCE_DIR" "$TARGET"
echo "${MINIO_BASE_URL}/${GITHUB_REPOSITORY}/${GITHUB_SHA::8}/${REPORT_TYPE}/index.html"
```
Skripti palauttaa URL:n, joka syötetään `report-status.sh`:lle commit-statusviestin `url`-kenttään.
## Konfiguraatio Giteassa
| Secret / Variable | Tyyppi | Sisältö |
|---|---|---|
| `MINIO_ACCESS_KEY` | Org secret | MinIO access key |
| `MINIO_SECRET_KEY` | Org secret | MinIO secret key |
| `MINIO_BASE_URL` | Org variable | `https://reports.smith.keskikuja.site` |
Workflow lukee nämä automaattisesti — projekti ei määrittele niitä `ci-flow-values.yaml`:ssa.
## MinIO-deployment (viitteellinen)
Minimalistinen Kubernetes-deployment samassa klusterissa:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio-reports
spec:
replicas: 1
template:
spec:
containers:
- name: minio
image: minio/minio:latest
args: ["server", "/data", "--console-address", ":9001"]
env:
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-secrets
key: access-key
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-secrets
key: secret-key
ports:
- containerPort: 9000 # S3 API
- containerPort: 9001 # Console UI
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: minio-reports-pvc
```
## Huomioitavaa
- **Bucketin luonti ja web-hostingin aktivointi** tehdään kerran deploymentin yhteydessä. Ei per build.
- **URL on deterministinen** — raportin URL voidaan generoida jo ennen kuin raportti on pushattu. Jos raporttihakemistoa ei ole (esim. testit skipattiin), linkki johtaa 404:ään.
- **Indeksitiedoston nimi** riippuu raporttityypistä: Cucumber → `overview-features.html`, JaCoCo → `index.html`, Maven Site → `index.html`. `push-reports.sh`:n vastuulla varmistaa, että indeksitiedosto löytyy.
+8 -4
View File
@@ -1,8 +1,12 @@
# Jaetut skriptit # Jaetut skriptit
> Kuuluu arkkitehtuuriin: [architecture.md](architecture.md). Tämä dokumentti määrittelee reusable workflow'sta kutsuttavien bash-skriptien rajapinnat, parametrit ja vastuut. > ⚠️ **POC-vaihe.** Osa kuvatuista skripteistä (push-reports.sh, tag-commit.sh)
> on suunniteltu mutta ei toteutettu. Toteutetut: `publish-git-pages.sh`,
> `report-status.sh`, `dispatch-workflow.sh` (POC-taso).
>
> Uudelleenkirjoitus odottaa: skriptien määrä ja rajapinnat voivat muuttua.
Skriptit asuvat `gitea-ci-library/scripts/`-hakemistossa. Workflowt lataavat ne checkout-stepin jälkeen. Skriptit asuvat `gitea-ci-library/scripts/`-hakemistossa.
--- ---
@@ -91,9 +95,9 @@ dispatch-workflow.sh "tests/integration" "test.yml" "main" \
--- ---
## `push-reports.sh` ## `push-reports.sh` (vanhentunut — korvattu `publish-git-pages.sh`:lla)
Puskaa raporttihakemiston MinIO:hon ja päivittää indeksisivut. Puskaa raporttihakemiston git-pagesiin.
### Rajapinta ### Rajapinta
+15 -85
View File
@@ -1,109 +1,39 @@
# Tech Stack — Gitea Actions CI -kirjasto # Tech Stack — Gitea Actions CI -kirjasto
> Absoluuttinen lähde sille, mitä teknologioita kirjasto käyttää ja tukee. Tämä dokumentti laaditaan `design-rationale.md`:n pohjalta. Mitään tässä listaamatonta teknologiaa ei oleteta olevan käytössä. > ⚠️ POC-vaihe. Osa teknologiavalinnoista voi muuttua uudelleenkirjoituksen
> myötä. Katso myös `git-pages/docs/tech-stack.md`.
--- ---
## Kirjaston oma runtime ## Kirjaston oma runtime
Kirjasto itsessään on kokoelma **Gitea Actions reusable workflow** -tiedostoja (`.gitea/workflows/*.yml`). Ajonaikaiset stepit käyttävät:
| Teknologia | Versio / minimi | Käyttötarkoitus | | Teknologia | Versio / minimi | Käyttötarkoitus |
|---|---|---| |---|---|---|
| **Gitea Actions** | 1.21+ | CI-alusta, workflow-moottori | | **Gitea Actions** | 1.21+ | CI-alusta, workflow-moottori |
| **Gitea act runner** | 0.2+ | Workflow'n suoritus | | **Gitea act runner** | 0.2+ | Workflow'n suoritus |
| **Bash** | 4.0+ | Integraatioskriptit workflow-stepeissä | | **Bash** | 4.0+ | Integraatioskriptit workflow-stepeissä |
| **curl** | 7.0+ | Gitea REST API -kutsut (statusraportointi, dispatch, pollaus) | | **curl** | 7.0+ | Gitea REST API, git-pages PATCH |
| **jq** | 1.6+ | JSON-vastausten jäsennys REST API -kutsuista | | **jq** | 1.6+ | JSON-vastausten jäsennys |
| **git** | 2.30+ | SCM-operaatiot (checkout, tag, push) |
| **MinIO client (mc)** | latest | Raporttien pushaus MinIO:hon ja URL-generointi |
--- ## Raporttien hostaus
## Konfiguraatio Raportit hostataan git-pages-palvelulla (`git-pages/`-Helm-chartti).
Julkaisu: `scripts/publish-git-pages.sh` → PATCH tar. Tarkemmat
| Teknologia | Käyttötarkoitus | teknologiavalinnat: `git-pages/docs/tech-stack.md`.
|---|---|
| **YAML** | Projektikohtainen konfiguraatio (`ci-flow-values.yaml`) |
| **Gitea organization secrets** | Tokenit ja salasanat (Gitea API -token, MinIO credentials) |
| **Gitea organization variables** | Infra-tason asetukset (MinIO base URL, SonarQube URL) |
---
## Tuetut ulkoiset palvelut ## Tuetut ulkoiset palvelut
Kirjasto integroituu näihin ulkoisiin palveluihin workflow-stepeistä käsin. Palvelut eivät ole osa kirjastoa.
| Palvelu | Rajapinta | Käyttötarkoitus | | Palvelu | Rajapinta | Käyttötarkoitus |
|---|---|---| |---|---|---|
| **Gitea REST API** | `/api/v1/` | Commit-statusraportointi, workflow-dispatch, run-status-pollaus, taggaus | | **Gitea REST API** | `/api/v1/` | Commit-status, workflow-dispatch, branch-listaus (retention) |
| **MinIO** | S3 API + staattinen web-hosting | Testiraporttien tallennus ja selailu | | **git-pages** | HTTP | Raporttien hostaus |
| **SonarQube** | REST API (`/api/`) | Quality gate -pollaus, dashboard-linkitys | | **Gitea Packages** | Container registry API | Docker-imagen push |
| **Gitea Packages** | Container registry API | Docker-imagen push ja taggaus |
---
## Tuetut build-ekosysteemit
Kirjasto tukee näitä käyttäjäprojektien build-työkaluja. Työkalut asennetaan workflow'n ajonaikaiseen konttiin (projektin itse määrittelemään).
| Ekosysteemi | Työkalut | Artifact-tyypit |
|---|---|---|
| **Java / Maven** | `mvn`, `java` | JAR, Docker |
| **Java / Gradle** | `gradle`, `java` | JAR, Docker |
| **Node.js / npm** | `npm`, `node` | npm-paketti, Docker |
| **Docker** | `docker`, `docker buildx` | Docker-image |
---
## Tuetut testikehykset
Kirjasto generoi ja julkaisee raportit näistä testikehyksistä. Itse testikehysten ajurit ovat käyttäjäprojektissa.
| Testikehys | Raporttiformaatti | MinIO-kohde |
|---|---|---|
| **Cucumber** | HTML (Cucumber Reports) | `/{commit_short}/cucumber/` |
| **JUnit** | XML | `/{commit_short}/junit/` |
| **JaCoCo** | HTML | `/{commit_short}/jacoco/` |
| **Maven Site** | HTML | `/{commit_short}/site/` |
| **Mukautettu HTML** | HTML | `/{commit_short}/{report_name}/` |
---
## Tuetut deployment-työkalut
| Työkalu | Käyttötarkoitus |
|---|---|
| **Helm v3** | Kubernetes-deployment (arvot `values-{environment}.yaml`) |
| **Kubernetes** | Ajonaikainen ympäristö (testiympäristöt, tuotanto) |
---
## Mitä EI tueta (verrattuna Jenkins-versioon) ## Mitä EI tueta (verrattuna Jenkins-versioon)
Nämä olivat Jenkins-kirjastossa, mutta on tietoisesti jätetty pois Gitea Actions -versiosta:
| Teknologia | Syy | | Teknologia | Syy |
|---|---| |---|---|
| **GitLab REST API** | Ei multi-platform-tukea (periaate 6) | | **MinIO** | Korvattu git-pagesilla |
| **BitBucket Server REST API** | Ei multi-platform-tukea | | **Multi-Git-platform** | Vain Gitea |
| **BitBucket Cloud REST API** | Ei multi-platform-tukea | | **Jenkins** (shared library, plugins) | Gitea Actions korvaa |
| **Jenkins** (shared library, cucumber plugin, publishHTML, jacoco plugin, buildJob) | Ei Jenkins-riippuvuutta — Gitea Actions korvaa | | **Artifactory/Nexus** | MVP:ssä ei, factory/adapter-pattern valmiina |
| **Artifactory** (Docker registry) | MVP:ssä ei. Factory/adapter-patternilla lisättävissä myöhemmin |
| **Nexus** (Docker registry) | MVP:ssä ei. Factory/adapter-patternilla lisättävissä myöhemmin |
| **Artifactory** (npm registry) | MVP:ssä ei. Factory/adapter-patternilla lisättävissä myöhemmin |
| **Groovy** | Jenkins-spesifi. Korvautuu Bashilla ja YAML:lla |
---
## Huomautus: Docker-rekisterit
MVP:ssä tuetaan vain **Gitea Packages** (Container registry). Arkkitehtuuriin suunnitellaan factory/adapter-pattern, jolla Artifactory ja Nexus voidaan lisätä myöhemmin ilman workflow-muutoksia. Rekisteri konfiguroidaan `ci-flow-values.yaml`:n `docker`-osiossa `type`-kentällä (vrt. Jenkins `ArtifactRepoType`-enum).
## Huomautus: SonarQube-integraatio
Jenkins-versio pollasi quality gatea: `GET /api/project_analyses/search` → etsi uusin analyysi → `GET /api/qualitygates/project_status?analysisId=X`. SonarQubessa on tätä parempi rajapinta. Tutkitaan arkkitehtuurivaiheessa.
## Huomautus: Docker-in-Docker
Gitea Actions tukee Docker-in-Dockeria natiivisti `services:`-määrittelyllä workflow-tiedostossa. Tämä vastaa Jenkinsin `podTemplate`-konttia `docker:19.03.1-dind` + `docker:19.03.1` sidecar-mallia. Projektin workflow määrittelee tarvittavat kontit itse — reusable workflow ei pakota tiettyä konttivalikoimaa.
@@ -1,8 +1,8 @@
# Ticket 0006: `ci-feature.yml` (reusable workflow) # Ticket 0006: Pipeline as conf
**Vaihe:** 6/12 **Vaihe:** 6/12
**Status:** pending **Status:** pending
**Feature branch:** `feature/0006-ci-feature-yml` **Feature branch:** `feature/0006-pipeline-as-conf`
**TDD required:** Yes **TDD required:** Yes
**Feature file required:** Yes **Feature file required:** Yes
@@ -11,6 +11,9 @@
- `tests/features/0006-ci-feature.feature` - `tests/features/0006-ci-feature.feature`
- Skills: `tdd`, `implementation`, `clean-code` - Skills: `tdd`, `implementation`, `clean-code`
**Pre-requisite (estotiketti):**
- Pipeline-as-conf -suunnitelma (`docs/tickets/0006-plan.md` tai vastaava) on valmis ja hyväksytty. Toteutusta ei aloiteta ennen kuin `ci-flow-values.yaml`-skeema ja `ci-feature.yml`-jobirakenne on suunniteltu.
--- ---
## TDD — Red-Green-Refactor + Dogfood ## TDD — Red-Green-Refactor + Dogfood
@@ -27,7 +30,7 @@ bats tests/workflows.bats
``` ```
### Green ### Green
Toteuta `gitea/workflows/ci-feature.yml`. Toteuta `.gitea/workflows/ci-feature.yml`.
```bash ```bash
bats tests/workflows.bats bats tests/workflows.bats
@@ -35,12 +38,12 @@ bats tests/workflows.bats
``` ```
### Dogfood ### Dogfood
Lisää kutsu `gitea/workflows/ci.yml`:stä: Lisää kutsu `.gitea/workflows/ci.yml`:stä:
```yaml ```yaml
feature: feature:
if: github.ref != 'refs/heads/master' if: github.ref != 'refs/heads/master'
uses: ./gitea/workflows/ci-feature.yml@main uses: ./.gitea/workflows/ci-feature.yml
``` ```
Jokainen push feature-branchiin ajaa `ci-feature.yml`:n. Jokainen push feature-branchiin ajaa `ci-feature.yml`:n.
@@ -48,16 +51,19 @@ Jokainen push feature-branchiin ajaa `ci-feature.yml`:n.
## DoD ## DoD
- [ ] Cucumber: `@ticket-0006 and @mock` → kaikki skenaariot GREEN - [ ] Cucumber: `@ticket-0006 and @mock` → kaikki skenaariot GREEN
- [ ] `tests/workflows.bats` — YAML-validointi läpi - [ ] `tests/workflows.bats` — YAML-validointi läpi
- [ ] Kaikki stepit määritelty: start → unit-test → code-coverage → publish-html → end - [ ] 4 jobia: `start``unit-test``code-coverage``end`
- [ ] `concurrency` estää rinnakkaiset ajot samalle branchille - [ ] `concurrency` estää rinnakkaiset ajot samalle branchille
- [ ] Dogfood: kirjaston oma CI käyttää tätä workflow'ta - [ ] Dogfood: kirjaston oma CI käyttää tätä workflow'ta
- [ ] Statusviestit raportoivat jokaisen stepin tilan - [ ] Jokainen job postaa oman commit-statuksen `report-status.sh`:lla (eri `context`-avain per job)
- [ ] Branch protection: `ci/unit-test` ja `ci/code-coverage` näkyvät erillisinä checkeinä
--- ---
## Toiminto ## Toiminto
Feature-branchin CI-workflow. Kutsuu `report-status.sh` ja `push-reports.sh` (Ticket 0001 ja 0003). Feature-branchin CI-workflow. Kutsuu vain `report-status.sh`:ta (Ticket 0001). Ei raporttien julkaisua MinIO:hon.
**Huom:** Raporttien julkaisu (`push-reports.sh`) lisätään erillisessä tiketissä (0013).
## Trigger ## Trigger
@@ -71,20 +77,18 @@ Feature-branchin CI-workflow. Kutsuu `report-status.sh` ja `push-reports.sh` (Ti
| `maven-image` | Ei | Maven-kontti (esim. `maven:3.9-eclipse-temurin-21`) | | `maven-image` | Ei | Maven-kontti (esim. `maven:3.9-eclipse-temurin-21`) |
| `node-image` | Ei | Node-kontti (npm-projektit) | | `node-image` | Ei | Node-kontti (npm-projektit) |
## Steppit ## Steppit (4 jobia, jokainen oma check)
``` ```
start → unit-test → code-coverage → publish-html → end start → unit-test → code-coverage → end
``` ```
| Steppi | Skripti | Status | | Job | `context` | Toiminto |
|--------|---------|--------| |-----|-----------|----------|
| `start` | `report-status.sh pending "Building..."` | INPROGRESS | | `start` | `ci/start` | `report-status.sh pending "Build started"` |
| `unit-test` | Projektin oma testiajo | — | | `unit-test` | `ci/unit-test` | Projektin oma testiajo `report-status.sh success/failure` |
| `code-coverage` | JaCoCo / vastaava | — | | `code-coverage` | `ci/code-coverage` | JaCoCo / vastaava → `report-status.sh success/failure` |
| `publish-html` | `push-reports.sh cucumber; push-reports.sh jacoco` | — | | `end` | `ci/end` | `report-status.sh success/failure` (kokonaistulos) |
| `end` | `report-status.sh success/failure` | Lopullinen status |
| `fail` (catch) | `report-status.sh failure` | FAILURE |
## Concurrency ## Concurrency
@@ -97,10 +101,10 @@ concurrency:
## Verifiointi ## Verifiointi
Simuloi paikallisesti (act-runner tai manuaalinen steppien ajo): Simuloi paikallisesti (act-runner tai manuaalinen steppien ajo):
1. `report-status.sh pending` → commitin status = pending 1. `report-status.sh pending` → commitin status = pending (`ci/start`)
2. Testit ajetaan 2. Testit ajetaan`ci/unit-test` = success/failure
3. `push-reports.sh` → raportit MinIO:ssa 3. Coverage ajetaan → `ci/code-coverage` = success/failure
4. `report-status.sh success` → statusviesti + URL 4. `report-status.sh success/failure``ci/end` = lopullinen status
## Viitteet ## Viitteet
@@ -0,0 +1,102 @@
# Ticket 0013: Raporttien julkaisu `ci-feature.yml`:ään
**Vaihe:** 13 (riippuu 0003 ja 0005)
**Status:** pending
**Feature branch:** `feature/0013-ci-feature-report-publish`
**TDD required:** Yes
**Feature file required:** Yes
**Required context:**
- `docs/test-plan/tdd-guide.md`
- `tests/features/0013-ci-feature-report-publish.feature`
- Skills: `tdd`, `implementation`, `clean-code`
**Pre-requisite (estotiketti):**
- Tiketti 0003 (`push-reports.sh`) — valmis
- Tiketti 0005 (`report-service/generate-index.sh`) — valmis
- Tiketti 0006 (`ci-feature.yml`) — valmis (perusstepit ilman raportteja)
---
## TDD — Red-Green-Refactor
### Red
Ennen muutoksia, kirjoita validointitestit (`tests/workflows.bats`):
- `publish-html`-job lisätty `ci-feature.yml`:ään
- `push-reports.sh`-kutsut oikeilla parametreilla
- `end`-jobin URL-parametri sisältää raportti-URL:n
```bash
bats tests/workflows.bats
# FAIL: publish-html -steppiä ei ole
```
### Green
Lisää `publish-html`-job `ci-feature.yml`:ään.
```bash
bats tests/workflows.bats
# PASS
```
## DoD
- [ ] Cucumber: `@ticket-0013 and @mock` → kaikki skenaariot GREEN
- [ ] `tests/workflows.bats` — YAML-validointi läpi
- [ ] `publish-html`-job puskaa raportit MinIO:hon
- [ ] `end`-jobin statusviesti sisältää raportti-URL:n
- [ ] `ci-flow-values.yaml`-skeema tukee raporttityyppejä (`cucumber`, `jacoco`, `site`)
---
## Toiminto
Lisää `ci-feature.yml`:ään `publish-html`-job, joka kutsuu `push-reports.sh`:ta (Ticket 0003) jokaiselle raporttityypille. Raporttityypit konfiguroidaan `ci-flow-values.yaml`:ssa.
## Muutokset `ci-feature.yml`:ään
Uusi steppijärjestys:
```
start → unit-test → code-coverage → publish-html → end
```
| Job | `context` | Toiminto |
|-----|-----------|----------|
| `start` | `ci/start` | `report-status.sh pending` |
| `unit-test` | `ci/unit-test` | Projektin oma testiajo |
| `code-coverage` | `ci/code-coverage` | JaCoCo / vastaava |
| `publish-html` | `ci/publish-html` | `push-reports.sh` jokaiselle raporttityypille |
| `end` | `ci/end` | `report-status.sh success/failure` + raportti-URL |
## `ci-flow-values.yaml`-laajennus
```yaml
build:
ecosystem: maven
java-version: "21"
test:
unit: "mvn test"
coverage: "mvn jacoco:report"
reports:
- type: cucumber
source: target/cucumber-report
- type: jacoco
source: target/site/jacoco
- type: site
source: target/site
```
## Verifiointi
1. `push-reports.sh cucumber target/cucumber-report` → onnistuu
2. `push-reports.sh jacoco target/site/jacoco` → onnistuu
3. `end`-jobin statusviestin URL osoittaa raporttiin
## Viitteet
- `docs/tickets/0003-push-reports-sh.md``push-reports.sh`
- `docs/tickets/0005-report-service-index-generation.md``generate-index.sh`
- `docs/tickets/0006-pipeline-as-conf.md` — Pipeline as conf (perusstepit)
- `docs/report-hosting.md` — Raporttien URL-rakenne
+9 -11
View File
@@ -1,6 +1,8 @@
# Reusable workflowt # Reusable workflowt
> Kuuluu arkkitehtuuriin: [architecture.md](architecture.md). Tämä dokumentti määrittelee jokaisen reusable workflow'n elinkaaren ja rajapinnan. > ⚠️ **POC-vaihe.** Tämä dokumentti kuvaa suunniteltuja workflow'ta
> (ci-feature, ci-master, deploy, test). POCissa on toteutettu vain
> `ci-engine.yml`. Uudelleenkirjoitus odottaa.
--- ---
@@ -29,8 +31,7 @@ start → unit-test → code-coverage → html-reports → end
| Parametri | Pakollinen | Kuvaus | | Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------| |-----------|------------|--------|
| `config-file` | Kyllä | Polku `ci-flow-values.yaml`:aan (yleensä `ci-flow-values.yaml`) | | `config-file` | Kyllä | Polku `ci-flow-values.yaml`:aan (yleensä `ci-flow-values.yaml`) |
| `maven-image` | Ei | Maven-kontin image (esim. `maven:3.9-eclipse-temurin-21`) | | `containers` | Ei | Kuvaus konteista: avain = nimi, arvo = image. Steppi valitsee missä kontissa ajaa. |
| `node-image` | Ei | Node-kontin image (jos npm-projekti) |
### Steppi-kaavio ### Steppi-kaavio
@@ -42,9 +43,8 @@ flowchart TD
aja testit, generoi raportit"] aja testit, generoi raportit"]
UNIT --> COV["code-coverage UNIT --> COV["code-coverage
jacoco / vastaava"] jacoco / vastaava"]
COV --> HTML["publish-html COV --> HTML["publish-reports
pushaa raportit MinIO:hon vie raportit git-pagesiin"]
generoi index.html"]
HTML --> END(["end HTML --> END(["end
POST lopullinen status"]) POST lopullinen status"])
@@ -84,8 +84,7 @@ unit-test → quality-gate → build-jar → build-docker → push-docker → ta
| Parametri | Pakollinen | Kuvaus | | Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------| |-----------|------------|--------|
| `config-file` | Kyllä | Polku `ci-flow-values.yaml`:aan | | `config-file` | Kyllä | Polku `ci-flow-values.yaml`:aan |
| `maven-image` | Ei | Maven-kontti | | `containers` | Ei | Kuvaus konteista: avain = nimi, arvo = image |
| `docker-image` | Ei | Docker-in-Docker -image (esim. `docker:26-dind`) |
### isContainerBuilt-check ### isContainerBuilt-check
@@ -125,9 +124,8 @@ flowchart TD
CHECK -- "kyllä" --> CTF["continueToTestFlow"] CHECK -- "kyllä" --> CTF["continueToTestFlow"]
TAG --> CTF TAG --> CTF
CTF --> HTML["publish-html CTF --> HTML["publish-reports
pushaa Maven Site vie raportit git-pagesiin"]
MinIO:hon"]
HTML --> END(["end HTML --> END(["end
lopullinen status"]) lopullinen status"])
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v2
name: git-pages
description: Codeberg git-pages for CI HTML reports (apex site, Traefik BasicAuth publish)
type: application
version: 0.1.0
appVersion: "0.9.1"
+151
View File
@@ -0,0 +1,151 @@
# git-pages
Jaettu **Gitea CI -raporttien** tallennus- ja lukupaikka: HTML-raportit (esim. Cucumber)
commit-kohtaisiin polkuihin, selaimella avattavina linkkeinä Gitean commitin CI-job
raportista. Kuvaus ja perustelut: [docs/](docs/).
---
## Quick Start
### 1. Secretit
Luo secretit ennen Helm-asennusta: [docs/secrets.md](docs/secrets.md)
```bash
# Avaa secrets.md ja suorita snipletit siellä
# Palaa tähän jälkeen Helm-asennukseen
```
### 2. Instanssin values-tiedosto
```bash
# Muokkaa oma values-tiedosto (tai käytä dev-values.yaml)
cp git-pages/dev-values.yaml my-values.yaml
# Muuta: ingress.host, certificate.issuerRef.name, jne.
```
### 3. Helm-asennus
```bash
NS=git-pages
VALUES=my-values.yaml
helm upgrade --install git-pages ./git-pages \
-n "$NS" --create-namespace \
-f "$VALUES"
```
Helm ajaa asennuksen jälkeen init-jobin, joka PUTtaa paikanpitäjäsivun
git-pagesiin. Tämä luo tarvittavan `.index`-tiedoston — sen jälkeen
Gitea Actions -scriptit voivat käyttää suoraan PATCHia ilman
PUT-fallbackia.
---
## Vie publish-token Gitea Actions-secretiin (per repo)
⚠️ **Tehtävä jokaiselle repoille**, joka julkaisee raportteja git-pagesiin.
```bash
NS=git-pages
REPO_OWNER="niko"
REPO_NAME="gitea-ci-library"
# 1. Lue plaintext-token erillisestä secretistä
TOKEN=$(kubectl get secret git-pages-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
# 2. Kopioi leikepöydälle
echo -n "$TOKEN" | pbcopy # macOS
# echo -n "$TOKEN" | xclip -sel clip # Linux
# 3. Avaa Gitea Actions secrets -sivu
open "https://gitea.app.keskikuja.site/${REPO_OWNER}/${REPO_NAME}/settings/actions/secrets"
# Linux: xdg-open "https://gitea.app.keskikuja.site/${REPO_OWNER}/${REPO_NAME}/settings/actions/secrets"
```
**Gitea UI:ssa:** New Secret → Name: `GIT_PAGES_PUBLISH_TOKEN` → Value: **liitä leikepöydältä** → Save
> 💡 **Monelle repoille:** Toista vaiheet 34, tai katso [automatisointi](docs/secrets.md#automatisointi-useamman-repon-salaisuuden-lis%C3%A4%C3%A4miseen).
---
## Käyttöönotto
### 1. Secretit
[docs/secrets.md](docs/secrets.md)
### 2. Instanssin values-tiedosto
`values.yaml` sisältää jaetut vakiot. Ympäristökohtaiset arvot omaan tiedostoon, esim.
`dev-values.yaml` / `prod-values.yaml`:
```yaml
ingress:
host: pages.example.com # julkinen host (luku + julkaisu)
certificate:
issuerRef:
name: letsencrypt-prod # cert-manager ClusterIssuer / Issuer
kind: ClusterIssuer
persistence:
storageClass: "" # tyhjä = klusterin oletus
size: 5Gi
retention:
giteaApiUrl: https://gitea.example.com
rules:
default:
maxAgeDays: 90
keepMin: 5
branches:
main:
maxAgeDays: 365
keepMin: 20
```
Esimerkki dev-ympäristöstä: [dev-values.yaml](dev-values.yaml).
### 3. Helm-asennus
Repojuuresta (sama `NS` kuin [docs/secrets.md](docs/secrets.md)):
```bash
NS=git-pages
VALUES=git-pages/dev-values.yaml
helm upgrade --install git-pages ./git-pages \
-n "$NS" --create-namespace \
-f "$VALUES"
helm template git-pages ./git-pages -f "$VALUES"
```
---
## CI-julkaisu
Julkaisu DNS-osoitteeseen BasicAuthilla:
```bash
# Esimerkki: julkaise raportti
curl -X PATCH https://ci-reports.helm-dev.keskikuja.site/owner/repo/commit/sha8/ \
-H "Authorization: Basic $(echo -n "publish:$GIT_PAGES_PUBLISH_TOKEN" | base64)" \
-H "Content-Type: application/x-tar" \
--data-binary @raportti.tar
```
**Vaaditut asetukset:**
| Missä | Arvo |
|-------|------|
| Gitea Actions secret | `GIT_PAGES_PUBLISH_TOKEN` (sama kuin K8s `git-pages-publish-token` `token`-avain) |
| Scriptissä | `curl` käyttää BasicAuth-headeria yllä |
**K8s secretit (Traefik-yhteensopivuus):**
- `git-pages-publish-auth` = htpasswd (Traefik BasicAuth middleware)
- `git-pages-publish-token` = plaintext (luetaan Giteaan viedessä)
Tarkemmat secret-ohjeet: [docs/secrets.md](docs/secrets.md).
+29
View File
@@ -0,0 +1,29 @@
# Dev instance — overrides values.yaml constants.
# helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml
ingress:
host: ci-reports.helm-dev.keskikuja.site
certificate:
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
persistence:
storageClass: ""
size: 5Gi
retention:
enabled: true
giteaApiUrl: https://gitea.app.keskikuja.site
rules:
branches:
default:
maxAgeDays: 90
keepMin: 5
main:
maxAgeDays: 365
keepMin: 20
master:
maxAgeDays: 365
keepMin: 20
+142
View File
@@ -0,0 +1,142 @@
# Architecture — git-pages
> Komponentit, datavirrat ja rajapinnat. Miksi näin on rakennettu: [design-rationale.md](design-rationale.md).
> Secretit: [secrets.md](secrets.md). Teknologiat: [tech-stack.md](tech-stack.md).
Tämä dokumentti koskee vain `git-pages/`-palvelua — ei juuren `gitea-ci-library`-kirjastoa.
---
## Yleiskuvaus
git-pages on jaettu **HTML-raporttiarkisto**: yksi apex-host, monta Gitea-repoa, commit-kohtaiset
raporttipolut. Julkaisija (esim. CI) puskaa sisällön tar-arkistona; lukija avaa raportin
selaimella commit-linkistä.
Codeberg git-pages ajaa `PAGES_INSECURE=1` — sovellus ei tee forge-authia. Julkaisu- ja
TLS-rajaukset ovat Kubernetes-kerroksessa (Traefik, cert-manager, Secretit).
---
## Komponentit
| Komponentti | Rooli |
|-------------|-------|
| **git-pages Pod** | Codeberg git-pages `0.9.1`, filesystem-storage `/app/data` |
| **retention sidecar** | Samassa podissa, HTTP API localhost:3000, siivoaa vanhat raportit |
| **PVC** | Raporttisisältö (storage v2 — `.index` + blob) |
| **Service** | ClusterIP :3000 git-pagesille |
| **Traefik IngressRoute** | Julkaisu (PATCH/PUT + BasicAuth) ja luku (GET/HEAD) eri säännöillä |
| **Traefik Middleware** | `git-pages-publish-auth` (BasicAuth), HTTPS-redirect |
| **cert-manager Certificate** | TLS → Secret `git-pages-tls` |
| Secret | Rooli |
|--------|-------|
| `git-pages-publish-auth` | htpasswd julkaisuun (Traefik) |
| `git-pages-publish-token` | plaintext token (Gitea Actions -secretiin vietäväksi) |
| `git-pages-retention-gitea` | Gitea PAT branch-tarkistukseen (sidecar)
---
## URL ja sisältö
Julkinen osoite:
```
https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/reports/f4baa286/cucumber/index.html
└────────── selvä URL ─────────┘ └───────────────── Gitea-yhteensopiva polku ─────────────────────────┘
```
Levyllä (apex index-site):
```
/app/data/site/{host}/
.index # Protobuf-manifesti (storage v2 — kaikki tiedostot tässä yhdessä tiedostossa)
```
Tiedostot eivät ole flat-FS:nä — katso `implementation-notes.md`.
Apex-juuri `/` on tyhjä — ei landing-sivua.
---
## Järjestelmäkaavio
```mermaid
flowchart TB
subgraph ext["Ulkoiset"]
PUB["Julkaisija\n(CI)"]
BR["Selain"]
GITEA["Gitea API\n(branch-lista)"]
end
subgraph edge["Reuna"]
TRAEFIK["Traefik\nIngressRoute + Middleware"]
CM["cert-manager\nTLS"]
end
subgraph cluster["git-pages Pod"]
GP["git-pages\n(kontti)"]
RT["retention sidecar\n(kontti)\nHTTP API localhost:3000"]
PVC["PVC /app/data"]
end
PUB -->|"PATCH/PUT + BasicAuth\ntar"| TRAEFIK
BR -->|"GET/HEAD"| TRAEFIK
TRAEFIK --> GP
CM --> TRAEFIK
GP --> PVC
RT -->|"reads .git-pages/manifest.json\nHTTP localhost"| GP
RT -->|"check branches"| GITEA
```
---
## Julkaisu
1. Julkaisija paketoi `{owner}/{repo}/reports/{sha8}/` tar-arkistoksi (sis. `.meta`)
2. `PATCH` tai `PUT` apex-URL:iin (`https://{host}/`) + `Content-Type: application/x-tar`
3. Traefik tarkistaa BasicAuth (`publish` + token) → välittää git-pagesille
4. git-pages kirjoittaa PVC:lle
Julkaisu kulkee aina julkisen ingressin kautta — ei suoraa ClusterIP-kirjoitusta ulkopuolelta.
---
## Luku
1. Selain avaa commit-statuslinkin (GET/HEAD)
2. Traefik välittää git-pagesille ilman julkaisu-Middlewarea
3. git-pages palauttaa HTML:n polusta
Luku-auth (OIDC) ei ole toteutettu — GET/HEAD on julkinen, jos URL tunnetaan.
Katso [design-rationale.md — Luku-auth](design-rationale.md#luku-auth).
---
## Retention
Sidecar-kontti samassa podissa, ajaa retention-cleanup.sh 24h välein:
1. Lukee `.git-pages/manifest.json` HTTP:lla localhost:3000
2. Etsii `.meta`-tiedostot, tarkistaa iän ja branchin
3. **Poistettu branch** — jos `.meta.branch` ei ole Giteassa → whiteout PATCH
4. **Aktiivinen branch**`maxAgeDays` + `keepMin` (`retention.rules`)
5. Whiteout-tar → PATCH localhost:3000 — poistaa raportit
Gitea API: `GET /api/v1/repos/{owner}/{repo}/branches/{branch}``read:repository` PAT.
Katso [secrets.md](secrets.md).
---
## Rajapinnat
| Suunta | Protokolla | Auth | Kuvaus |
|--------|------------|------|--------|
| Julkaisija → Traefik | HTTPS PATCH/PUT | BasicAuth `publish` | tar → apex site |
| Selain → Traefik | HTTPS GET/HEAD | — (tänään) | HTML-raportti |
| Sidecar → Gitea | HTTPS GET | PAT `read:repository` | branch-tarkistus per repo |
| Sidecar → git-pages | HTTP :3000 | — (PAGES_INSECURE) | manifestin luku + whiteout PATCH |
| Traefik → git-pages | HTTP :3000 | — | sisäverkko |
git-pages ei käytä Gitea forge-API:a julkaisuun eikä `pages`-branchia.
+198
View File
@@ -0,0 +1,198 @@
# Design Rationale — git-pages
> Miksi git-pages on rakennettu näin. Arvot, periaatteet ja reunaehdot.
>
> Tämä dokumentti on **normatiivinen** `git-pages/`-alikansiolle. Se ei kuvaa juuren
> `gitea-ci-library`-kirjastoa eikä sen workfloweja.
>
> Liittyvät dokumentit: [architecture.md](architecture.md), [tech-stack.md](tech-stack.md), [secrets.md](secrets.md).
---
## Miksi tämä on olemassa
### Ongelma
CI-testiajoista syntyy HTML-raportteja. Esimerkiksi Cucumber-testiraportti toimii
elävänä dokumentaationa git commitin tilasta.
Gitea ei tarjoa web-selaimella selattavaa arkistoa näille HTML-raporteille.
git-pages ratkaisee tämän ongelman.
| Vaihtoehto | Miksi ei riitä |
|---|---|
| **Gitea Actions -artifactit** | Vain ZIP-lataus — HTML ei renderöidy selaimessa |
| **Gitea `pages`-branch** | Yksi branch per repo; rinnakkaiset buildit törmäävät saman branchin pushissa |
| **Gitea Releases** | Sotkee julkaisuhistorian satojen CI-buildien raporteilla |
### Ongelma URL:ssa (hylätty malli)
Alkuvaiheen malli sitoi hostin repoon: `https://{owner}.{host}/{repo}/...`
(subdomain per owner). Julkinen linkki piti sitten “kääntää” Gitea-tyyliseksi poluksi
Traefik-rewritellä (`/{owner}/{repo}/...` → eri `Host` + lyhyempi polku).
Tämä oli ongelmallinen:
- per-owner middleware / rewrite kube-resursseina
- julkaisu-URL ja lukemis-URL eri muodossa
- wildcard-TLS tai monimutkainen cert-hallinta
- vaikea selittää kehittäjälle mistä host tulee
### Ratkaisu — `selvä_url` + Gitea-yhteensopiva polku
URL rakennetaan kahdesta erillisestä osasta, ei yhdestä sekavasta kaavasta:
| Osa | Mistä | Esimerkki |
|-----|-------|-----------|
| **Selvä URL** | Organisaation kiinteä pages-host | `https://pages.example.com` |
| **Gitea-yhteensopiva polku** | Repo (`{owner}/{repo}`) + commit | `/acme-corp/backend-api/reports/abc12345/index.html` |
**Julkinen linkki** = selvä URL + polku (yksi merkkijono commit-statusiin, ei rewritea):
`https://pages.example.com/acme-corp/backend-api/reports/abc12345/index.html`
Polku vastaa Gitea Pages -käytäntöä (`/{owner}/{repo}/...`). Host on aina sama —
ei `{owner}.pages...`-subdomainia.
**Konkreettinen esimerkki (nykyinen ympäristö):**
| Elementti | Arvo |
|-----------|------|
| **Gitea-instanssi** | `gitea.app.keskikuja.site` |
| **Repo** | `niko/gitea-ci-library` |
| **Haara** | `plan/0003-alkaa-käyttämään-itseään-commit-raportti` |
| **Commit SHA** | `14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb` |
| **Raportin nimi** | `cucumber` (esim.) |
| **Gitea commit -URL** | `https://gitea.app.keskikuja.site/niko/gitea-ci-library/commit/14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb` |
| **Raportin julkinen URL** | `https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/commit/14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb/cucumber/index.html` |
Tämä varmistaa, että CI-statuslinkki on suoraan luettavissa ilman domain-rewriteä: raportin polku peilaa täsmälleen Gitean commit-polun rakennetta (`/{owner}/{repo}/commit/{sha}/{raportin-nimi}/`). Koska yksi ajo tuottaa useita raportteja, raportin nimi erottaa ne toisistaan.
Julkaisija (CI tai muu asiakas) lähettää tar-arkiston PATCH/PUT:lla. Lukija hakee
HTML:n GET:llä. Ei Gitea-git-integraatiota eikä `pages`-branchia.
**Codebergin security-malli ei sovellu tähän käyttöön** — forge-auth (Gitea PAT +
`write:repository`), DNS TXT -haaste ja muut git-pagesin sisäänrakennetut valtuutus-
mekanismit on ohitettu kokonaan (`PAGES_INSECURE=1`). Niiden sijaan Kubernetes-kerros
hoitaa rajauksen: Traefik BasicAuth julkaisuun, cert-manager TLS:ään, erillinen
publish-token ([secrets.md](secrets.md)). Sovellus palvelee sisältöä; klusteri päättää
kuka saa kirjoittaa.
---
## Suunnitteluperiaatteet
### 1. Selvä URL + Gitea-yhteensopiva polku
Julkinen osoite = kiinteä apex-host + polku `/{owner}/{repo}/reports/{sha8}/...`.
Apex-juuri `/` on tyhjä tarkoituksella — ei landing-sivua.
**Miksi:** Kehittäjä näkee Gitea-tyylisen polun; infra näkee yhden hostin. Ei Traefik-
rewritea, ei per-owner subdomaineja, ei erillistä “julkaisu-URL vs. lukemis-URL” -kaavaa.
Yksi TLS-sertifikaatti, yksi IngressRoute, yksi PVC.
### 2. Sovelluksen sisäinen security kytketty pois, Traefik hoitaa rajauksen
`git-pages`-sovelluksen koko sisäinen security-mekanismi on kytketty pois päältä (`PAGES_INSECURE=1`). Kirjoitusoikeuden validointi tapahtuu yksinomaan Kubernetes-reunalla Traefik BasicAuth -middlewaren avulla. Sovellus palvelee sisältöä sokeana; klusteri päättää, kuka saa kirjoittaa.
### 3. Julkaisu ja luku erotettu
Julkaisu (PATCH/PUT) vaatii Traefik BasicAuthin. Luku (GET/HEAD) on erillinen reitti — katso [Luku-auth](#luku-auth) alla.
**Miksi:** Koska sovellus ei validoi julkaisuoikeuksia, kirjoitusoikeus on eksplisiittisesti eriytetty Traefik Middlewaressä (`git-pages-publish-auth`).
### 4. Yksi publish-token, kaksi säilöä
Sama plaintext-token: klusterin Secretissä htpasswd-hashina, julkaisijan secret-holvissa
(esim. CI-alustan Actions-secret).
**Miksi:** Ei Gitea PAT:ia eikä `write:repository` -oikeutta. Token antaa vain
julkaisuoikeuden tähän palveluun. Yksi arvo, kaksi paikkaa — ks. [secrets.md](secrets.md).
### 5. Secretit erillisessä hallinnassa
`git-pages-publish-auth` luodaan ennen käyttöönottoa — ei osana sovelluksen konfiguraatiotiedostoja.
**Miksi:** Salaisuudet eivät kulje versionoiduissa arvoissa. Rotaatio ja SealedSecrets
pysyvät operaattorin hallussa. Ks. [secrets.md](secrets.md).
### 6. Minimaalinen parametrisointi
Instance-arvot (`host`, `issuer`, PVC) `{env}-values.yaml`:ssa. Resurssinimet,
secret-nimet ja Traefik-wire kovakoodattu templatessa.
**Miksi:** Parametrisoi vain se, mikä vaihtelee instanssien välillä (host, TLS-issuer,
levy). Vakioidut nimet ja wire pysyvät ennustettavina kaikissa asennuksissa.
---
## Puutteet
Tietoisesti avoimet asiat — eivät estä nykyistä julkaisu- ja lukumallia.
### Luku-auth
Julkaisu on suojattu (Traefik BasicAuth). **Luku ei ole:** GET/HEAD on julkinen — kuka
tuntee URL:n voi lukea raportin.
Tavoite: Traefik OIDC GET/HEAD-reitille (Gitea OAuth2 -provider). Session säilyy —
commit-statuslinkki toimii kirjautumisen jälkeen ilman uutta julkaisuoikeutta.
Ei toteutettu. Julkaisu- ja luku-reitit pysyvät erillisinä; OIDC lisätään vain lukupuolelle.
### Retention
Sidecar samassa podissa (HTTP localhost:3000), ajaa retention-cleanup.sh
24h välein:
| Sääntö | Konfiguroitavissa? | Kuvaus |
|--------|-------------------|--------|
| **Poistettu branch** | Ei — aina | Jos `.meta.branch` ei ole Giteassa enää, raportti poistetaan |
| **maxAgeDays** | Kyllä (`dev-values`) | Aktiivisen branchin raportit vanhemmat kuin N päivää |
| **keepMin** | Kyllä (`dev-values`) | Aktiivisella branchilla pidetään vähintään N uusinta |
Poistettujen branchien siivous ei tarvitse parametreja. Jäljelle jääneille
branchille säännöt tulevat `retention.rules` (`branches.default` +
`branches.{name}`).
Ei PVC-skaalausta — sidecar lukee manifestin HTTP:lla ja poistaa whiteout
PATCHilla. Ei K8s API -oikeuksia.
Secret: `git-pages-retention-gitea` — Gitea PAT branch-tarkistukseen.
Ks. [secrets.md](secrets.md).
---
## Rajat
- **Ei forge-integraatiota** — ei `pages`-branchia, ei Gitea API -hakua, ei forge-authia
- **Ei julkaisijalogiikkaa** — kuka julkaisee ja milloin on julkaisijan vastuulla
- **Ei sisäverkon ohitusjulkaisua** — julkaisu kulkee julkisen ingressin kautta (BasicAuth)
---
## Teknologiavalinnat
| Valinta | Miksi |
|---------|-------|
| **Codeberg git-pages** `0.9.1` | Natiivi apex index-site + tar-pohjainen PATCH/PUT -julkaisu |
| **Filesystem + PVC** | Yksinkertainen, yksi replica, ei erillistä objektivarastoa |
| **Traefik IngressRoute + Middleware** | Julkaisuauth erillään sovelluksesta; GET/HEAD eri säännöllä |
| **cert-manager** | TLS automaattisesti (`git-pages-tls`) |
| **Helm v3** | Toistettava asennus; instanssikohtaiset arvot erillisessä values-tiedostossa |
---
## Mitä tietoisesti hylättiin
| Hylätty | Syy |
|---------|-----|
| **deadnews/gitea-pages** | Vetää sisällön Gitea API:sta — ei sopinut CI-push-malliin |
| **Gitea `pages`-branch** | Race condition rinnakkaisissa buildeissa |
| **Per-owner subdomain** (`{owner}.pages...`) | Ongelmallinen URL; vaatii rewrite-middlewarea polun kääntämiseen |
| **Traefik path→host -rewrite** | Korvattu apex + Gitea-polulla — yksi selvä URL commit-linkissä |
| **Gitea forge-auth / PAT** | `write:repository` liian laaja oikeus vain raporttijulkaisuun |
| **DNS TXT -haaste julkaisuun** | Operatiivinen kompleksisuus ilman hyötyä BasicAuthiin verrattuna |
| **Helm-managed publish Secret** | Arvot values-tiedostoihin on kielletty; manuaalinen lähde totuudelle |
| **Image tag `v0.9.1`** | Oikea tagi on `0.9.1` (ei `v`-etuliitettä) |
+43
View File
@@ -0,0 +1,43 @@
# Implementation Notes
Teknisiä huomioita git-pages 0.9.1:n käyttäytymisestä, joita ei ole ilmeistä
dokumentaatiosta.
## Storage v2 (Protobuf manifest)
Git-pages 0.9.1 käyttää v2-arkkitehtuuria. Kaikki sisältö on pakattu
Protobuf-manifestiin (`site/{host}/.index`), ei flat-FS:nä. Tästä seuraa:
- Tiedostoja ei voi etsiä tai poistaa `find`/`rm`-komennoilla
- `.git-pages/manifest.json` listaa kaikki tiedostot (ProtoJSON)
- `.git-pages/archive.tar` antaa koko sisällön (saattaa truncata HTTP/2:ssa)
## Host-header
Git-pages valitsee sivuston Host-headerin perusteella. Ilman oikeaa Hostia
palauttaa 404.
- Ulkoiset requestit: Traefik välittää alkuperäisen Hostin automaattisesti
- Sidecar/localhost: `-H "Host: ci-reports.helm-dev.keskikuja.site"`
## PATCH ja directory-entryt
Jos PATCH-tar sisältää directory-entryn (tyyppi directory, tar typeflag '5'),
se **korvaa** koko hakemiston dokumentaation mukaan. Tar saa sisältää vain
file- ja symlink-entryjä, jotta PATCH toimii odotetusti.
## Whiteout — tiedostojen poisto
Ainoa tapa poistaa tiedostoja ilman koko sivuston PUT-korvausta:
- Tarissa character device entry (`CHRTYPE`, tar typeflag '4')
- `devmajor=0`, `devminor=0`
- PATCH:ataan sivuston juureen
## .init — ensimmäinen julkaisu
Ensimmäinen julkaisu vaatii PUTin, joka luo `.index`-tiedoston. Tämän jälkeen
PATCH riittää.
Helm-chartin post-install -job hoitaa tämän automaattisesti:
consumerien publish-scriptien ei tarvitse tuntea asennuksen tilaa.
+237
View File
@@ -0,0 +1,237 @@
# Secrets — git-pages
## Quick Start
### Vaihe 1: Secret-arkkitehtuuri
Järjestelmässä on kaksi loogista salaista arvoa. Publish-token jaetaan kahteen K8s-secretiin (Traefik-yhteensopivuus):
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` (htpasswd) | `git-pages-publish-auth` (users) | - |
| `report_publish_api_token` (plaintext) | `git-pages-publish-token` (token) | Actions Secret: `GIT_PAGES_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `git-pages-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
**Huomio:** Publish-token jaetaan kahteen secretiin, koska Traefik BasicAuth middleware vaatii single-key secretin sekä on muodossa, missä sitä ei saa takaisin. Jokainen repo mikä raportteja käyttää, tarvitsee selväkielisen arvon, joka on "ylimääräisessä" secretissä.
### Vaihe 2: Luo Gitea PAT (retention)
**Avaa Gitea browserissa:**
1. Kirjaudu Gitea-käyttäjällä, jolla on luku kaikkiin raporttirepoihin
2. **Settings****Applications****Generate New Token**
3. Token name: `CI-REPORTS_READ_FOR_RETENTION`
4. Scopes: valitse vain **`read:repository`**
5. **Generate Token****kopioi token heti** (näytetään vain kerran)
6. Tallenna token talteen (`GITEA_RETENTION_TOKEN`)
### Vaihe 3: Generoi publish-token
**Palaa terminaalille:**
```bash
GITEA_RETENTION_TOKEN="<from Gitea>"
GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)"
echo "Publish-token generoitu. Tallennetaan K8s-secretiin Vaiheessa 4."
echo "$GIT_PAGES_PUBLISH_TOKEN"
```
### Vaihe 4: Luo K8s secrets
```bash
NS=git-pages
# 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin)
kubectl create secret generic git-pages-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$GIT_PAGES_PUBLISH_TOKEN")" \
-n "$NS"
# 2. Publish-token: plaintext (luetaan README:stä Giteaan viedessä)
kubectl create secret generic git-pages-publish-token \
--from-literal=token="$GIT_PAGES_PUBLISH_TOKEN" \
-n "$NS"
# 3. Retention (käyttää Vaiheessa 2 luotua PAT:ia)
kubectl create secret generic git-pages-retention-gitea \
--from-literal=token="$GITEA_RETENTION_TOKEN" \
-n "$NS"
kubectl get secrets -n "$NS"
```
---
### Seuraava: Helm-asennus
Palaa takaisin [README.md](../README.md#käyttöönotto) ja jatka kohdasta "Instanssin values-tiedosto".
---
## Secret Arkkitehtuuri
### Loogiset salaisuudet
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` | `git-pages-publish-auth` (htpasswd) | Actions Secret: `GIT_PAGES_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `git-pages-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
### Secret Reference Architecture
```mermaid
graph TD
subgraph "Publish Flow"
P1["Actions Secret<br/>GIT_PAGES_PUBLISH_TOKEN"]
P2["K8s Secret<br/>git-pages-publish-auth"]
P1 -->|token| TRAEFIK
P2 -->|htpasswd| TRAEFIK
TRAEFIK["Traefik BasicAuth"]
end
subgraph "Retention Flow"
R1["K8s Secret<br/>git-pages-retention-gitea"]
R2["Gitea PAT<br/>CI-REPORTS_READ_FOR_RETENTION"]
R1 -->|token| SC["Sidecar"]
SC -->|API auth| GITEA["Gitea API"]
SC -->|read branches| GITEA
end
```
---
## Data Flow
### Flow 1: Julkaisu (Publish)
```mermaid
sequenceDiagram
participant Actions as Gitea Actions
participant Traefik as Traefik
participant K8sAuth as K8s Secret<br/>git-pages-publish-auth
participant K8sToken as K8s Secret<br/>git-pages-publish-token
participant GP as git-pages
Note over Actions: 1. Lue plaintext-token
Actions->>K8sToken: lue token-avain
K8sToken-->>Actions: plaintext token
Note over Actions: 2. Lähettää raportin
Actions->>Traefik: PUT / + BasicAuth<br/>publish:TOKEN + repo-url
Traefik->>K8sAuth: lue users (htpasswd)
K8sAuth-->>Traefik: publish:$apr1$...
alt Token match
Traefik->>GP: välitä
GP-->>Traefik: 200 OK
Traefik-->>Actions: 200 OK
else Token ei match
Traefik-->>Actions: 401 Unauthorized
end
```
**Kaksi secretiä (Traefik-yhteensopivuus):**
- `git-pages-publish-auth` = `users` (htpasswd, Traefik käyttää)
- `git-pages-publish-token` = `token` (plaintext, luetaan Giteaan viedessä)
---
### Flow 2: Luku (Read)
```mermaid
sequenceDiagram
participant Browser as Selain
participant Traefik as Traefik
participant GP as git-pages
Browser->>Traefik: GET /OWNER/REPO/commit/SHA/raportti/index.html
Traefik->>GP: välitä (ei authia)
GP-->>Traefik: HTML
Traefik-->>Browser: HTML
```
GET/HEAD-reitillä ei ole Middlewarea. Luku on julkinen, jos URL tunnetaan.
---
### Flow 3: Retention (Siivous)
```mermaid
sequenceDiagram
participant Sidecar as Retention Sidecar
participant K8sSecret as K8s Secret<br/>git-pages-retention-gitea
participant GiteaAPI as Gitea API
participant GP as git-pages (localhost:3000)
Note over Sidecar: 1. Lue PAT
Sidecar->>K8sSecret: lue token
K8sSecret-->>Sidecar: Gitea PAT
Note over Sidecar: 2. Lue manifest
Sidecar->>GP: GET .git-pages/manifest.json
GP-->>Sidecar: sisällysluettelo
Note over Sidecar: 3. Kysy branch
Sidecar->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH
GiteaAPI-->>Sidecar: 200 / 404
Note over Sidecar: 4. Luo whiteout-tar + PATCH
Sidecar->>GP: PATCH / (whiteout)
GP-->>Sidecar: 200 OK
```
**Huomio:** Retention-PAT:in omistajalla on oltava lukuoikeus KAIKKIIN repoihin,
joista raportteja on PVC:llä.
---
## Troubleshooting
- **"secret not found"** — luiko secretit ennen Helm-asennusta?
- **"401 Unauthorized"** — onko Gitea Actions secret oikea?
- **"found 2 elements for secret"** — Traefik vaatii single-key secretin. Varmista että `git-pages-publish-auth` sisältää vain `users`-avaimen.
- **"token hukkuu"** — generoi uusi token (Vaihe 3) ja päivitä molemmat publish-secretit:
```bash
# 1. Generoi uusi
GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)"
# 2. Päivitä K8s secrets (molemmat)
NS=git-pages
kubectl delete secret git-pages-publish-auth -n "$NS"
kubectl delete secret git-pages-publish-token -n "$NS"
kubectl create secret generic git-pages-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$GIT_PAGES_PUBLISH_TOKEN")" \
-n "$NS"
kubectl create secret generic git-pages-publish-token \
--from-literal=token="$GIT_PAGES_PUBLISH_TOKEN" \
-n "$NS"
# 3. Päivitä Gitea Actions secret jokaisessa repoissa (luke README:stä)
```
## Automatisointi: useamman repon salaisuuden lisääminen
Jos repoja on monta, voit käyttää Gitea API:ta (vaatii admin-tokenin):
```bash
ADMIN_TOKEN="<gitea-admin-token>"
NS=git-pages
# Lue plaintext-token erillisestä secretistä
TOKEN=$(kubectl get secret git-pages-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
for repo in "owner/repo1" "owner/repo2" "owner/repo3"; do
curl -X POST "https://gitea.example.com/api/v1/repos/$repo/actions/secrets" \
-H "Authorization: token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"GIT_PAGES_PUBLISH_TOKEN\",\"data\":\"$TOKEN\"}"
done
```
Tai `tea` CLI:lla (Gitea:n virallinen CLI):
```bash
tea actions secrets add --repo owner/repo1 GIT_PAGES_PUBLISH_TOKEN "$TOKEN"
tea actions secrets add --repo owner/repo2 GIT_PAGES_PUBLISH_TOKEN "$TOKEN"
```
+81
View File
@@ -0,0 +1,81 @@
# Tech Stack — git-pages
> Mitä teknologioita `git-pages/` Helm chart käyttää ja edellyttää. Tämä dokumentti koskee vain
> `git-pages/`-alikansiota monorepossa — ei juuren `gitea-ci-library`-kirjastoa.
---
## Sovellus
| Teknologia | Versio | Käyttö |
|---|---|---|
| **git-pages** (Codeberg) | `0.9.1` | Staattinen sisältö, apex index-site (`/.index`), HTTP PATCH/PUT -julkaisu |
| **Filesystem storage** | — | Sisältö PVC:llä (`/app/data`) |
| **TOML** | — | Sovellusconfig ConfigMapissa (`config.toml`) |
Image: `codeberg.org/git-pages/git-pages:0.9.1` (ei `v`-etuliitettä tagissa).
Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei forge-authia.
---
## Alusta ja verkko
| Teknologia | Versio / minimi | Käyttö |
|---|---|---|
| **Kubernetes** | 1.24+ | Deployment, Service, PVC, Secret |
| **Helm** | v3 | Chart asennus ja päivitys |
| **Traefik** | CRD `traefik.io/v1alpha1` | IngressRoute, Middleware (`basicAuth`, HTTPS-redirect) |
| **cert-manager** | — | TLS-sertifikaatti (`git-pages-tls`) |
---
## Pysyvyys
| Teknologia | Käyttö |
|---|---|
| **PersistentVolumeClaim** | Raporttisisältö (`ReadWriteOnce`) |
| **storageClass** | Instance-kohtainen (`dev-values.yaml`) |
---
## Esiehdot (klusteri)
| Resurssi | Lähde | Dokumentti |
|---|---|---|
| `git-pages-publish-auth` | Manuaalinen Secret | [secrets.md](secrets.md) |
| `git-pages-tls` | cert-manager Certificate | Automaattinen asennuksessa |
| `ClusterIssuer` | Klusteri (esim. `letsencrypt-prod`) | `dev-values.yaml` |
---
## Asennustyökalut (operaattori)
| Työkalu | Käyttö |
|---|---|
| **kubectl** | Secretin luonti |
| **openssl** | Publish-tokenin generointi (`rand -base64 24`) |
| **Docker** (`httpd:2-alpine`) | htpasswd-rivin generointi |
---
## Konfiguraatio
| Tiedosto | Kerros | Sisältö |
|---|---|---|
| `values.yaml` | Chart-vakiot | Image, resurssit, Traefik entrypointit |
| `{env}-values.yaml` | Instanssi | Host, issuer, PVC koko/storageClass |
Esimerkki: `helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml`
---
## Mitä EI käytetä
| Teknologia | Syy |
|---|---|
| **Gitea `pages`-branch** | Ei Gitea git-pages -integraatiota |
| **deadnews/gitea-pages** | Ei Gitea API -hakua |
| **MinIO / S3** | Erillinen raporttivarasto — ei tämän chartin scope |
| **Gitea forge-auth / PAT** | Julkaisu BasicAuth-tokenilla (`git-pages-publish-auth`) |
| **Helm-managed publish Secret** | `publishAuth.create: false` — secret manuaalisesti |
+197
View File
@@ -0,0 +1,197 @@
#!/usr/bin/env bash
set -eo pipefail
PAGES_URL="${PAGES_URL:-http://localhost:3000}"
PAGES_HOST="${PAGES_HOST:?PAGES_HOST is required}"
CONFIG="${RETENTION_CONFIG:-/etc/retention/retention.json}"
GITEA_API_URL="${GITEA_API_URL:-}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
curl_with_host() {
curl -sS -H "Host: ${PAGES_HOST}" "$@"
}
[ -f "$CONFIG" ] || { echo "ERROR: config missing: $CONFIG" >&2; exit 1; }
BRANCH_CACHE=""
branch_exists() {
local owner="$1" repo="$2" branch="$3" key="${owner}/${repo}/${branch}"
local status
[ -z "$GITEA_API_URL" ] && return 0
[ -z "$GITEA_TOKEN" ] && return 0
if grep -q "^${key}$" <<< "$BRANCH_CACHE" 2>/dev/null; then
return 0
fi
status=$(curl -sS -o /dev/null -w "%{http_code}" \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_API_URL}/api/v1/repos/${owner}/${repo}/branches/${branch}" 2>/dev/null || echo "000")
if [ "$status" = "200" ]; then
BRANCH_CACHE="${BRANCH_CACHE}${key}"$'\n'
return 0
fi
return 1
}
default_max_age=$(jq -r '.branches.default.maxAgeDays // 90' "$CONFIG")
default_keep_min=$(jq -r '.branches.default.keepMin // 5' "$CONFIG")
rule_max_age() {
local branch="$1" v
v=$(jq -r --arg b "$branch" '.branches[$b].maxAgeDays // empty' "$CONFIG")
[ -n "$v" ] && echo "$v" || echo "$default_max_age"
}
rule_keep_min() {
local branch="$1" v
v=$(jq -r --arg b "$branch" '.branches[$b].keepMin // empty' "$CONFIG")
[ -n "$v" ] && echo "$v" || echo "$default_keep_min"
}
age_days() {
local published="$1" epoch_pub now
epoch_pub=$(date -u -d "$published" +%s 2>/dev/null || echo 0)
[ "$epoch_pub" -eq 0 ] && echo 99999 && return
now=$(date -u +%s)
echo $(( (now - epoch_pub) / 86400 ))
}
parse_path() {
local rel="$1"
OWNER="${rel%%/*}"
rest="${rel#*/}"
REPO="${rest%%/*}"
}
echo "Fetching manifest from ${PAGES_URL}/.git-pages/manifest.json"
MANIFEST=$(curl_with_host "${PAGES_URL}/.git-pages/manifest.json")
echo "Manifest loaded"
META_PATHS=$(echo "$MANIFEST" | jq -r '.contents | to_entries[] | select(.key | test("/reports/")) | select(.key | endswith("/.meta")) | .key' 2>/dev/null || true)
if [ -z "$META_PATHS" ]; then
echo "No .meta files found under /reports/ — nothing to clean"
exit 0
fi
echo ""
echo "=== Phase 1: collect reports ==="
declare -a REPORTS
while IFS= read -r meta_path; do
report_dir=$(dirname "$meta_path")
parse_path "$report_dir"
meta_content=$(curl_with_host "${PAGES_URL}/${meta_path}" 2>/dev/null || true)
[ -n "$meta_content" ] || { echo " WARN: could not fetch $meta_path"; continue; }
branch=$(echo "$meta_content" | jq -r '.branch // empty' 2>/dev/null || true)
published=$(echo "$meta_content" | jq -r '.published_at // empty' 2>/dev/null || true)
[ -n "$branch" ] || { echo " WARN: no branch in $meta_path"; continue; }
[ -n "$published" ] || { echo " WARN: no published_at in $meta_path"; continue; }
days=$(age_days "$published")
REPORTS+=("${report_dir}|${OWNER}|${REPO}|${branch}|${days}")
echo " ${OWNER}/${REPO} branch=${branch} age=${days}d"
done <<< "$META_PATHS"
[ "${#REPORTS[@]}" -eq 0 ] && { echo "No actionable reports"; exit 0; }
echo ""
echo "=== Phase 2: check branches in Gitea ==="
declare -a TO_DELETE
declare -a KEEP
for entry in "${REPORTS[@]}"; do
IFS='|' read -r dir owner repo branch days <<< "$entry"
if [ -n "$GITEA_API_URL" ] && [ -n "$GITEA_TOKEN" ]; then
if branch_exists "$owner" "$repo" "$branch"; then
echo " BRANCH EXISTS: ${owner}/${repo}/${branch}"
KEEP+=("${dir}|${owner}|${repo}|${branch}|${days}")
else
echo " BRANCH DELETED: ${owner}/${repo}/${branch} -> DELETE"
TO_DELETE+=("$dir")
fi
else
KEEP+=("${dir}|${owner}|${repo}|${branch}|${days}")
fi
done
echo ""
echo "=== Phase 3: apply retention rules to remaining reports ==="
if [ "${#KEEP[@]}" -gt 0 ]; then
IFS=$'\n'
for entry in $(printf '%s\n' "${KEEP[@]}" | sort -t'|' -k4,4 -k5,5rn); do
IFS='|' read -r dir owner repo branch days <<< "$entry"
max_age=$(rule_max_age "$branch")
keep_min=$(rule_keep_min "$branch")
if [ "$days" -gt "$max_age" ]; then
echo " DELETE: ${dir} (age ${days}d > maxAge ${max_age}d, branch ${branch})"
TO_DELETE+=("$dir")
continue
fi
key="${branch}"
count="${BRANCH_COUNTS[$key]:-0}"
count=$((count + 1))
BRANCH_COUNTS["$key"]=$count
if [ "$count" -gt "$keep_min" ]; then
echo " DELETE: ${dir} (kept ${keep_min}/${count}, exceeds keepMin, branch ${branch})"
TO_DELETE+=("$dir")
fi
done
unset IFS
fi
if [ "${#TO_DELETE[@]}" -eq 0 ]; then
echo "Nothing to delete"
exit 0
fi
echo ""
echo "=== Phase 4: whiteout deletion ==="
echo "Creating whiteout tar for ${#TO_DELETE[@]} report(s)..."
WHITEOUT_TAR=$(mktemp)
trap 'rm -f "$WHITEOUT_TAR"' EXIT
python3 -c "
import tarfile, sys
tar = tarfile.open(name='${WHITEOUT_TAR}', mode='w')
dirs = set()
for d in sys.argv[1:]:
dirs.add(d.strip())
tarinfo = tarfile.TarInfo()
tarinfo.type = tarfile.CHRTYPE
tarinfo.devmajor = 0
tarinfo.devminor = 0
for d in sorted(dirs, key=len, reverse=True):
info = tarinfo
info.name = d
tar.addfile(info)
tar.close()
" "${TO_DELETE[@]}"
echo "Patching ${PAGES_URL}/ with whiteout tar..."
HTTP_CODE=$(curl_with_host -X PATCH "${PAGES_URL}/" \
-H "Content-Type: application/x-tar" \
-H "Atomic: no" \
--data-binary @"${WHITEOUT_TAR}" \
-w "%{http_code}" \
-o /dev/null)
echo "HTTP $HTTP_CODE"
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then
echo "Retention cleanup finished."
else
echo "ERROR: retention HTTP ${HTTP_CODE}" >&2
exit 1
fi
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Scale down git-pages, run PVC cleanup (RWO), scale back up.
set -euo pipefail
NAMESPACE="${NAMESPACE:?NAMESPACE is required}"
DEPLOYMENT="${DEPLOYMENT:?DEPLOYMENT is required}"
echo "Scaling ${DEPLOYMENT} to 0..."
kubectl scale "deployment/${DEPLOYMENT}" --replicas=0 -n "$NAMESPACE"
kubectl wait --for=delete pod \
-l "app.kubernetes.io/name=git-pages,app.kubernetes.io/instance=${INSTANCE}" \
-n "$NAMESPACE" --timeout=180s
/scripts/retention-cleanup.sh
echo "Scaling ${DEPLOYMENT} to 1..."
kubectl scale "deployment/${DEPLOYMENT}" --replicas=1 -n "$NAMESPACE"
echo "Retention job done."
+16
View File
@@ -0,0 +1,16 @@
git-pages installed.
See docs/secrets.md for secret prerequisites (K8s + Gitea Actions).
Install:
helm upgrade --install git-pages ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml
Host: https://{{ .Values.ingress.host }}
Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/reports/{sha8}/index.html
Publish (CI):
PATCH https://{{ .Values.ingress.host }}/
Authorization: Basic publish:<GIT_PAGES_PUBLISH_TOKEN>
Upgrade: helm upgrade {{ .Release.Name }} ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml
Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }}
+39
View File
@@ -0,0 +1,39 @@
{{- define "git-pages.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "git-pages.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "git-pages.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "git-pages.labels" -}}
helm.sh/chart: {{ include "git-pages.chart" . }}
{{ include "git-pages.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "git-pages.selectorLabels" -}}
app.kubernetes.io/name: {{ include "git-pages.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "git-pages.componentLabels" -}}
{{ include "git-pages.labels" . }}
app.kubernetes.io/component: pages-server
{{- end }}
+15
View File
@@ -0,0 +1,15 @@
{{- if and .Values.ingress.enabled .Values.certificate.enabled }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ include "git-pages.fullname" . }}-tls
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
secretName: git-pages-tls
dnsNames:
- {{ .Values.ingress.host | quote }}
issuerRef:
name: {{ .Values.certificate.issuerRef.name }}
kind: {{ .Values.certificate.issuerRef.kind }}
{{- end }}
+20
View File
@@ -0,0 +1,20 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "git-pages.fullname" . }}-config
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
data:
config.toml: |
log-format = "text"
[server]
pages = "tcp/:3000"
caddy = "-"
metrics = "tcp/:3002"
[storage]
type = "fs"
[storage.fs]
root = "/app/data"
+120
View File
@@ -0,0 +1,120 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "git-pages.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "git-pages.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "git-pages.componentLabels" . | nindent 8 }}
spec:
securityContext:
fsGroup: 1000
containers:
- name: git-pages
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- git-pages
args:
- -config
- /etc/git-pages/config.toml
{{- if .Values.pagesInsecure }}
env:
- name: PAGES_INSECURE
value: "1"
{{- end }}
ports:
- name: http
containerPort: 3000
protocol: TCP
- name: metrics
containerPort: 3002
protocol: TCP
volumeMounts:
- name: config
mountPath: /etc/git-pages
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 10
periodSeconds: 20
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if and .Values.persistence.enabled .Values.retention.enabled (eq .Values.retention.mode "sidecar") }}
- name: retention
image: "{{ .Values.retention.image.repository }}:{{ .Values.retention.image.tag }}"
imagePullPolicy: {{ .Values.retention.image.pullPolicy }}
securityContext:
runAsUser: 0
command:
- bash
- -c
- |
set -euo pipefail
echo "Retention sidecar: installing deps..."
apt-get update -qq
apt-get install -y --no-install-recommends curl jq python3 >/dev/null
echo "Retention sidecar: ready"
while true; do
/scripts/retention-cleanup.sh
echo "Retention sidecar: next run in 24h"
sleep 86400
done
env:
- name: PAGES_URL
value: http://localhost:3000
- name: PAGES_HOST
value: {{ .Values.ingress.host | quote }}
- name: RETENTION_CONFIG
value: /etc/retention/retention.json
- name: GITEA_API_URL
value: {{ .Values.retention.giteaApiUrl | quote }}
- name: GITEA_TOKEN
valueFrom:
secretKeyRef:
name: git-pages-retention-gitea
key: token
volumeMounts:
- name: retention-scripts
mountPath: /scripts
- name: retention-config
mountPath: /etc/retention
{{- end }}
volumes:
- name: config
configMap:
name: {{ include "git-pages.fullname" . }}-config
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "git-pages.fullname" . }}-data
{{- end }}
{{- if and .Values.persistence.enabled .Values.retention.enabled (eq .Values.retention.mode "sidecar") }}
- name: retention-scripts
configMap:
name: git-pages-retention
defaultMode: 0755
- name: retention-config
configMap:
name: git-pages-retention
items:
- key: retention.json
path: retention.json
{{- end }}
+48
View File
@@ -0,0 +1,48 @@
{{- if .Values.ingress.enabled }}
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: {{ include "git-pages.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
entryPoints:
- {{ .Values.ingress.entryPoints.websecure }}
routes:
- match: >-
Host(`{{ .Values.ingress.host }}`) &&
(Method(`PATCH`) || Method(`PUT`))
kind: Rule
middlewares:
- name: {{ include "git-pages.fullname" . }}-publish-auth
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
- match: Host(`{{ .Values.ingress.host }}`) && (Method(`GET`) || Method(`HEAD`))
kind: Rule
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
tls:
secretName: git-pages-tls
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: {{ include "git-pages.fullname" . }}-http
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
entryPoints:
- {{ .Values.ingress.entryPoints.web }}
routes:
- match: >-
Host(`{{ .Values.ingress.host }}`) &&
!PathPrefix(`/.well-known/acme-challenge/`)
kind: Rule
middlewares:
- name: {{ include "git-pages.fullname" . }}-https-redirect
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
{{- end }}
+48
View File
@@ -0,0 +1,48 @@
{{- if .Values.initJob.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "git-pages.fullname" . }}-init
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
annotations:
"helm.sh/hook": post-install, post-upgrade
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
spec:
backoffLimit: 5
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "git-pages.name" . }}-init
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
restartPolicy: Never
containers:
- name: init
image: "{{ .Values.initJob.image.repository }}:{{ .Values.initJob.image.tag }}"
imagePullPolicy: {{ .Values.initJob.image.pullPolicy }}
command:
- bash
- -c
- |
set -euo pipefail
apt-get update -qq && apt-get install -y -qq curl tar >/dev/null
echo "Init: waiting for git-pages..."
until curl -sf \
-H "Host: {{ .Values.ingress.host }}" \
-o /dev/null "http://git-pages:3000/.git-pages/health"
do sleep 2; done
echo "Init: creating placeholder site..."
WORK=$(mktemp -d)
mkdir -p "$WORK/__init__"
echo "initialized" > "$WORK/__init__/index.html"
tar cf /tmp/init.tar -C "$WORK" __init__
curl -sf -X PUT "http://git-pages:3000/" \
-H "Host: {{ .Values.ingress.host }}" \
-H "Content-Type: application/x-tar" \
--data-binary @/tmp/init.tar -o /dev/null
echo "Init: done"
env:
- name: PAGES_INSECURE
value: "1"
{{- end }}
+22
View File
@@ -0,0 +1,22 @@
{{- if .Values.ingress.enabled }}
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "git-pages.fullname" . }}-publish-auth
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
basicAuth:
secret: git-pages-publish-auth
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "git-pages.fullname" . }}-https-redirect
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
redirectScheme:
scheme: https
permanent: true
{{- end }}
@@ -0,0 +1,12 @@
{{- if and .Values.publishAuth.create .Values.publishAuth.htpasswdUsers }}
apiVersion: v1
kind: Secret
metadata:
name: git-pages-publish-auth
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
type: Opaque
stringData:
users: |
{{ .Values.publishAuth.htpasswdUsers }}
{{- end }}
+17
View File
@@ -0,0 +1,17 @@
{{- if .Values.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "git-pages.fullname" . }}-data
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.accessMode }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}
@@ -0,0 +1,15 @@
{{- if and .Values.persistence.enabled .Values.retention.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: git-pages-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
data:
retention.json: |
{{- .Values.retention.rules | toJson | nindent 4 }}
retention-cleanup.sh: |
{{- .Files.Get "files/retention-cleanup.sh" | nindent 4 }}
retention-run.sh: |
{{- .Files.Get "files/retention-run.sh" | nindent 4 }}
{{- end }}
@@ -0,0 +1,78 @@
{{- if and .Values.persistence.enabled .Values.retention.enabled (eq .Values.retention.mode "cronjob") }}
apiVersion: batch/v1
kind: CronJob
metadata:
name: git-pages-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
schedule: {{ .Values.retention.schedule | quote }}
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 1
template:
metadata:
labels:
app.kubernetes.io/name: git-pages-retention
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: git-pages-retention
restartPolicy: OnFailure
containers:
- name: retention
image: "{{ .Values.retention.image.repository }}:{{ .Values.retention.image.tag }}"
imagePullPolicy: {{ .Values.retention.image.pullPolicy }}
securityContext:
runAsUser: 0
command:
- bash
- -c
- |
set -euo pipefail
apt-get update -qq
apt-get install -y --no-install-recommends curl jq >/dev/null
chmod +x /scripts/retention-run.sh /scripts/retention-cleanup.sh
/scripts/retention-run.sh
env:
- name: NAMESPACE
value: {{ .Release.Namespace | quote }}
- name: DEPLOYMENT
value: {{ include "git-pages.fullname" . | quote }}
- name: INSTANCE
value: {{ .Release.Name | quote }}
- name: DATA_ROOT
value: /app/data
- name: RETENTION_CONFIG
value: /etc/retention/retention.json
- name: GITEA_API_URL
value: {{ required "retention.giteaApiUrl is required" .Values.retention.giteaApiUrl | quote }}
- name: GITEA_TOKEN
valueFrom:
secretKeyRef:
name: git-pages-retention-gitea
key: token
volumeMounts:
- name: data
mountPath: /app/data
- name: scripts
mountPath: /scripts
- name: config
mountPath: /etc/retention
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ include "git-pages.fullname" . }}-data
- name: scripts
configMap:
name: git-pages-retention
defaultMode: 0755
- name: config
configMap:
name: git-pages-retention
items:
- key: retention.json
path: retention.json
{{- end }}
+37
View File
@@ -0,0 +1,37 @@
{{- if and .Values.persistence.enabled .Values.retention.enabled (eq .Values.retention.mode "cronjob") }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: git-pages-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: git-pages-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale"]
verbs: ["get", "patch", "update"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: git-pages-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: git-pages-retention
subjects:
- kind: ServiceAccount
name: git-pages-retention
namespace: {{ .Release.Namespace }}
{{- end }}
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "git-pages.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
{{- include "git-pages.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
+67
View File
@@ -0,0 +1,67 @@
# Constants — shared across all instances. Do not put per-env values here.
# Per instance: use {env}-values.yaml (e.g. dev-values.yaml):
# helm install git-pages ./git-pages -n git-pages -f dev-values.yaml
nameOverride: ""
fullnameOverride: ""
image:
repository: codeberg.org/git-pages/git-pages
tag: "0.9.1"
pullPolicy: IfNotPresent
pagesInsecure: true
service:
type: ClusterIP
port: 3000
persistence:
enabled: true
accessMode: ReadWriteOnce
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
ingress:
enabled: true
entryPoints:
websecure: websecure
web: web
certificate:
enabled: true
# Post-install init job: creates placeholder site so .index exists.
# Consumers can use PATCH directly without PUT fallback.
initJob:
enabled: true
image:
repository: debian
tag: bookworm-slim
pullPolicy: IfNotPresent
# Optional Helm-managed secret — prefer manual create (see docs/secrets.md).
publishAuth:
create: false
htpasswdUsers: ""
retention:
enabled: false
mode: sidecar
schedule: "0 3 * * *"
image:
repository: debian
tag: bookworm-slim
pullPolicy: IfNotPresent
giteaApiUrl: ""
rules:
branches:
default:
maxAgeDays: 90
keepMin: 5
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# Publish a report directory to git-pages apex index-site via Traefik (BasicAuth).
# Public URL: https://{PAGES_HOST}/{owner}/{repo}/reports/{sha8}/index.html
set -euo pipefail
REPORT_DIR="${1:-}"
PAGES_HOST="${PAGES_HOST:-}"
GIT_PAGES_PUBLISH_URL="${GIT_PAGES_PUBLISH_URL:-https://pages.helm-dev.keskikuja.site}"
GIT_PAGES_PUBLISH_TOKEN="${GIT_PAGES_PUBLISH_TOKEN:-}"
GIT_PAGES_PUBLISH_USER="${GIT_PAGES_PUBLISH_USER:-publish}"
REPO_SLUG="${GITHUB_REPOSITORY:-}"
[ -n "$REPORT_DIR" ] || { echo "ERROR: report directory argument required" >&2; exit 1; }
[ -d "$REPORT_DIR" ] || { echo "ERROR: not a directory: $REPORT_DIR" >&2; exit 1; }
[ -n "$PAGES_HOST" ] || { echo "ERROR: PAGES_HOST is not set" >&2; exit 1; }
[ -n "$GIT_PAGES_PUBLISH_TOKEN" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "$REPO_SLUG" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; }
[ -n "${GITHUB_SHA:-}" ] || { echo "ERROR: GITHUB_SHA is not set" >&2; exit 1; }
OWNER="${REPO_SLUG%%/*}"
REPO="${REPO_SLUG##*/}"
SHA8="${GITHUB_SHA:0:8}"
REPORT_BASE="https://${PAGES_HOST}/${OWNER}/${REPO}/reports/${SHA8}"
PUBLISH_BASE="${GIT_PAGES_PUBLISH_URL%/}"
PUBLISH_SITE_URL="${PUBLISH_BASE}/"
WORK=$(mktemp -d)
TAR=$(mktemp)
trap 'rm -rf "$WORK" "$TAR"' EXIT
RELPATH="${REPORT_DIR#reports/${SHA8}/}"
if [ "$RELPATH" != "$REPORT_DIR" ] && [ -n "$RELPATH" ]; then
TARGET="$WORK/${OWNER}/${REPO}/reports/${SHA8}/${RELPATH}"
else
TARGET="$WORK/${OWNER}/${REPO}/reports/${SHA8}"
fi
mkdir -p "$TARGET"
cp -a "$REPORT_DIR/." "$TARGET/"
cat > "$WORK/${OWNER}/${REPO}/reports/${SHA8}/.meta" <<EOF
{"branch":"${GITHUB_REF_NAME:-}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF
find "$WORK/$OWNER" \( -type f -o -type l \) -print | sed "s|^${WORK}/||" | tar -cf "$TAR" -C "$WORK" -T -
publish() {
local method="$1"
curl -sS -X "$method" "$PUBLISH_SITE_URL" \
-u "${GIT_PAGES_PUBLISH_USER}:${GIT_PAGES_PUBLISH_TOKEN}" \
-H "Content-Type: application/x-tar" \
-H "Atomic: no" \
-H "Create-Parents: yes" \
--data-binary @"$TAR" \
-o /tmp/git-pages-publish-response.txt \
-w "%{http_code}"
}
HTTP_CODE=$(publish PATCH)
case "$HTTP_CODE" in
200|201|204) ;;
*)
echo "ERROR: git-pages publish HTTP ${HTTP_CODE}" >&2
cat /tmp/git-pages-publish-response.txt >&2
exit 1
;;
esac
echo "$REPORT_BASE"
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Vie raportit git-pagesiin + commit-status linkillä
set -euo pipefail
REPORT_DIR="${1:-reports}"
PAGES_HOST="${PAGES_HOST:-ci-reports.helm-dev.keskikuja.site}"
REPORT_URL=$(bash "$(dirname $0)/publish-git-pages.sh" "$REPORT_DIR")
echo "Published: $REPORT_URL"
bash "$(dirname $0)/report-status.sh" success "Reports published" "$REPORT_URL" ci-report
@@ -30,7 +30,7 @@ function envBlock() {
} }
function setupMock(seqJson) { function setupMock(seqJson) {
execSync('pkill -9 -f mock-server 2>/dev/null || true', { stdio: 'ignore' }); execSync('lsof -ti :18080 2>/dev/null | xargs -r kill -9 2>/dev/null || true', { stdio: 'ignore' });
execSync('sleep 0.4', { stdio: 'ignore' }); execSync('sleep 0.4', { stdio: 'ignore' });
const seqFile = path.join(os.tmpdir(), `cucumber_seq_${Date.now()}.json`); const seqFile = path.join(os.tmpdir(), `cucumber_seq_${Date.now()}.json`);
@@ -0,0 +1,449 @@
# ci-engine.yml — moottori ja consumerin job-tiedostot
> **Tila:** DRAFT — työstettävä esimerkki, ei lopullinen suunnitelma.
> **Liittyy:** ticket 0006, [analysis/ci-flow-values-vs-native-config.md](../../docs/analysis/ci-flow-values-vs-native-config.md)
Esimerkkiprojekti: `temperature-store` (Java/Maven-mikropalvelu)
Provider (`gitea-ci-library`) tarjoaa **vain yhden tiedoston**: `ci-engine.yml`.
Consumer tuo omat job-tiedostonsa ja datansa. Moottori päättelee itse mitä ajaa.
---
## Tiedostot ja roolit
```
temperature-store/ ← CONSUMER
├── .gitea/workflows/
│ └── ci.yml ← [A] Kutsuja: molemmat jobit (feature + master) yhdessä
├── ci-flow-values.yaml ← [B] Projektin data — uniikki per projekti
└── pom.xml
gitea-ci-library/ ← PROVIDER (= MOOTTORI)
├── .gitea/workflows/
│ └── ci-engine.yml ← [C] AINOA workflow — kaikki pipelinetyypit
├── scripts/
│ ├── report-status.sh
│ ├── dispatch-workflow.sh
│ ├── push-reports.sh
│ └── tag-commit.sh
└── ci-flow-values.yaml ← Provider-testaus (dogfood — kirjasto testaa itsensä)
```
**Roolit:**
| Tiedosto | Omistaja | Rooli |
|----------|----------|-------|
| `ci-engine.yml` | **Provider** | Moottori: build-logiikka, konffaus, komennot |
| `ci.yml` | **Consumer** | Kutsuja: molemmat jobit yhdessä tiedostossa, `if:` valitsee |
| `ci-flow-values.yaml` | **Consumer** | Data: ekosysteemi, docker-nimi, test-flow jne |
| `scripts/*.sh` | **Provider** | Jaetut työkalut |
---
## [A] Consumerin `.gitea/workflows/ci.yml` — rooli: **kutsuja**
```yaml
# temperature-store/.gitea/workflows/ci.yml
#
# Rooli: KUTSUJA.
# - Valitsee branchin perusteella oikean jobin (feature / master)
# - Välittää KONTIT moottorille (moottori ei tunne yhtään konttia)
# - Välittää config-filen polun moottorille
name: CI
on:
push:
branches: ["**"]
workflow_dispatch:
jobs:
feature:
if: github.ref != 'refs/heads/master'
uses: org/gitea-ci-library/.gitea/workflows/ci-engine.yml@v1
secrets: inherit
with:
config-file: ci-flow-values.yaml
maven-image: maven:3.9-eclipse-temurin-21 # ← Consumer omistaa kontit
master:
if: github.ref == 'refs/heads/master'
uses: org/gitea-ci-library/.gitea/workflows/ci-engine.yml@v1
secrets: inherit
with:
config-file: ci-flow-values.yaml
maven-image: maven:3.9-eclipse-temurin-21
dind-image: docker:26-dind # ← Master tarvitsee DinD:n
```
**Mitä tässä tapahtuu:**
1. Push mihin tahansa branchiin → `ci.yml` triggeröityy
2. `if: github.ref != 'refs/heads/master'``feature`-job
3. `if: github.ref == 'refs/heads/master'``master`-job
4. Molemmat kutsuvat samaa moottoria (`ci-engine.yml@v1`) samoilla parametreilla
5. Moottori päättelee itse branchista mitä pipelinea ajaa
**Miksi yksi tiedosto, kaksi jobia:**
- Yksi paikka katsoa mitä CI tekee — `ci.yml` on koko projektin CI-määrittely
- `if:`-ehto on riittävä erottelemaan featuren ja masterin
- Kopioitavissa sellaisenaan — consumer ei muokkaa mitään
---
## [B] Consumerin `ci-flow-values.yaml` — rooli: **projektidata**
```yaml
# temperature-store/ci-flow-values.yaml
#
# Rooli: DATA ("mikä tämä projekti on, mitä se tarvitsee")
# Tämä tiedosto on jokaisessa projektissa ERI.
# Kaikki non-secret-arvot tänne. Tokenit → Gitea org secrets.
build:
ecosystem: maven # maven | gradle | npm — moottori valitsee kontin ja komennot
docker:
registry: gitea
imageName: temperature-store
sonarqube:
url: https://sonar.example.com
projectKey: temperature-store
deployment:
projectFolder: microservices
fileName: values-{.environment}.yaml
property: container.version
test-flow:
- deploy: development
wait: true
- test:
name: "integration fast"
environment: integration
repo: tests/integration
workflow: test.yml
ref: main
tags: "@temperature and not @slow"
```
**Mitä `ci-engine.yml` lukee tästä (MVP, tiketti 0006):**
- `build.ecosystem` — määrittää kontin ja komennot
**Non-secret vs secret:**
- Tänne: ekosysteemi, image-nimi, URL:t, test-flow — versioitavaa
- Gitea org secrets: `GITEA_TOKEN`, `SONAR_TOKEN`, `DEPLOY_TOKEN` — salaisuudet
---
## [C] Providerin `ci-engine.yml` — rooli: **moottori**
Providerin **ainoa** workflow. Moottori koostuu kolmesta kerroksesta:
| Kerros | Mistä tulee | Sisältö |
|--------|-------------|---------|
| **Kontit** | `with:` parametrit consumerilta | `maven-image`, `dind-image`, … — moottori ei tunne yhtään konttia |
| **Pipeline runko** | `ci-engine.yml` sisäinen | Feature-skeleton: start → test → coverage → end<br>Master-skeleton: start → build → deploy → [test-flow] → end |
| **Test plan + data** | `ci-flow-values.yaml` | `build.ecosystem` → komennot<br>`test-flow[]` → test plan (vain master)<br>`docker.*`, `deployment.*` (vain master) |
```yaml
# gitea-ci-library/.gitea/workflows/ci-engine.yml
#
# MOOTTORI — kolme kerrosta:
# 1. Kontit: ${{ inputs.maven-image }} jne (consumerilta)
# 2. Pipeline runko: job-graafi erikseen featurelle ja masterille (täällä)
# 3. Data: ci-flow-values.yaml → ekosysteemi, test plan, docker (consumerilta)
name: CI Engine
on:
workflow_call:
inputs:
config-file:
required: true
type: string # ← Polku ci-flow-values.yaml:aan
maven-image: # ← KONTTI: consumerilta
required: false
type: string
dind-image: # ← KONTTI: consumerilta (vain master)
required: false
type: string
node-image: # ← KONTTI: consumerilta (vain npm)
required: false
type: string
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: false
jobs:
# ═══════════════════════════════════════════════════════════════
# YHTEINEN: konfiguraation luku (data-kerros)
# Raportoi: pending → config luettu → success
# ═══════════════════════════════════════════════════════════════
start:
runs-on: ubuntu-latest
outputs:
ecosystem: ${{ steps.read-config.outputs.ecosystem }}
steps:
- uses: actions/checkout@v4
- name: Report pipeline start
run: |
bash scripts/report-status.sh \
"pending" "Pipeline starting" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/start"
- name: Read project config
id: read-config
run: |
ECOSYSTEM=$(yq '.build.ecosystem' "${{ inputs.config-file }}")
[ -z "$ECOSYSTEM" ] || [ "$ECOSYSTEM" = "null" ] && \
echo "ERROR: build.ecosystem not set" >&2 && exit 1
echo "ecosystem=$ECOSYSTEM" >> $GITHUB_OUTPUT
- name: Report pipeline started
if: success()
run: |
bash scripts/report-status.sh \
"success" "Pipeline started" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/start"
- name: Report start failure
if: failure()
run: |
bash scripts/report-status.sh \
"failure" "Pipeline start failed" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/start"
# ═══════════════════════════════════════════════════════════════
# FEATURE-SKELETON: testit + coverage
# Ajetaan kun github.ref != refs/heads/master
# Jokainen steppi raportoi: alussa pending, lopussa success/failure
# ═══════════════════════════════════════════════════════════════
unit-test:
needs: start
if: github.ref != 'refs/heads/master'
runs-on: ubuntu-latest
container:
image: ${{ inputs.maven-image }}
steps:
- uses: actions/checkout@v4
- name: Report unit-test start
run: |
bash scripts/report-status.sh \
"pending" "Unit tests running" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/unit-test"
- name: Run unit tests
id: test
env:
ECOSYSTEM: ${{ needs.start.outputs.ecosystem }}
run: |
case "$ECOSYSTEM" in
maven|gradle) mvn test ;;
npm) npm test ;;
esac
- name: Report unit-test success
if: success()
run: |
bash scripts/report-status.sh \
"success" "Unit tests passed" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/unit-test"
- name: Report unit-test failure
if: failure()
run: |
bash scripts/report-status.sh \
"failure" "Unit tests failed" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/unit-test"
code-coverage:
needs: [start, unit-test]
if: github.ref != 'refs/heads/master'
runs-on: ubuntu-latest
container:
image: ${{ inputs.maven-image }}
steps:
- uses: actions/checkout@v4
- name: Report coverage start
run: |
bash scripts/report-status.sh \
"pending" "Coverage running" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/code-coverage"
- name: Run coverage
id: coverage
env:
ECOSYSTEM: ${{ needs.start.outputs.ecosystem }}
run: |
case "$ECOSYSTEM" in
maven) mvn jacoco:report ;;
gradle) gradle jacocoTestReport ;;
npm) npm run coverage ;;
esac
- name: Report coverage success
if: success()
run: |
bash scripts/report-status.sh \
"success" "Coverage report generated" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/code-coverage"
- name: Report coverage failure
if: failure()
run: |
bash scripts/report-status.sh \
"failure" "Coverage failed" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/code-coverage"
# ═══════════════════════════════════════════════════════════════
# MASTER-SKELETON: build + deploy + test plan (tiketti 0009)
# github.ref == refs/heads/master
# Kontti: ${{ inputs.maven-image }} + ${{ inputs.dind-image }}
# Test plan: ci-flow-values.yaml → test-flow[]
# ═══════════════════════════════════════════════════════════════
# build-container:
# needs: start
# if: github.ref == 'refs/heads/master'
# container:
# image: ${{ inputs.dind-image }} ← KONTTI consumerilta
# ...
#
# deploy:
# needs: build-container
# if: github.ref == 'refs/heads/master'
# ...lukee deployment.* configista...
#
# test-flow:
# needs: deploy
# if: github.ref == 'refs/heads/master'
# ...lukee test-flow[] configista... ← TEST PLAN configista
# ═══════════════════════════════════════════════════════════════
# LOPETUS: kaikille yhteinen
# Raportoi: pending → aggregoi tulokset → success/failure
# ═══════════════════════════════════════════════════════════════
end:
needs: [unit-test, code-coverage]
if: always()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Report pipeline ending
run: |
bash scripts/report-status.sh \
"pending" "Pipeline finishing" \
"$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER" \
"ci/end"
- name: Report final status
run: |
BUILD_URL="$GITEA_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_NUMBER"
if [ "${{ needs.unit-test.result }}" = "success" ] && \
[ "${{ needs.code-coverage.result }}" = "success" ]; then
STATE="success"; DESC="All checks passed"
else
STATE="failure"; DESC="Some checks failed"
fi
bash scripts/report-status.sh "$STATE" "$DESC" "$BUILD_URL" "ci/end"
```
**Moottorin kolme kerrosta käytännössä:**
1. **Kontit** (consumerilta `with:` → moottori `inputs:` → job `container.image`)
- Moottori ei sisällä yhtään kontti-imagea
- Consumer päättää versiot: `maven:3.9-eclipse-temurin-21`
2. **Pipeline runko** (moottorin sisäinen job-graafi)
- `if: github.ref != 'refs/heads/master'` → feature-skeleton
- `if: github.ref == 'refs/heads/master'` → master-skeleton (myöhemmät tiketit)
3. **Data** (`ci-flow-values.yaml`)
- `build.ecosystem` → mitä komentoja ajetaan
- `test-flow[]` → test plan (vain master, tiketti 0009)
**Mitä tässä EI ole (myöhemmät tiketit):**
- `push-reports.sh` — raporttien julkaisu MinIO:hon (tiketti 0013)
- `isContainerBuild()` / kontin buildaus (tiketti 0009)
- SonarQube quality gate (tiketti 0009)
- Test flow -ketjutus `test-flow[]`:sta (tiketti 0008)
---
## Yhteenveto
```
┌──────────────────────────────────────────────────────────────────────┐
│ CONSUMER: temperature-store │
│ │
│ ci.yml ci-flow-values.yaml │
│ ┌──────────────────────────┐ ┌────────────────────────┐ │
│ │ jobs: │ │ build: │ │
│ │ feature (if !master) ───┼─┐ │ ecosystem: maven │ │
│ │ with: │ │ │ docker: │ │
│ │ config-file + kontit │ │ │ imageName: ... │ │
│ │ │ │ │ test-flow: [...] │ │
│ │ master (if master) ────┼─┤ └────────────────────────┘ │
│ │ with: │ │ │
│ │ config-file + kontit │ │ │
│ └──────────────────────────┘ │ │
└───────────────────────────────┼───────────────────────────────────────┘
│ with: kontit + config-file
┌──────────────────────────────────────────────────────────────────────┐
│ PROVIDER: gitea-ci-library (ci-engine.yml) │
│ │
│ Kerros 1: KONTIT (consumerilta) │
│ ┌──────────────────────────────────────────┐ │
│ │ inputs: maven-image, dind-image, ... │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Kerros 2: PIPELINE RUNKO (moottorin sisäinen) │
│ ┌──────────────────────────────────────────┐ │
│ │ FEATURE-SKELETON MASTER-SKELETON │ │
│ │ start ──────────────────── start │ │
│ │ ↓ ↓ │ │
│ │ unit-test build-container│ │
│ │ ↓ ↓ │ │
│ │ code-coverage deploy │ │
│ │ ↓ ↓ │ │
│ │ end test-flow[] ───│── Kerros 3: TEST PLAN │
│ │ ↓ │ │
│ │ end │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Kerros 3: DATA (ci-flow-values.yaml) │
│ ┌──────────────────────────────────────────┐ │
│ │ ecosystem → komennot │ │
│ │ test-flow[] → test plan (vain master) │ │
│ │ docker.*, deployment.* (vain master) │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
```
### Päätöstaulukko
| Asia | Sijainti | Kerros |
|------|----------|:--:|
| Kontit (`maven-image`, `dind-image`) | Consumer (`ci.yml` `with:`) | 1 |
| Pipeline runko (job-graafi) | Provider (`ci-engine.yml`) | 2 |
| `build.ecosystem` → komennot | Consumer (`ci-flow-values.yaml`) | 3 |
| `test-flow[]` → test plan | Consumer (`ci-flow-values.yaml`) | 3 |
| `docker.*`, `deployment.*` | Consumer (`ci-flow-values.yaml`) | 3 |
| Skriptit | Provider (`scripts/`) | — |
| Tokenit, salasanat | Gitea org secrets | — |
+282
View File
@@ -0,0 +1,282 @@
# Data flow — periaatetaso
> Tila: DRAFT — kevyt suunnitteludokumentti, muokataan tarpeen mukaan.
> Ei ADR. Ei normatiivinen. Kuvaa periaatteen, ei implementaatiota.
>
> Lähde: [docs/design-rationale.md](../docs/design-rationale.md) — erityisesti periaatteet 1 (commit-status), 3 (konfiguraatio repossa), 5 (raportit MinIO:ssa) ja 7 (cross-repo traceability).
## Tarkoitus
Selventää **kuka omistaa mitä datan** ja **miten se liikkuu** consumer → provider → Gitea -ketjussa.
**Ydinperiaate:** Branch-päätös on consumerin vastuulla. Provider on branch-agnostinen — se saa polun config-tiedostoon ja suorittaa sen määrittämän pipelinen. Consumer valitsee kumman jobin ja minkä pipeline-as-conf -tiedoston ajetaan; engine lukee vain sen mitä `with:` välittää.
---
## Roolit ja tiedostot
| Rooli | Tiedosto(t) | Mitä tietää / mitä tekee |
|-------|-------------|--------------------------|
| **Consumer** | `ci.yml` | Trigger, branch (`if:`), valitsee pipeline-as-conf -tiedoston ja kontit, kutsuu engineä `uses:` |
| **Consumer** | `*-conf.yaml` (pipeline-as-conf) | Projektin data: mitä pipeline ajaa (ekosysteemi, test-flow, docker jne.) — versioitu repossa |
| **Provider** | `ci-engine.yml` | Gitea workflow (`.gitea/workflows/`); `workflow_call` only; pipeline-määrittely |
| **Provider** | jaettu suorituskoodi | Uudelleenkäytettävä logiikka — **ei** Gitea workflow; engine kutsuu steppien sisällä |
| **Gitea org** | secrets | Tokenit, salasanat — ei koskaan repoon; `secrets: inherit` |
| **Gitea org** | variables (kapea poikkeus) | Org-laajuiset infra-endpointit (esim. `MINIO_BASE_URL`, `GITEA_SERVER_URL`) — valinnainen shortcut |
### Secrets vs. variables vs. conf
Kolmitasoinen jako — helppo sääntö kehittäjälle:
| Tyyppi | Missä | Esimerkkejä |
|--------|-------|-------------|
| **Secrets** | Gitea org secrets | `GITEA_TOKEN`, `SONAR_TOKEN`, `DEPLOY_TOKEN` |
| **Projektidata** | Consumer `*-conf.yaml` | `build.ecosystem`, `docker.imageName`, `sonarqube.*`, `test-flow`, `deployment.*` |
| **Org-infra** | Gitea org variables (valinnainen) | `MINIO_BASE_URL`, `GITEA_SERVER_URL` — sama kaikille, harvoin muuttuu |
1. Salainen? → org secret
2. Projektin asia? → conf-tiedosto (periaate 3)
3. Koko orgin sama infra-endpoint? → org variable, vain jos et halua toistaa sitä jokaisessa confissa
Org variables on **kapea poikkeus** infra-endpointeille — ei korvike conf-tiedostolle. Projektikohtaiset arvot (docker-nimi, test-flow, Sonar projectKey) kuuluvat aina confiin.
### Pipeline vs. jaettu suorituskoodi
Kaksi eri artefaktityyppiä provider-puolella:
```
Provider (gitea-ci-library)
├── .gitea/workflows/ci-engine.yml ← pipeline (Gitea workflow, consumer kutsuu uses:)
└── jaettu suorituskoodi ← uudelleenkäytettävä logiikka (EI .gitea/workflows/:ssa)
Consumer (mikropalvelu)
├── .gitea/workflows/ci.yml ← ohut kutsuja (Gitea workflow)
└── ci-feature.yaml ← data (ei suoritettavaa koodia)
```
- **Pipeline-tiedosto** (`ci-engine.yml`) on Gitean workflow — sen täytyy olla `.gitea/workflows/`:ssa, jotta `uses:` löytää sen.
- **Jaettu suorituskoodi** on providerin omistamaa logiikkaa (status-raportointi, raporttien julkaisu, dispatch jne.). Consumer ei kopioi sitä — korjaus provider-repoon, kaikki consumerit hyötyvät `@v1`:stä.
- **Toteutus** (kieli, tiedostomuoto, sijainti repossa) ei kuulu tähän dokumenttiin.
### Binding: consumer → provider
Ei dynaamista löytämistä — consumer kovakoodaa `uses:`-polun:
```
org/gitea-ci-library/.gitea/workflows/ci-engine.yml@v1
```
| Vaihe | Mitä tapahtuu |
|-------|---------------|
| 1 | Push consumer-repoon → Gitea lukee consumerin `ci.yml` |
| 2 | `if:` valitsee jobin |
| 3 | `uses:` → Gitea hakee provider-reposta `ci-engine.yml` annetulla `@ref`:llä |
| 4 | Provider vaatii `on: workflow_call` |
| 5 | `with:` → providerin `inputs`; checkout = **consumer-repo** (lähdekoodi + config-file) |
| 6 | Engine tuo provider-artefaktit runnerille tagilla `@v1` |
**`@ref`:** `@v1` (tag providerin main-haarassa). Pinnaa engine-version — consumer ei seuraa providerin kehityshaaraa.
### Dogfood-erikoistapaus
`gitea-ci-library` sisältää sekä consumer- että provider-tiedostot samassa repossa. **Roolit eivät muutu** — vain fyysinen sijainti on poikkeuksellinen.
**Dogfood-sääntö:** Consumerin `ci.yml` viittaa provideriin **samalla `uses:`-polulla** kuin mikä tahansa ulkoinen mikropalvelu. Ei suhteellista polkua (`./.gitea/workflows/...`).
```
Ulkoinen mikropalvelu (puhdas consumer) gitea-ci-library (dogfood)
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ ci.yml │ │ ci.yml ← consumer │
│ ci-feature.yaml │ │ ci-feature.yaml ← consumer │
│ ci-main.yaml │ │ ci-main.yaml ← consumer │
│ │ │ ci-engine.yml ← provider │
│ uses: org/gitea-ci-library/ │ │ uses: org/gitea-ci-library/ │
│ .../ci-engine.yml@v1 │ │ .../ci-engine.yml@v1 │
└──────────────────────────────┘ │ suorituskoodi ← provider │
└──────────────────────────────┘
```
Ulkoinen mikropalvelu on puhdas consumer (vain `ci.yml` + conf-tiedostot). Provider tulee `uses:`-viittauksella kirjastosta — dogfood mukaan lukien.
---
## Kuusi data-virtaa
```
1. CONSUMER → PROVIDER (push)
Consumer tietää branchin → valitsee jobin + config-file + kontit.
Provider vastaanottaa, ei tulkitse branchia.
ci.yml ──with: config-file + kontti-imaget──► ci-engine.yml
2. PROVIDER SISÄINEN
Engine lukee config-file-polun → rakentaa steppigraafin → ajaa stepit.
Mitä pipelinea ajetaan johtuu conf-tiedostosta, ei branch-tiedosta providerissa.
3. PROVIDER → GITEA (commit-status)
Jokainen steppi raportoi tilansa commitille (Gitea REST API).
Uniikki key per vaihe (periaate 1).
4. GITEA → CONSUMER (feedback loop)
Consumerin commitille kertyy statuksia jokaisesta stepistä.
Kehittäjä näkee suoraan commitilta: mitä ajettiin, menikö läpi.
url-kenttä linkittää raportteihin / buildiin.
5. PROVIDER → MinIO → GITEA (raportit)
Steppi generoi raportin → julkaisu MinIO:hon
→ deterministinen URL → URL liitetään commit-statusviestiin
(periaate 5).
6. CROSS-REPO KETJU
Mikropalvelu-commit (root) → dispatch → toinen repo → status takaisin root-committiin.
root-build kulkee inputs-parametrina koko ketjun läpi (periaate 7).
```
---
## Silmukka
```
Consumer pushaa koodia
├── feature-branch → ci.yml valitsee feature-jobin
│ with: config-file: ci-feature.yaml + kontit
└── main-branch → ci.yml valitsee main-jobin
with: config-file: ci-main.yaml + kontit
▼ uses:
┌──────────────────────────────────────────────┐
│ ci-engine.yml (provider, branch-agnostinen) │
│ Lukee config-file → steppigraafi → stepit │
│ │
│ config-file → steppigraafi → stepit ajetaan │
│ │ │ │ │
│ └────────────┴────────────┘ │
│ │ │
│ ▼ │
│ jokainen steppi → status commitille │
│ │ │
│ (raportit) → MinIO → url statusiin │
└──────────────────┬───────────────────────────┘
│ POST /api/v1/repos/.../statuses/{sha}
┌──────────────────────────────────────────────┐
│ Gitea commit │
│ ├── ci/start ✓ │
│ ├── ci/unit-test ✓ (url → raportti) │
│ ├── ci/code-coverage ✓ │
│ └── ci/end ✓ │
└──────────────────────────────────────────────┘
```
### Cross-repo ketju (periaate 7)
```
Mikropalvelu (root-commit abc123)
├── build + deploy ──dispatch──► Helm-repo (commit def456)
│ │
│ ├── status → oma commit
│ └── status → root abc123
└── test-flow ──dispatch──► Testi-repo (commit ghi789)
├── status → oma commit
├── status → root abc123
└── status → Helm def456
root-build (abc123) kulkee workflow_dispatch inputs -parametrina koko ketjun läpi.
```
---
## Omistajuus
| Mitä | Omistaja | Miten liikkuu |
|------|----------|---------------|
| Branch-päätös | Consumer | `ci.yml` `if:` |
| Mitä pipelinea ajetaan | Consumer | Valittu pipeline-as-conf -tiedosto |
| Pipeline-runko (steppigraafi) | Provider | Engine rakentaa confista |
| Kontit (maven, dind…) | Consumer | `with:` → provider `inputs` |
| Projektidata (ecosystem, test-flow…) | Consumer | pipeline-as-conf repossa |
| Moottori + jaettu suorituskoodi | Provider | `ci-engine.yml` + provider-logiikka |
| Tokenit, salasanat | Gitea org | secrets — `secrets: inherit` |
| Org-laajuiset infra-endpointit | Gitea org | variables (valinnainen poikkeus) |
---
## Consumer-sopimus
`ci.yml` on **mahdollisimman lyhyt**:
- Kaksi jobia (feature / main) tai vastaava `if:`-jaottelu
- Sama engine, eri `config-file` per job
- Kontit consumerin `with:`:ssa — provider ei sisällä image-versioita
- `secrets: inherit`
```yaml
# Esimerkki — kovakoodattu provider-polku (provider main-haarassa, tag @v1)
jobs:
feature:
if: gitea.ref != 'refs/heads/main'
uses: org/gitea-ci-library/.gitea/workflows/ci-engine.yml@v1
secrets: inherit
with:
config-file: ci-feature.yaml
maven-image: ...
main:
if: gitea.ref == 'refs/heads/main'
uses: org/gitea-ci-library/.gitea/workflows/ci-engine.yml@v1
secrets: inherit
with:
config-file: ci-main.yaml
maven-image: ...
dind-image: ...
```
---
## Provider-sopimus
- Vain `workflow_call` — ei omaa triggeriä
- Pakollinen input: `config-file` (polku repossa)
- Valinnaiset: kontti-imaget consumerilta
- Ei branch-ehtoja provider-tiedostossa — consumer on jo päättänyt mitä ajetaan conf-valinnalla
- Lukee consumer-datan (lähdekoodi + config-file) ja tuo provider-artefaktit runnerille
- Lukee conf → suorittaa → raportoi
### Mitä engine EI tiedä
- Onko kyseessä feature vai main — consumer on jo valinnut conf-tiedoston
- Mitä komentoja ajetaan — ne tulevat conf-tiedostosta
- Mitä kontteja on käytössä — consumer kertoo `with:`:ssa
Engine on branch-agnostinen suorittaja. Consumer omistaa päätökset ja datan.
---
## Esimerkki: gitea-ci-library (dogfood)
Tämä projekti on sekä provider että consumer. Roolit pysyvät samoina kuin ulkoisella mikropalvelulla — **sama `uses:`-viittaus** (`@v1`), ei suhteellista polkua. Consumer-sopimuksen esimerkki pätee sellaisenaan.
| Rooli | Tiedosto | Mitä tekee |
|-------|----------|------------|
| Consumer | `ci.yml` | Tietää branchin, valitsee jobin ja conf-tiedoston, syöttää kontit |
| Consumer | `ci-feature.yaml`, `ci-main.yaml` | Pipeline-as-conf — projektin data per pipeline-tyyppi |
| Provider | `ci-engine.yml` | Pipeline-määrittely; lukee config-file:n, rakentaa steppigraafin, ajaa, raportoi |
| Provider | jaettu suorituskoodi | Status, raportit, dispatch — toteutus avoin |
---
## Mitä tämä EI ota kantaa
- Moottorin toteutuskieli ja -muoto (bash, TypeScript, Python, Go — erillinen päätös)
- Nykyiset skriptitiedostot ja niiden rajapinnat (mahdollinen redesign)
- Raporttien julkaisumekanismi (miten MinIO:hon — toteutus avoin)
- Provider-artefaktien toimitus runnerille (checkout, bundle, inline — toteutus myöhemmin)
- Main-skeletonin yksityiskohdat (build, deploy, test-flow)
- Conf-tiedoston skeema / kentät (→ `config-model.md`)
- Cross-repon workflow-tiedostojen sisältö
+259
View File
@@ -0,0 +1,259 @@
# DEPRECATED — käytä Helm chartia: git-pages/
# helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml
#
# git-pages — k3s homelab (Codeberg git-pages)
#
# Yksi apex index-site (pages.helm-dev.../.index). Sisältö Gitea-poluissa:
# {owner}/{repo}/reports/{sha8}/index.html
#
# Julkaisu (CI → Traefik → git-pages):
# PATCH https://pages.helm-dev.keskikuja.site/ Authorization: Basic publish:<token>
# Traefik basicAuth middleware — token K8s-secretissä (htpasswd)
# git-pages PAGES_INSECURE=1 takana — ei forge/DNS/Gitea write
#
# Secret (kerran, ennen ensimmäistä publishia):
# export GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 32)"
# kubectl -n git-pages create secret generic git-pages-publish-auth \
# --from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$GIT_PAGES_PUBLISH_TOKEN")"
# # Sama arvo → Gitea Actions secret GIT_PAGES_PUBLISH_TOKEN
#
# Lukeminen: GET/HEAD julkinen (OIDC myöhemmin Traefikissä)
#
# URL-esimerkki:
# https://pages.helm-dev.keskikuja.site/niko/gitea-ci-library/reports/abc12345/index.html
#
# Image: codeberg.org/git-pages/git-pages:0.9.1
# CI: GIT_PAGES_PUBLISH_URL=https://pages.helm-dev.keskikuja.site
# GIT_PAGES_PUBLISH_TOKEN → Gitea Actions secret
---
apiVersion: v1
kind: Namespace
metadata:
name: git-pages
labels:
app.kubernetes.io/name: git-pages
app.kubernetes.io/component: pages-server
---
apiVersion: v1
kind: ConfigMap
metadata:
name: git-pages-config
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
data:
config.toml: |
log-format = "text"
[server]
pages = "tcp/:3000"
caddy = "-"
metrics = "tcp/:3002"
[storage]
type = "fs"
[storage.fs]
root = "/app/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: git-pages-data
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: git-pages
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
app.kubernetes.io/component: pages-server
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: git-pages
template:
metadata:
labels:
app.kubernetes.io/name: git-pages
app.kubernetes.io/component: pages-server
spec:
securityContext:
fsGroup: 1000
containers:
- name: git-pages
image: codeberg.org/git-pages/git-pages:0.9.1
imagePullPolicy: IfNotPresent
command:
- git-pages
args:
- -config
- /etc/git-pages/config.toml
env:
- name: PAGES_INSECURE
value: "1"
ports:
- name: http
containerPort: 3000
protocol: TCP
- name: metrics
containerPort: 3002
protocol: TCP
volumeMounts:
- name: config
mountPath: /etc/git-pages
readOnly: true
- name: data
mountPath: /app/data
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 10
periodSeconds: 20
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
volumes:
- name: config
configMap:
name: git-pages-config
- name: data
persistentVolumeClaim:
claimName: git-pages-data
---
apiVersion: v1
kind: Service
metadata:
name: git-pages
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: git-pages
ports:
- name: http
port: 3000
targetPort: http
protocol: TCP
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: git-pages-tls
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
spec:
secretName: git-pages-tls
dnsNames:
- pages.helm-dev.keskikuja.site
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
---
# PATCH/PUT vaatii BasicAuth (publish-token). Ilman tokenia → 401.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: git-pages-publish-auth
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
spec:
basicAuth:
secret: git-pages-publish-auth
---
# Julkinen luku: GET/HEAD. Julkaisu: PATCH/PUT + basicAuth (erillinen reitti).
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: git-pages
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
spec:
entryPoints:
- websecure
routes:
- match: >-
Host(`pages.helm-dev.keskikuja.site`) &&
(Method(`PATCH`) || Method(`PUT`))
kind: Rule
middlewares:
- name: git-pages-publish-auth
services:
- name: git-pages
port: 3000
- match: Host(`pages.helm-dev.keskikuja.site`) && (Method(`GET`) || Method(`HEAD`))
kind: Rule
services:
- name: git-pages
port: 3000
tls:
secretName: git-pages-tls
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: https-redirect
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
spec:
redirectScheme:
scheme: https
permanent: true
---
# HTTP → HTTPS. Jätä /.well-known/acme-challenge/ pois — cert-manager HTTP-01 (web :80).
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: git-pages-http
namespace: git-pages
labels:
app.kubernetes.io/name: git-pages
spec:
entryPoints:
- web
routes:
- match: >-
Host(`pages.helm-dev.keskikuja.site`) &&
!PathPrefix(`/.well-known/acme-challenge/`)
kind: Rule
middlewares:
- name: https-redirect
services:
- name: git-pages
port: 3000
+162
View File
@@ -0,0 +1,162 @@
# DEPRECATED — älä käytä. deadnews/gitea-pages vetää pages-branchin Giteasta (väärä suunta).
# Käytä sen sijaan: tmp/git-pages.yaml (Codeberg git-pages, CI pushaa HTML:n).
#
# Gitea Pages — k3s homelab (standardimalli, kuten vikunja)
#
# Sovellus: HTTP :8000 — ei omaa ingressiä, ei ACME:ä, ei TLS:ää podissa.
# Ulospäin: cert-manager Certificate → Traefik IngressRoute (websecure).
#
# Image: ghcr.io/deadnews/gitea-pages — vetää tiedostot Gitea API:sta.
# Data flow: CI git push → Gitea (branch "pages") → pages-server lukee API:lla.
#
# URL: https://pages.helm-dev.keskikuja.site/{owner}/{repo}/reports/{sha8}/cucumber/...
#
# Secret = Gitea PAT (read repository).
# POC: Secret inline alla (älä commitoi oikeaa tokenia).
# Tuotanto: kubectl-snippet, Secret pois manifestista — PR:llä repoon.
---
apiVersion: v1
kind: Namespace
metadata:
name: gitea-pages
labels:
app.kubernetes.io/name: gitea-pages
app.kubernetes.io/component: pages-server
# Tuotanto — Secret kubectl:lla (PAT Giteasta):
#
# NS=gitea-pages
# export GITEA_PAGES_TOKEN='gitea_pat_...'
# kubectl create secret generic gitea-pages-secrets \
# --from-literal=gitea-api-token="$GITEA_PAGES_TOKEN" \
# -n $NS
#
# kubectl apply -f tmp/gitea-pages.yaml # ilman Secret-resurssia
---
apiVersion: v1
kind: Secret
metadata:
name: gitea-pages-secrets
namespace: gitea-pages
labels:
app.kubernetes.io/name: gitea-pages
type: Opaque
stringData:
# POC: Gitea PAT read repository — täytä paikallisesti, älä commitoi
gitea-api-token: ""
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea-pages
namespace: gitea-pages
labels:
app.kubernetes.io/name: gitea-pages
app.kubernetes.io/component: pages-server
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: gitea-pages
template:
metadata:
labels:
app.kubernetes.io/name: gitea-pages
app.kubernetes.io/component: pages-server
spec:
containers:
- name: gitea-pages
image: ghcr.io/deadnews/gitea-pages:v1.0.1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
protocol: TCP
env:
- name: GITEA_PAGES_SERVER
value: "https://gitea.app.keskikuja.site"
- name: GITEA_PAGES_BRANCH
value: "pages"
- name: GITEA_PAGES_ADDR
value: ":8000"
- name: GITEA_PAGES_TOKEN
valueFrom:
secretKeyRef:
name: gitea-pages-secrets
key: gitea-api-token
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 20
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: gitea-pages
namespace: gitea-pages
labels:
app.kubernetes.io/name: gitea-pages
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: gitea-pages
ports:
- name: http
port: 8000
targetPort: http
protocol: TCP
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: gitea-pages-tls
namespace: gitea-pages
labels:
app.kubernetes.io/name: gitea-pages
spec:
secretName: gitea-pages-tls
commonName: pages.helm-dev.keskikuja.site
dnsNames:
- pages.helm-dev.keskikuja.site
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: gitea-pages
namespace: gitea-pages
labels:
app.kubernetes.io/name: gitea-pages
spec:
entryPoints:
- websecure
routes:
- match: Host(`pages.helm-dev.keskikuja.site`)
kind: Rule
services:
- name: gitea-pages
port: 8000
tls:
secretName: gitea-pages-tls