diff --git a/.gitea/workflows/build-feature.yml b/.gitea/workflows/build-feature.yml new file mode 100644 index 0000000..4a6ce87 --- /dev/null +++ b/.gitea/workflows/build-feature.yml @@ -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 "

Bats tests

" + } > "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 "" + echo "CI report ${SHA8}" + echo "" + echo "

CI report ${SHA8}

" + echo "

Commit: ${GITHUB_SHA}
Branch: ${GITHUB_REF_NAME}
Run: ${GITHUB_RUN_ID}

" + echo "" + echo "" + echo "" + echo "" + echo "" + echo "
SuitePassedFailedReport
Bats${BATS_PASS}${BATS_FAIL}results.txt" + echo " | junit.xml
Cucumber${CUCUMBER_PASS}${CUCUMBER_FAIL}report" + echo " | json
" + } > "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 diff --git a/.gitea/workflows/ci-engine.yml b/.gitea/workflows/ci-engine.yml new file mode 100644 index 0000000..ba66fb6 --- /dev/null +++ b/.gitea/workflows/ci-engine.yml @@ -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 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index a93f88b..4310951 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,33 +1,20 @@ -# .gitea/workflows/ci.yml — Kirjaston oma CI (eat your own dogfood) -# -# 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 - +name: CI on: push: branches: ["**"] workflow_dispatch: jobs: - # --- Feature-branch: testit + raportit, ei konttia --- feature: - if: github.ref != 'refs/heads/master' - uses: ./.gitea/workflows/ci-feature.yml + if: github.ref != 'refs/heads/main' + uses: niko/gitea-ci-library/.gitea/workflows/build-feature.yml@v1 secrets: inherit with: - config-file: ci-flow-values.yaml + bats-image: bats/bats:latest - # --- Master-branch: build + kontti + test flow --- - master: - if: github.ref == 'refs/heads/master' - uses: ./.gitea/workflows/ci-master.yml + main: + if: github.ref == 'refs/heads/main' + uses: niko/gitea-ci-library/.gitea/workflows/build-feature.yml@v1 secrets: inherit with: - config-file: ci-flow-values.yaml + bats-image: bats/bats:latest diff --git a/.gitignore b/.gitignore index 71dd431..7de0cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ AGENTS.md .ai node_modules/ +tmp/ diff --git a/README.md b/README.md index da98b7b..e6df085 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,65 @@ 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 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) -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 @@ -67,6 +130,7 @@ helm upgrade --install act-runner gitea/actions \ --set giteaRootURL="$GITEA_URL" \ --set existingSecret=act-runner-token \ --set existingSecretKey=token \ + --set statefulset.dind.tag=29.5.2-dind \ --set-string 'statefulset.runner.config=log: level: info cache: @@ -78,13 +142,28 @@ container: --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ä. +#### 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 ```bash 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ä: @@ -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. +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) ### 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`) | | `existingSecret` | Kubernetes secretin nimi, jossa token | | `existingSecretKey` | Avain secretin sisällä | +| `statefulset.dind.tag` | DinD-image tag (`29.5.2-dind` minimi) | | `statefulset.runner.labels` | Mukautetut labelit | diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..56d23f8 --- /dev/null +++ b/config.yaml @@ -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 diff --git a/docs/adr/0004-commit-status.md b/docs/adr/0004-commit-status.md new file mode 100644 index 0000000..8084eb3 --- /dev/null +++ b/docs/adr/0004-commit-status.md @@ -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. diff --git a/docs/adr/0005-provider-consumer.md b/docs/adr/0005-provider-consumer.md new file mode 100644 index 0000000..b898ae1 --- /dev/null +++ b/docs/adr/0005-provider-consumer.md @@ -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. diff --git a/docs/ai-context.md b/docs/ai-context.md index 22aa56f..c5b2a0e 100644 --- a/docs/ai-context.md +++ b/docs/ai-context.md @@ -1,67 +1,98 @@ # AI Context: Gitea Actions CI -kirjasto -**Updated**: 2026-06-08 +**Updated**: 2026-06-12 (POC-vaihe, suunniteltu uudelleenkirjoitus) ## 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 -- 4 reusable workflow'ta: `ci-feature.yml`, `ci-master.yml`, `deploy.yml`, `test.yml` -- 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 -- Cross-repo commit traceability Gitea REST API:n kautta -- `report-service/`-moduuli samassa repossa: raporttiskriptit, retention CronJob, index.html-generointi -- Normatiivinen arkkitehtuuri: `docs/architecture.md`, perustelut `docs/design-rationale.md` +POC on valmis: raporttien julkaisu git-pagesiin ja commit-status linkillä +toimii. + +## Monorepo: kaksi erillistä kokonaisuutta + +Tämä repo on käytännössä monorepo, jossa on kaksi itsenäistä osaa: + +### 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 + → 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 + | Path | Purpose | |---|---| -| `.gitea/workflows/` | Reusable workflow -tiedostot (`ci-feature.yml`, `ci-master.yml`, `deploy.yml`, `test.yml`) | -| `scripts/` | Jaetut bash-skriptit | -| `report-service/` | Raporttipalvelun koodi (retention, indeksigenerointi) | -| `docs/` | Arkkitehtuuri-, vaatimus- ja konfiguraatiodokumentaatio | -| `docs/tickets/` | Toteutustiketit (0001–0012), yksi per feature-branch | -| `docs/test-plan/` | TDD-opas: 3-kerrosmalli, kehityslooppi, mock, kontekstikuratointi | -| `docs/test-plan/tdd-guide.md` | Testivetoisen kehityksen menetelmädokumentti | -| `tests/` | Bats-testit skripteille ja workflow-validointi | -| `tests/features/` | Cucumber `.feature`-tiedostot (yksi per tiketti, tägätty @mock/@real/@ticket-NNNN) | -| `tests/helpers/` | Jaettu mock-palvelin (Gitea API + MinIO) | -| `.gitea/workflows/ci.yml` | Kirjaston oma CI — dogfood (käyttää itse itseään) | +| `.gitea/workflows/` | `ci-engine.yml` (ainoa reusable workflow POC-vaiheessa) | +| `scripts/` | `publish-git-pages.sh`, `report-status.sh`, `dispatch-workflow.sh` | +| **`git-pages/`** | **Oma kokonaisuus: Helm-chartti + docs + retention** | +| `docs/` | Root-tason arkkitehtuuri, ADRt (0001–0005) | +| `docs/adr/` | Architecture Decision Records | +| `tests/` | Bats-testit skripteille | +| `.gitea/workflows/ci.yml` | Dogfood — kutsuu `ci-engine.yml`:a | + +**Tarkemmat git-pages-asiat:** `git-pages/docs/` (implementation-notes, +architecture, design-rationale, secrets, tech-stack). ## Key Technical Decisions -- **Vain Gitea.** Ei multi-platform-tukea (GitLab, BitBucket, GitHub) -- **Ei omaa runtimea.** Reusable workflowt, ei Docker custom actioneita (ellei pakko) -- **Konfiguraatio repoon.** `ci-flow-values.yaml` projektin juuressa, infra-asetukset Gitea org secrets/variables -- **Vaiheittainen test flow.** Ei rinnakkaista suoritusta — deterministinen, debuggattava -- **Raportit MinIO:ssa.** Gitea artifact-järjestelmä ei tue HTML-selailtavuutta -- **Docker-rekisterit:** MVP:ssä vain Gitea Packages. Factory/adapter-pattern valmiina Artifactorylle/Nexukselle -- **MVP-scope:** `doNotDowngrade` ei mukana, vain Gitea Packages docker-rekisterinä +- **Provider & Consumer**: `ci-engine.yml` lukittu rajapinta, muu koodi + vapaasti muutettavissa +- **Vain Gitea, vain reusable workflowt**: ei custom actioneita, ei + multi-platform +- **Raportit git-pagesissa**: HTML selailtavissa, retention automaattinen +- **Git-pages omana kokonaisuutena**: voi erottaa omaksi repokseen + tulevaisuudessa -## Tech Stack -- **Runtime:** Bash 4.0+, curl 7.0+, jq 1.6+, git 2.30+, MinIO client (`mc`) -- **Alusta:** Gitea Actions 1.21+, Gitea act runner 0.2+ -- **Integraatiot:** Gitea REST API (`/api/v1/`), MinIO S3 API, SonarQube REST API -- **Tuetut build-ekosysteemit:** Java/Maven, Java/Gradle, Node.js/npm -- **Tuetut testikehykset:** Cucumber, JUnit, JaCoCo, Maven Site, custom HTML +## Tech Stack (POC) +- **Runtime:** Bash, curl, jq, python3 (retention whiteout) +- **Alusta:** Gitea Actions, Gitea act runner +- **Hostaus:** git-pages 0.9.1 (Codeberg), Traefik, cert-manager +- **Integraatiot:** Gitea REST API, Gitea Packages ## Common Commands -- Workflow-triggerit: `push` branchiin tai `workflow_dispatch` -- Skriptien kutsuminen tapahtuu workflow-stepeistä, ei paikallisesti -- `ci-flow-values.yaml` validointi: skeema `docs/config-model.md`:ssa -- 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 +- Helm-asennus: `helm upgrade --install git-pages ./git-pages -n -f ` +- Julkaisu: `bash scripts/publish-git-pages.sh ` +- Status: `bash scripts/report-status.sh ` ## 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ä siirrä konfiguraatiota pois reposita (`ci-flow-values.yaml`) -- Älä lisää rinnakkaista test flow -suoritusta -- Älä lisää ulkoista orkestraattoria — Gitea REST API riittää -- Älä käytä `repository_dispatch`-webhookia test flow -ketjutukseen +- Älä kirjoita git-pages-spesifiä tietoa juuren `docs/`-kansioon — + kuuluu `git-pages/docs/`-alle +- Älä POSTaa commit-status APIin jokaiselle vaiheelle — natiivi riittää diff --git a/docs/analysis/ci-flow-values-vs-native-config.md b/docs/analysis/ci-flow-values-vs-native-config.md new file mode 100644 index 0000000..aa0f6ce --- /dev/null +++ b/docs/analysis/ci-flow-values-vs-native-config.md @@ -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 0–n 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. diff --git a/docs/architecture.md b/docs/architecture.md index 7c169cc..3e6bad3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,216 +1,52 @@ # 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 -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 | -|----------|----------|-------| -| **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. | +## Komponentit (POC) -> 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 - -| 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 +## Ulkoiset palvelut | Palvelu | Rooli | |---------|-------| -| **Gitea REST API** | Commit-statusraportointi, workflow-dispatch, run-pollaus, taggaus | -| **Gitea Packages** | Docker-imagen ja NPM-paketin säilytys | -| **Gitea act runner** | Suorittaa workflowt. Konteista, DinD:stä ja label-järjestelmästä: [runner.md](runner.md) | -| **MinIO** | Testiraporttien tallennus ja staattinen web-hosting | -| **SonarQube** | Koodin laadun analyysi ja quality gate | +| **Gitea REST API** | Commit-status, workflow-dispatch, run-pollaus | +| **Gitea Packages** | Docker-imagen säilytys | +| **git-pages** | Raporttien hostaus | +| **SonarQube** | Koodin laadun analyysi (suunniteltu) | ---- +## Arkkitehtuuriset rajoitteet -## Järjestelmäkaavio - -```mermaid -%%{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. +- `ci-engine.yml` on ainoa consumerin kutsuma rajapinta (ADR 0005) +- Gitea Actionsin natiivi commit-status on ensisijainen (ADR 0004) +- Raportit ovat julkisia URL:lla (osoite tunnettava) diff --git a/docs/ci-pipeline-practices.md b/docs/ci-pipeline-practices.md new file mode 100644 index 0000000..f2e0a78 --- /dev/null +++ b/docs/ci-pipeline-practices.md @@ -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ä) diff --git a/docs/config-model.md b/docs/config-model.md index 2005b1f..c2a9876 100644 --- a/docs/config-model.md +++ b/docs/config-model.md @@ -1,6 +1,14 @@ # 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 # is-container-built.sh @@ -156,34 +173,8 @@ jobs: **Miksi tämä on välttämätöntä:** - Estää versiokonfliktit: `1.2.3.42` ei voi olla kahdesti -- Säästää CI-aikaa: kontin buildaus on hitain vaihe -- Pitää commitin ja kontin 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. +- Säästää CI-aikaa: artifaktin buildaus on hitain vaihe +- Pitää commitin ja artifaktin välisen suhteen yksiselitteisenä: `git tag` kertoo suoraan mikä versio vastaa tätä committia --- diff --git a/docs/design-rationale.md b/docs/design-rationale.md index 09e9808..1be0113 100644 --- a/docs/design-rationale.md +++ b/docs/design-rationale.md @@ -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. -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 -### 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. **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. @@ -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ä. -### 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. **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. @@ -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. -### 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. @@ -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. -### 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. @@ -128,18 +159,7 @@ sequenceDiagram --- -## Teknologiavalinnat (seurauksina ylläolevista periaatteista) - -| 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 diff --git a/docs/report-hosting.md b/docs/report-hosting.md index 92e9aa5..c9af9c8 100644 --- a/docs/report-hosting.md +++ b/docs/report-hosting.md @@ -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/`. ---- - -## 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 -# 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. +Tämä dokumentti on korvattu git-pages-kohtaisella dokumentaatiolla. diff --git a/docs/shared-scripts.md b/docs/shared-scripts.md index 0103c75..be8677a 100644 --- a/docs/shared-scripts.md +++ b/docs/shared-scripts.md @@ -1,8 +1,12 @@ # 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 diff --git a/docs/tech-stack.md b/docs/tech-stack.md index 1ab4de5..11217d4 100644 --- a/docs/tech-stack.md +++ b/docs/tech-stack.md @@ -1,109 +1,39 @@ # 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 -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 | |---|---|---| | **Gitea Actions** | 1.21+ | CI-alusta, workflow-moottori | | **Gitea act runner** | 0.2+ | Workflow'n suoritus | | **Bash** | 4.0+ | Integraatioskriptit workflow-stepeissä | -| **curl** | 7.0+ | Gitea REST API -kutsut (statusraportointi, dispatch, pollaus) | -| **jq** | 1.6+ | JSON-vastausten jäsennys REST API -kutsuista | -| **git** | 2.30+ | SCM-operaatiot (checkout, tag, push) | -| **MinIO client (mc)** | latest | Raporttien pushaus MinIO:hon ja URL-generointi | +| **curl** | 7.0+ | Gitea REST API, git-pages PATCH | +| **jq** | 1.6+ | JSON-vastausten jäsennys | ---- +## Raporttien hostaus -## Konfiguraatio - -| Teknologia | Käyttötarkoitus | -|---|---| -| **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) | - ---- +Raportit hostataan git-pages-palvelulla (`git-pages/`-Helm-chartti). +Julkaisu: `scripts/publish-git-pages.sh` → PATCH tar. Tarkemmat +teknologiavalinnat: `git-pages/docs/tech-stack.md`. ## Tuetut ulkoiset palvelut -Kirjasto integroituu näihin ulkoisiin palveluihin workflow-stepeistä käsin. Palvelut eivät ole osa kirjastoa. - | Palvelu | Rajapinta | Käyttötarkoitus | |---|---|---| -| **Gitea REST API** | `/api/v1/` | Commit-statusraportointi, workflow-dispatch, run-status-pollaus, taggaus | -| **MinIO** | S3 API + staattinen web-hosting | Testiraporttien tallennus ja selailu | -| **SonarQube** | REST API (`/api/`) | Quality gate -pollaus, dashboard-linkitys | -| **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) | - ---- +| **Gitea REST API** | `/api/v1/` | Commit-status, workflow-dispatch, branch-listaus (retention) | +| **git-pages** | HTTP | Raporttien hostaus | +| **Gitea Packages** | Container registry API | Docker-imagen push | ## Mitä EI tueta (verrattuna Jenkins-versioon) -Nämä olivat Jenkins-kirjastossa, mutta on tietoisesti jätetty pois Gitea Actions -versiosta: - | Teknologia | Syy | |---|---| -| **GitLab REST API** | Ei multi-platform-tukea (periaate 6) | -| **BitBucket Server REST API** | Ei multi-platform-tukea | -| **BitBucket Cloud REST API** | Ei multi-platform-tukea | -| **Jenkins** (shared library, cucumber plugin, publishHTML, jacoco plugin, buildJob) | Ei Jenkins-riippuvuutta — Gitea Actions korvaa | -| **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. +| **MinIO** | Korvattu git-pagesilla | +| **Multi-Git-platform** | Vain Gitea | +| **Jenkins** (shared library, plugins) | Gitea Actions korvaa | +| **Artifactory/Nexus** | MVP:ssä ei, factory/adapter-pattern valmiina | diff --git a/docs/tickets/0006-ci-feature-yml.md b/docs/tickets/0006-pipeline-as-conf.md similarity index 50% rename from docs/tickets/0006-ci-feature-yml.md rename to docs/tickets/0006-pipeline-as-conf.md index 7cf264b..ef3fb60 100644 --- a/docs/tickets/0006-ci-feature-yml.md +++ b/docs/tickets/0006-pipeline-as-conf.md @@ -1,8 +1,8 @@ -# Ticket 0006: `ci-feature.yml` (reusable workflow) +# Ticket 0006: Pipeline as conf **Vaihe:** 6/12 **Status:** pending -**Feature branch:** `feature/0006-ci-feature-yml` +**Feature branch:** `feature/0006-pipeline-as-conf` **TDD required:** Yes **Feature file required:** Yes @@ -11,6 +11,9 @@ - `tests/features/0006-ci-feature.feature` - 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 @@ -27,7 +30,7 @@ bats tests/workflows.bats ``` ### Green -Toteuta `gitea/workflows/ci-feature.yml`. +Toteuta `.gitea/workflows/ci-feature.yml`. ```bash bats tests/workflows.bats @@ -35,12 +38,12 @@ bats tests/workflows.bats ``` ### Dogfood -Lisää kutsu `gitea/workflows/ci.yml`:stä: +Lisää kutsu `.gitea/workflows/ci.yml`:stä: ```yaml feature: 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. @@ -48,16 +51,19 @@ Jokainen push feature-branchiin ajaa `ci-feature.yml`:n. ## DoD - [ ] Cucumber: `@ticket-0006 and @mock` → kaikki skenaariot GREEN - [ ] `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 - [ ] 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 -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 @@ -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`) | | `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 | -|--------|---------|--------| -| `start` | `report-status.sh pending "Building..."` | INPROGRESS | -| `unit-test` | Projektin oma testiajo | — | -| `code-coverage` | JaCoCo / vastaava | — | -| `publish-html` | `push-reports.sh cucumber; push-reports.sh jacoco` | — | -| `end` | `report-status.sh success/failure` | Lopullinen status | -| `fail` (catch) | `report-status.sh failure` | FAILURE | +| Job | `context` | Toiminto | +|-----|-----------|----------| +| `start` | `ci/start` | `report-status.sh pending "Build started"` | +| `unit-test` | `ci/unit-test` | Projektin oma testiajo → `report-status.sh success/failure` | +| `code-coverage` | `ci/code-coverage` | JaCoCo / vastaava → `report-status.sh success/failure` | +| `end` | `ci/end` | `report-status.sh success/failure` (kokonaistulos) | ## Concurrency @@ -97,10 +101,10 @@ concurrency: ## Verifiointi Simuloi paikallisesti (act-runner tai manuaalinen steppien ajo): -1. `report-status.sh pending` → commitin status = pending -2. Testit ajetaan -3. `push-reports.sh` → raportit MinIO:ssa -4. `report-status.sh success` → statusviesti + URL +1. `report-status.sh pending` → commitin status = pending (`ci/start`) +2. Testit ajetaan → `ci/unit-test` = success/failure +3. Coverage ajetaan → `ci/code-coverage` = success/failure +4. `report-status.sh success/failure` → `ci/end` = lopullinen status ## Viitteet diff --git a/docs/tickets/0013-ci-feature-report-publish.md b/docs/tickets/0013-ci-feature-report-publish.md new file mode 100644 index 0000000..f76f63d --- /dev/null +++ b/docs/tickets/0013-ci-feature-report-publish.md @@ -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 diff --git a/docs/workflows.md b/docs/workflows.md index 5d2cfdb..a6bd90b 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -1,6 +1,8 @@ # 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 | |-----------|------------|--------| | `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`) | -| `node-image` | Ei | Node-kontin image (jos npm-projekti) | +| `containers` | Ei | Kuvaus konteista: avain = nimi, arvo = image. Steppi valitsee missä kontissa ajaa. | ### Steppi-kaavio @@ -42,9 +43,8 @@ flowchart TD aja testit, generoi raportit"] UNIT --> COV["code-coverage jacoco / vastaava"] - COV --> HTML["publish-html - pushaa raportit MinIO:hon - generoi index.html"] + COV --> HTML["publish-reports + vie raportit git-pagesiin"] HTML --> END(["end POST lopullinen status"]) @@ -84,8 +84,7 @@ unit-test → quality-gate → build-jar → build-docker → push-docker → ta | Parametri | Pakollinen | Kuvaus | |-----------|------------|--------| | `config-file` | Kyllä | Polku `ci-flow-values.yaml`:aan | -| `maven-image` | Ei | Maven-kontti | -| `docker-image` | Ei | Docker-in-Docker -image (esim. `docker:26-dind`) | +| `containers` | Ei | Kuvaus konteista: avain = nimi, arvo = image | ### isContainerBuilt-check @@ -125,9 +124,8 @@ flowchart TD CHECK -- "kyllä" --> CTF["continueToTestFlow"] TAG --> CTF - CTF --> HTML["publish-html - pushaa Maven Site - MinIO:hon"] + CTF --> HTML["publish-reports + vie raportit git-pagesiin"] HTML --> END(["end lopullinen status"]) diff --git a/git-pages/Chart.yaml b/git-pages/Chart.yaml new file mode 100644 index 0000000..7455afb --- /dev/null +++ b/git-pages/Chart.yaml @@ -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" diff --git a/git-pages/README.md b/git-pages/README.md new file mode 100644 index 0000000..9b47223 --- /dev/null +++ b/git-pages/README.md @@ -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 3–4, 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). diff --git a/git-pages/dev-values.yaml b/git-pages/dev-values.yaml new file mode 100644 index 0000000..7e1282a --- /dev/null +++ b/git-pages/dev-values.yaml @@ -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 diff --git a/git-pages/docs/architecture.md b/git-pages/docs/architecture.md new file mode 100644 index 0000000..8e82bb4 --- /dev/null +++ b/git-pages/docs/architecture.md @@ -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. diff --git a/git-pages/docs/design-rationale.md b/git-pages/docs/design-rationale.md new file mode 100644 index 0000000..669458a --- /dev/null +++ b/git-pages/docs/design-rationale.md @@ -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ä) | diff --git a/git-pages/docs/implementation-notes.md b/git-pages/docs/implementation-notes.md new file mode 100644 index 0000000..c980080 --- /dev/null +++ b/git-pages/docs/implementation-notes.md @@ -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. diff --git a/git-pages/docs/secrets.md b/git-pages/docs/secrets.md new file mode 100644 index 0000000..054eac0 --- /dev/null +++ b/git-pages/docs/secrets.md @@ -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="" + +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
GIT_PAGES_PUBLISH_TOKEN"] + P2["K8s Secret
git-pages-publish-auth"] + P1 -->|token| TRAEFIK + P2 -->|htpasswd| TRAEFIK + TRAEFIK["Traefik BasicAuth"] + end + + subgraph "Retention Flow" + R1["K8s Secret
git-pages-retention-gitea"] + R2["Gitea PAT
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
git-pages-publish-auth + participant K8sToken as K8s Secret
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
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
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="" +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" +``` diff --git a/git-pages/docs/tech-stack.md b/git-pages/docs/tech-stack.md new file mode 100644 index 0000000..bc3f099 --- /dev/null +++ b/git-pages/docs/tech-stack.md @@ -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 | diff --git a/git-pages/files/retention-cleanup.sh b/git-pages/files/retention-cleanup.sh new file mode 100644 index 0000000..11b9f27 --- /dev/null +++ b/git-pages/files/retention-cleanup.sh @@ -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 diff --git a/git-pages/files/retention-run.sh b/git-pages/files/retention-run.sh new file mode 100644 index 0000000..8fb4636 --- /dev/null +++ b/git-pages/files/retention-run.sh @@ -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." diff --git a/git-pages/templates/NOTES.txt b/git-pages/templates/NOTES.txt new file mode 100644 index 0000000..61f07ec --- /dev/null +++ b/git-pages/templates/NOTES.txt @@ -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: + +Upgrade: helm upgrade {{ .Release.Name }} ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml +Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} diff --git a/git-pages/templates/_helpers.tpl b/git-pages/templates/_helpers.tpl new file mode 100644 index 0000000..08418db --- /dev/null +++ b/git-pages/templates/_helpers.tpl @@ -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 }} diff --git a/git-pages/templates/certificate.yaml b/git-pages/templates/certificate.yaml new file mode 100644 index 0000000..a4d7377 --- /dev/null +++ b/git-pages/templates/certificate.yaml @@ -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 }} diff --git a/git-pages/templates/configmap.yaml b/git-pages/templates/configmap.yaml new file mode 100644 index 0000000..a13fe92 --- /dev/null +++ b/git-pages/templates/configmap.yaml @@ -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" diff --git a/git-pages/templates/deployment.yaml b/git-pages/templates/deployment.yaml new file mode 100644 index 0000000..2c5e722 --- /dev/null +++ b/git-pages/templates/deployment.yaml @@ -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 }} diff --git a/git-pages/templates/ingressroute.yaml b/git-pages/templates/ingressroute.yaml new file mode 100644 index 0000000..efb19b6 --- /dev/null +++ b/git-pages/templates/ingressroute.yaml @@ -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 }} diff --git a/git-pages/templates/init-job.yaml b/git-pages/templates/init-job.yaml new file mode 100644 index 0000000..ef6403b --- /dev/null +++ b/git-pages/templates/init-job.yaml @@ -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 }} diff --git a/git-pages/templates/middleware.yaml b/git-pages/templates/middleware.yaml new file mode 100644 index 0000000..8cc55e5 --- /dev/null +++ b/git-pages/templates/middleware.yaml @@ -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 }} diff --git a/git-pages/templates/publish-auth-secret.yaml b/git-pages/templates/publish-auth-secret.yaml new file mode 100644 index 0000000..e6d36ca --- /dev/null +++ b/git-pages/templates/publish-auth-secret.yaml @@ -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 }} diff --git a/git-pages/templates/pvc.yaml b/git-pages/templates/pvc.yaml new file mode 100644 index 0000000..a377929 --- /dev/null +++ b/git-pages/templates/pvc.yaml @@ -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 }} diff --git a/git-pages/templates/retention-configmap.yaml b/git-pages/templates/retention-configmap.yaml new file mode 100644 index 0000000..506728e --- /dev/null +++ b/git-pages/templates/retention-configmap.yaml @@ -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 }} diff --git a/git-pages/templates/retention-cronjob.yaml b/git-pages/templates/retention-cronjob.yaml new file mode 100644 index 0000000..c651b99 --- /dev/null +++ b/git-pages/templates/retention-cronjob.yaml @@ -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 }} diff --git a/git-pages/templates/retention-rbac.yaml b/git-pages/templates/retention-rbac.yaml new file mode 100644 index 0000000..0dcf543 --- /dev/null +++ b/git-pages/templates/retention-rbac.yaml @@ -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 }} diff --git a/git-pages/templates/service.yaml b/git-pages/templates/service.yaml new file mode 100644 index 0000000..91d0da1 --- /dev/null +++ b/git-pages/templates/service.yaml @@ -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 diff --git a/git-pages/values.yaml b/git-pages/values.yaml new file mode 100644 index 0000000..29f4ede --- /dev/null +++ b/git-pages/values.yaml @@ -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 diff --git a/scripts/publish-git-pages.sh b/scripts/publish-git-pages.sh new file mode 100755 index 0000000..61065ab --- /dev/null +++ b/scripts/publish-git-pages.sh @@ -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" <&2 + cat /tmp/git-pages-publish-response.txt >&2 + exit 1 + ;; +esac + +echo "$REPORT_BASE" diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 0000000..299f3ce --- /dev/null +++ b/scripts/publish.sh @@ -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 diff --git a/tests/features/step_definitions/test-execution.steps.js b/tests/features/step_definitions/test-execution.steps.js index 19ec879..264a861 100644 --- a/tests/features/step_definitions/test-execution.steps.js +++ b/tests/features/step_definitions/test-execution.steps.js @@ -30,7 +30,7 @@ function envBlock() { } 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' }); const seqFile = path.join(os.tmpdir(), `cucumber_seq_${Date.now()}.json`); diff --git a/tmp/0006-ci-feature-example/roles-and-files.md b/tmp/0006-ci-feature-example/roles-and-files.md new file mode 100644 index 0000000..a73ee29 --- /dev/null +++ b/tmp/0006-ci-feature-example/roles-and-files.md @@ -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
Master-skeleton: start → build → deploy → [test-flow] → end | +| **Test plan + data** | `ci-flow-values.yaml` | `build.ecosystem` → komennot
`test-flow[]` → test plan (vain master)
`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 | — | diff --git a/tmp/data-flow-design.md b/tmp/data-flow-design.md new file mode 100644 index 0000000..18832a7 --- /dev/null +++ b/tmp/data-flow-design.md @@ -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ö diff --git a/tmp/git-pages.yaml b/tmp/git-pages.yaml new file mode 100644 index 0000000..688d8f7 --- /dev/null +++ b/tmp/git-pages.yaml @@ -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: +# 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 diff --git a/tmp/gitea-pages.yaml b/tmp/gitea-pages.yaml new file mode 100644 index 0000000..89cf3de --- /dev/null +++ b/tmp/gitea-pages.yaml @@ -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