From bd6ed5c2c23ea8212550fe5a1314235556d67ae3 Mon Sep 17 00:00:00 2001 From: niko Date: Sun, 28 Jun 2026 09:10:10 +0300 Subject: [PATCH] Feature/git pages -> oma nginx perusteinen raporointipalvelu (#48) Co-authored-by: moilanik Reviewed-on: https://gitea.app.keskikuja.site/niko/gitea-ci-library/pulls/48 --- .gitea/workflows/config-provider.yml | 4 +- .gitea/workflows/example-bats-tests.yml | 4 +- .gitea/workflows/example-cucumber-tests.yml | 4 +- ....ci-main.yml => gitea-reports.ci-main.yml} | 12 +- ...-env.conf => gitea-reports.gitea-env.conf} | 5 +- .gitea/workflows/report-summary.yml | 2 +- README.md | 4 +- docs/adr/0005-provider-consumer.md | 2 +- docs/ci-pipeline-practices.md | 2 +- docs/config-model.md | 4 +- docs/design-rationale.md | 239 +------ docs/git-pages-nginx-arkkitehtuuri.md | 551 ++++++++++++++++ docs/shared-scripts.md | 6 +- docs/workflows.md | 2 +- git-pages/Chart.yaml | 6 - git-pages/docs/architecture.md | 142 ---- git-pages/docs/secrets.md | 237 ------- git-pages/files/retention-cleanup.sh | 276 -------- git-pages/files/retention-lib.sh | 184 ------ git-pages/files/retention-run.sh | 19 - git-pages/templates/NOTES.txt | 16 - git-pages/templates/configmap.yaml | 20 - git-pages/templates/deployment.yaml | 127 ---- git-pages/templates/init-job.yaml | 58 -- git-pages/templates/middleware.yaml | 22 - git-pages/templates/service.yaml | 15 - git-pages/tests/retention.bats | 623 ------------------ gitea-reports/Chart.yaml | 5 + {git-pages => gitea-reports}/README.md | 75 +-- {git-pages => gitea-reports}/dev-values.yaml | 12 +- gitea-reports/docs/architecture.md | 82 +++ .../docs/design-rationale.md | 22 +- .../docs/implementation-notes.md | 0 gitea-reports/docs/secrets.md | 267 ++++++++ .../docs/tech-stack.md | 22 +- gitea-reports/files/retention-cleanup.sh | 66 ++ gitea-reports/files/retention-lib.sh | 32 + gitea-reports/files/retention-run.sh | 6 + gitea-reports/templates/NOTES.txt | 19 + .../templates/_helpers.tpl | 20 +- .../templates/certificate.yaml | 6 +- gitea-reports/templates/configmap.yaml | 43 ++ gitea-reports/templates/deployment.yaml | 75 +++ .../templates/ingressroute.yaml | 26 +- gitea-reports/templates/middleware.yaml | 22 + .../templates/publish-auth-secret.yaml | 4 +- .../templates/pvc.yaml | 4 +- .../templates/retention-configmap.yaml | 4 +- .../templates/retention-cronjob.yaml | 26 +- .../templates/retention-rbac.yaml | 16 +- gitea-reports/templates/service.yaml | 19 + gitea-reports/tests/retention.bats | 150 +++++ {git-pages => gitea-reports}/values.yaml | 52 +- scripts/ci-report.sh | 2 +- scripts/ci-validate.sh | 2 +- scripts/publish-git-pages.sh | 76 +-- scripts/publish.sh | 2 +- scripts/report-status.sh | 2 +- skills/consumer-pipelines/REFERENCE.md | 2 +- tests/ci-validate.bats | 10 +- .../step_definitions/commit-status.steps.js | 4 +- tests/helpers/mock-server.py | 8 + tests/publish-git-pages.bats | 30 +- tests/publish.bats | 8 +- tests/report-status.bats | 3 +- 65 files changed, 1602 insertions(+), 2208 deletions(-) rename .gitea/workflows/{git-pages.ci-main.yml => gitea-reports.ci-main.yml} (84%) rename .gitea/workflows/{git-pages.gitea-env.conf => gitea-reports.gitea-env.conf} (70%) create mode 100644 docs/git-pages-nginx-arkkitehtuuri.md delete mode 100644 git-pages/Chart.yaml delete mode 100644 git-pages/docs/architecture.md delete mode 100644 git-pages/docs/secrets.md delete mode 100644 git-pages/files/retention-cleanup.sh delete mode 100644 git-pages/files/retention-lib.sh delete mode 100644 git-pages/files/retention-run.sh delete mode 100644 git-pages/templates/NOTES.txt delete mode 100644 git-pages/templates/configmap.yaml delete mode 100644 git-pages/templates/deployment.yaml delete mode 100644 git-pages/templates/init-job.yaml delete mode 100644 git-pages/templates/middleware.yaml delete mode 100644 git-pages/templates/service.yaml delete mode 100644 git-pages/tests/retention.bats create mode 100644 gitea-reports/Chart.yaml rename {git-pages => gitea-reports}/README.md (58%) rename {git-pages => gitea-reports}/dev-values.yaml (66%) create mode 100644 gitea-reports/docs/architecture.md rename {git-pages => gitea-reports}/docs/design-rationale.md (88%) rename {git-pages => gitea-reports}/docs/implementation-notes.md (100%) create mode 100644 gitea-reports/docs/secrets.md rename {git-pages => gitea-reports}/docs/tech-stack.md (66%) create mode 100644 gitea-reports/files/retention-cleanup.sh create mode 100644 gitea-reports/files/retention-lib.sh create mode 100644 gitea-reports/files/retention-run.sh create mode 100644 gitea-reports/templates/NOTES.txt rename {git-pages => gitea-reports}/templates/_helpers.tpl (65%) rename {git-pages => gitea-reports}/templates/certificate.yaml (64%) create mode 100644 gitea-reports/templates/configmap.yaml create mode 100644 gitea-reports/templates/deployment.yaml rename {git-pages => gitea-reports}/templates/ingressroute.yaml (52%) create mode 100644 gitea-reports/templates/middleware.yaml rename {git-pages => gitea-reports}/templates/publish-auth-secret.yaml (62%) rename {git-pages => gitea-reports}/templates/pvc.yaml (75%) rename {git-pages => gitea-reports}/templates/retention-configmap.yaml (78%) rename {git-pages => gitea-reports}/templates/retention-cronjob.yaml (72%) rename {git-pages => gitea-reports}/templates/retention-rbac.yaml (57%) create mode 100644 gitea-reports/templates/service.yaml create mode 100644 gitea-reports/tests/retention.bats rename {git-pages => gitea-reports}/values.yaml (55%) diff --git a/.gitea/workflows/config-provider.yml b/.gitea/workflows/config-provider.yml index 3a25bd7..d53ac18 100644 --- a/.gitea/workflows/config-provider.yml +++ b/.gitea/workflows/config-provider.yml @@ -8,7 +8,7 @@ on: secrets: GITEA_TOKEN: required: true - GIT_PAGES_PUBLISH_TOKEN: + REPORTS_PUBLISH_TOKEN: required: true outputs: env_json: @@ -19,7 +19,7 @@ on: env: CI_CONF_FILE: ${{ inputs.config_path }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }} + REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }} jobs: parse-config: diff --git a/.gitea/workflows/example-bats-tests.yml b/.gitea/workflows/example-bats-tests.yml index 6a7a507..71a492b 100644 --- a/.gitea/workflows/example-bats-tests.yml +++ b/.gitea/workflows/example-bats-tests.yml @@ -12,14 +12,14 @@ on: secrets: GITEA_TOKEN: required: true - GIT_PAGES_PUBLISH_TOKEN: + REPORTS_PUBLISH_TOKEN: required: true env: GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }} GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }} + REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }} jobs: bats: diff --git a/.gitea/workflows/example-cucumber-tests.yml b/.gitea/workflows/example-cucumber-tests.yml index a9ab2d6..7efa7e4 100644 --- a/.gitea/workflows/example-cucumber-tests.yml +++ b/.gitea/workflows/example-cucumber-tests.yml @@ -12,14 +12,14 @@ on: secrets: GITEA_TOKEN: required: true - GIT_PAGES_PUBLISH_TOKEN: + REPORTS_PUBLISH_TOKEN: required: true env: GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }} GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }} + REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }} jobs: cucumber: diff --git a/.gitea/workflows/git-pages.ci-main.yml b/.gitea/workflows/gitea-reports.ci-main.yml similarity index 84% rename from .gitea/workflows/git-pages.ci-main.yml rename to .gitea/workflows/gitea-reports.ci-main.yml index 112478a..0399d88 100644 --- a/.gitea/workflows/git-pages.ci-main.yml +++ b/.gitea/workflows/gitea-reports.ci-main.yml @@ -1,11 +1,11 @@ -name: CI Git-Pages Main +name: CI Gitea Reports Main on: push: branches: - main paths: - - git-pages/** - - .gitea/workflows/git-pages.* + - gitea-reports/** + - ".gitea/workflows/gitea-reports.*" workflow_dispatch: jobs: @@ -14,7 +14,7 @@ jobs: uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main secrets: inherit with: - config_path: .gitea/workflows/git-pages.gitea-env.conf + config_path: .gitea/workflows/gitea-reports.gitea-env.conf check-version: name: Latest version @@ -33,7 +33,7 @@ jobs: with: env_json: ${{ needs.load-config.outputs.env_json }} version: ${{ needs.check-version.outputs.version }} - chart_path: git-pages + chart_path: gitea-reports chart-gitops: name: GitOps @@ -44,7 +44,7 @@ jobs: env_json: ${{ needs.load-config.outputs.env_json }} version: ${{ needs.check-version.outputs.version }} GITOPS_FILE: dev/Chart.yaml - GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "git-pages") | .version) = "{{VERSION}}"' + GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "gitea-reports") | .version) = "{{VERSION}}"' GITOPS_REPO: niko/gitea-ci-gitops-tests report-summary: diff --git a/.gitea/workflows/git-pages.gitea-env.conf b/.gitea/workflows/gitea-reports.gitea-env.conf similarity index 70% rename from .gitea/workflows/git-pages.gitea-env.conf rename to .gitea/workflows/gitea-reports.gitea-env.conf index eea1639..e5a5bc4 100644 --- a/.gitea/workflows/git-pages.gitea-env.conf +++ b/.gitea/workflows/gitea-reports.gitea-env.conf @@ -1,6 +1,5 @@ GITEA_API_URL=https://gitea.app.keskikuja.site HELM_REGISTRY=gitea.app.keskikuja.site/niko HELM_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container -GIT_TAG_PREFIX=git-pages/ -VERSION_FILE=git-pages/Chart.yaml - +GIT_TAG_PREFIX=gitea-reports/ +VERSION_FILE=gitea-reports/Chart.yaml diff --git a/.gitea/workflows/report-summary.yml b/.gitea/workflows/report-summary.yml index ff4eacc..1960756 100644 --- a/.gitea/workflows/report-summary.yml +++ b/.gitea/workflows/report-summary.yml @@ -25,7 +25,7 @@ jobs: shell: bash run: | SHA8="${GITHUB_SHA:0:8}" - BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}" + BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}" { echo "## Test Reports" diff --git a/README.md b/README.md index 2953bf1..a65a7aa 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ Consumer-repossa on oltava seuraavat asetukset: | Secret | Kuvaus | |--------|--------| -| `GIT_PAGES_PUBLISH_TOKEN` | Git-pages-palvelimen BasicAuth-token. Nimi on lukittu — tämä tarkka nimi vaaditaan. | +| `REPORTS_PUBLISH_TOKEN` | Git-pages-palvelimen BasicAuth-token. Nimi on lukittu — tämä tarkka nimi vaaditaan. | `GITEA_TOKEN` on Gitean automaattisesti jokaiselle workflow-runille generoima token (`secrets.GITEA_TOKEN`). Se on scopeutettu **siihen repoon**, jossa workflow ajaa — ei toimi toiseen repoon dispatchaukseen eikä toisen repon commit-statusin asettamiseen. Ei tarvitse erikseen luoda. @@ -300,7 +300,7 @@ GIT_PAGES_URL=https://ci-reports.example.com Jokaisen jobin alussa `ci-validate.sh` tarkistaa: - `.gitea/workflows/gitea-env.conf` on olemassa ja sen arvot ovat validit -- `GITEA_TOKEN` ja `GIT_PAGES_PUBLISH_TOKEN` on asetettu +- `GITEA_TOKEN` ja `REPORTS_PUBLISH_TOKEN` on asetettu Jos validointi epäonnistuu, job keskeytyy exit-koodilla 1 ja Gitean commit-status näyttää epäonnistumisen linkkinä lokiin. diff --git a/docs/adr/0005-provider-consumer.md b/docs/adr/0005-provider-consumer.md index 5475a70..12ab0f7 100644 --- a/docs/adr/0005-provider-consumer.md +++ b/docs/adr/0005-provider-consumer.md @@ -26,7 +26,7 @@ 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`) +- Määrittelee Gitea-secretit (`REPORTS_PUBLISH_TOKEN`, `GITEA_TOKEN`) ### Provider → Consumer (mitä provider tarjoaa) diff --git a/docs/ci-pipeline-practices.md b/docs/ci-pipeline-practices.md index cb2aa26..ecbfe83 100644 --- a/docs/ci-pipeline-practices.md +++ b/docs/ci-pipeline-practices.md @@ -96,7 +96,7 @@ Avainkomponentit: - **workflow `env:`** — ainoa tapa jakaa arvot kaikkiin jobeihin. `fromJson(inputs.env_json).KEY` purkaa yksittäiset arvot ilman toistoa. - **Per-job `env:`** — sisältää vain secretit (`GITEA_TOKEN`, - `GIT_PAGES_PUBLISH_TOKEN`), ei config-arvoja. + `REPORTS_PUBLISH_TOKEN`), ei config-arvoja. ## 5. Pipeline Provides All Dependencies diff --git a/docs/config-model.md b/docs/config-model.md index 75dcaba..b70bac1 100644 --- a/docs/config-model.md +++ b/docs/config-model.md @@ -69,7 +69,7 @@ org-tason token. | Secret | Pakollinen | Käyttäjä | |---|---|---| | `GITEA_TOKEN` | Kyllä | `report-status.sh`, `check-version.yml`, `docker-build-push.yml`, `gitops-update.sh` (GitOps-repossa) | -| `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | `publish-git-pages.sh`, `config-provider.yml` (validointi) | +| `REPORTS_PUBLISH_TOKEN` | Kyllä | `publish-git-pages.sh`, `config-provider.yml` (validointi) | | `DOCKER_USERNAME` | Ei | `docker-build-push.yml` (oletus: `github.actor`, ei pakollinen kaikissa registryissä) | | `DOCKER_PASSWORD` | Kyllä | `docker-build-push.yml` | @@ -88,7 +88,7 @@ JSON-muotoisen `env_json`:n. - `.conf`-tiedosto on olemassa - Jokaisella `KEY=VALUE`-rivillä on arvo (ei tyhjää) - URL-tyyppiset avaimet alkavat `http://` tai `https://` -- Pakolliset secretit (`GITEA_TOKEN`, `GIT_PAGES_PUBLISH_TOKEN`) on asetettu +- Pakolliset secretit (`GITEA_TOKEN`, `REPORTS_PUBLISH_TOKEN`) on asetettu Kutsu: ```yaml diff --git a/docs/design-rationale.md b/docs/design-rationale.md index 2a61245..dc1ce0e 100644 --- a/docs/design-rationale.md +++ b/docs/design-rationale.md @@ -1,227 +1,44 @@ -# Design Rationale — Gitea Actions CI -kirjasto +# Design Rationale — git-pages → Nginx + PV -> Miksi kirjasto on rakennettu näin. Arvot, periaatteet ja reunaehdot, joiden -> varaan arkkitehtuuri nojaa. -> -> Tämä dokumentti on **normatiivinen** — arkkitehtuurin on noudatettava näitä -> periaatteita. Jos ehdotettu muutos on ristiriidassa rationalen kanssa, -> rationalen on muututtava ensin. +> Päivitetty: 2026-06-28 — git-pages korvattu Nginx + upload-sidecar -arkkitehtuurilla. ---- +## Miksi git-pages poistettiin -## Miksi tämä projekti on olemassa +Codeberg git-pages storage v2 pakottaa kaikki tiedostot yhteen `.index`-protobufiin per site. Raja on 1MB kovakoodattu. Monorepo täytti tämän — uusia raportteja ei voinut julkaista. -Mikropalveluarkkitehtuurissa jokainen palvelu tarvitsee CI-putken: testit, -laatutarkistukset, buildin, kontituksen ja julkaisun. Ilman jaettua -kirjastoa jokainen tiimi kopioi saman YAML-boilerplaten, tekee omat -virheensä ja ylläpitää omaa versiotaan. Ajan myötä putket ajautuvat erilleen -— toisessa on `shell: bash`, toisessa ei; toinen käyttää `set -o pipefail`, -toinen kadottaa exit-koodin `tee`:hen. +## Uusi arkkitehtuuri -Tämä kirjasto on se mitä kopioidaan. Se tarjoaa valmiit, testatut, -dokumentoidut rakennuspalikat joista jokainen tiimi kokoaa oman putkensa. -Palikat ovat Gitea Actionsin `uses:`-direktiivillä kutsuttavia reusable -workflow'ta — ei asennusta, ei runtime-riippuvuutta, ei versiopäivityksiä -projekteihin. +- **Nginx** palvelee staattisia tiedostoja suoraan PV:ltä +- **Upload-sidecar** (busybox httpd + CGI) ottaa vastaan PUT + tar.gz ja purkaa PV:lle +- **URL = FS-polku** — `/{owner}/{repo}/{branch}/{sha8}/{suite}/` +- **Branch raakana URL:ssa** — `feature/x` FS:ssä = `feature/x/` +- **Linkki on aina 1-suuntainen** — gitea → raportti +- **GITEA_API_URL poistuu** publish-skriptin pakollisista env-varista ---- +## Ratkaisun periaatteet -## Suunnitteluperiaatteet +### 1. Ei välikerrosta — tiedostot suoraan PV:llä -### 1. Palikka-arkkitehtuuri: pieniä, vaihdettavia, yhden vastuun workflow'ta +git-pagesin `.index`-protobuf poistuu. Tiedostot ovat suoraan PV:llä kansiorakenteessa. Nginx palvelee niitä suoraan. -Jokainen provider-workflow tekee yhden asian: +### 2. Yksinkertainen upload-sidecar -| Workflow | Vastuu | -|---|---| -| `config-provider.yml` | Lataa ja validoi konfiguraatio | -| `check-version.yml` | Tarkistaa onko commit buildattu, laskee version | -| `docker-build-push.yml` | Buildaa, puskea ja tagittaa kontin | +Python HTTP-serveri ottaa vastaan PUT-pyynnöt ja purkaa tar.gz:n oikeaan paikkaan. Ei monimutkaista API:a, ei tietokantaa. -Mikään workflow ei kutsu toista provider-workflowta. Consumer -— siis mikropalvelun oma pipeline-tiedosto — on ainoa paikka joka -tietää mitä palikoita tarvitaan ja missä järjestyksessä. +### 3. Julkaisu ja luku eri porteilla -**Miksi:** Tämä on sama periaate kuin Unix-putkissa tai mikropalveluissa: -pieniä, itsenäisiä komponentteja jotka tekevät yhden asian hyvin. -Consumer voi vaihtaa yhden palikan toiseen — esimerkiksi Docker-buildin -tilalle Maven-paketoinnin — ilman että muut palikat muuttuvat. -Ratkaisu ei ole se että kaikki ajetaan, vaan se että jokainen tiimi -valitsee mitä tarvitsee. Monoliittinen "kaikki yhdessä" -workflow -pakottaisi jokaisen tiimin ajamaan tarpeettomia vaiheita. +Eri portit mahdollistavat eri Traefik-säännöt: julkaisu vaatii BasicAuthin, luku on julkinen. -### 2. Gitea ensin — hyödynnä alustaa, älä taistele sitä vastaan +### 4. Retention lukee PV:tä suoraan -Gitea Actions tarjoaa kolme asiaa ilmaiseksi: +Ei HTTP API:a retentionia varten. Skripti listaa kansiot, tarkistaa branchit Giteasta, poistaa vanhat. -1. **Jobien visuaalinen status** — jokainen jobi näkyy automaattisesti - commit-näkymässä checkmarkilla, spinnerillä tai ristillä. -2. **Cross-job riippuvuudet** — `needs` hoitaa virheiden propagointin: - jos edeltävä jobi feilaa, riippuvat jobit skipataan. -3. **Reusable workflow -jakelu** — `uses: org/repo/.gitea/workflows/file.yml@v1` - on natiivisti versioitu, skopattu ja välimuistitettu. +## Rajapinnat -Kirjasto käyttää näitä kaikkia. Ei omaa tilakonetta, ei custom -action -runtimea, ei ulkoista orkestraattoria. - -**Esimerkki:** Tool-jobit eivät kutsu commit-status API:a lainkaan. -Gitean oma job-status riittää — `success`/`failure`/`running` näkyy -automaattisesti. API:a käytetään vain kun tarvitaan **custom-linkki** -(testiraporttiin tai Docker registryyn), jota natiivistaatus ei tarjoa. -Tämä linjaus on dokumentoitu ADR 0004 ja 0007:ssä. - -### 3. Status näkyy siellä missä työ tehdään — Git-commitissa - -Kehittäjä työskentelee Gitissä. `git log`, `git blame`, PR-näkymä — -nämä ovat päivittäiset työkalut. CI-statuksen kuuluu näkyä siellä, -ei erillisessä dashboardissa. - -Gitea Actionsin natiivi job-status tekee tämän automaattisesti: -jokainen commit näyttää välittömästi mitkä jobit on ajettu ja millä -tuloksella. Testiraportteihin pääsee yhdellä klikkauksella commitin -status-kuvakkeesta — koska `report-status.sh` asettaa `target_url`:n -osoittamaan suoraan HTML-raporttiin git-pagesissa. - -Tämä ei ole kosmeettinen yksityiskohta. Se on devops-käytännön -ydin: palautesilmukka on lyhin mahdollinen. Commit → build → status -näkyy samassa näkymässä jossa kehittäjä jo on. - -### 4. Exit-koodi on ainoa totuus - -CI-putken jokaisen `run`-stepin onnistuminen määräytyy **vain ja -ainoastaan** exit-koodin perusteella. Ei tiedoston olemassaolon, ei -stdout-tulosteen, ei arvauksen. `0` = ok, kaikki muu = ei ok. - -Tämä kuulostaa itsestään selvältä, mutta YAML-pipelineissa se rikkoutuu -helposti. Pipe (`|`) `tee`:hen syö exit-koodin. Tiedoston olemassaolon -tarkistus (`[ -f results.xml ]`) ei kerro testien läpimenosta. - -**Käytännössä:** Jokainen `run`-steppi ottaa exit-koodin talteen -`$?`-muuttujaan ennen kuin mikään muu komento ehtii muuttaa sitä, -ja stepin viimeinen rivi on `exit ${EXIT}`. Pipeä ei käytetä -työvaiheen viimeisenä komentona. Ks. ADR 0008. - -### 5. Pienin mahdollinen pinta-ala - -Jokainen ylimääräinen riippuvuus on ylimääräinen vikaantumispiste. -Kirjaston ainoat riippuvuudet: - -- Gitea Actions (alusta) -- `bash`, `curl`, `jq` (ubuntu-latest runnerissa valmiina) -- Docker (runnerissa valmiina) -- git-pages (raporttien hostaus, erillinen palvelu) - -Ei Pythonia, ei Node.js:ää ajonaikaisesti (testit omissa konteissaan). -Ei tietokantaa. Ei ulkoista tilanhallintaa. Kirjasto on joukko -YAML-tiedostoja ja shell-skriptejä — samat työkalut jotka jokainen -devops-ihminen jo osaa. - -### 6. Konfiguraatio repoon, salaisuudet Giteaan - -Projektikohtainen konfiguraatio (`.gitea/workflows/gitea-env.conf`) -asuu mikropalvelun omassa repossa. Kehittäjä omistaa sen — hän tietää -mikä on Docker-imagen nimi, mihin registryyn puskea, mikä on -testiympäristön URL. - -Salaisuudet (tokenit, salasanat) elävät Gitean secrets-mekanismissa, -eivät repon tiedostoissa. `secrets: inherit` välittää ne providerin -workflow'hun ilman että consumerin tarvitsee tietää mitä salaisuuksia -mikäkin provider tarvitsee. - -Poikkeus: infra-tason asetukset (`GIT_PAGES_URL`, `GITEA_API_URL`) -ovat Gitean organization secrets/variables -mekanismissa. Ne eivät -ole repokohtaisia. - -### 7. Consumer omistaa orkestroinnin, provider tarjoaa palikat - -Tämä on kirjaston tärkein arkkitehtuurinen päätös (ADR 0005). - -Provider (`gitea-ci-library`) ei tiedä mitä testejä ajetaan, missä -järjestyksessä, tai millä branchilla. Se tarjoaa kolme reusable -workflow'ta ja joukon skriptejä. - -Consumer (mikropalvelun `example-feature.yml` / `example-main.yml`) -päättää: -- Mitkä palikat kutsutaan -- Missä järjestyksessä (`needs`) -- Millä branch-ehdoilla (`if`) -- Mitkä testikontit käytetään (input-parametrit) - -Tämä on tarkoituksellinen vallanjako. Provider ei voi tietää jokaisen -tiimin tarpeita — eikä sen pidäkään. Consumer ei voi muuttaa providerin -sisäistä toteutusta — eikä sen pidäkään. Rajapinta on `workflow_call` ja -se on molemmille osapuolille selvä. - -### 8. Branch-kohtainen reititys, ei yhtä kaikille - -Eri brancheilla on eri tavoite: - -- **Feature-haara:** Onko koodi laadukasta? → testit, validointi -- **Main-haara:** Onko tästä versiosta jo artifakti? Jos ei → - testit + build + push + tag. Jos on → ei tehdä mitään (tai - jatketaan klusteritesteihin). - -Tämä logiikka elää consumerin pipeline-tiedostossa, ei providerissa. -Se on puhdasta `if`-ehtoa ja `needs`-ketjutusta — ei skriptausta, -ei monimutkaisia ehtoja providerin sisällä. - -### 9. Raportit erillisellä palvelulla, linkit commitissa - -Gitea Actionsin artifact-järjestelmä on binääriarkisto — ZIP-lataus, -ei HTML-selailtavuutta. Testiraportit (Cucumber HTML, Bats-coverage) -on voitava avata selaimessa yhdellä klikkauksella. - -Ratkaisu: git-pages Helm-chartti, joka tarjoaa staattista -tiedostohostingia HTTP:llä. `publish-git-pages.sh` vie raportit -sinne; `report-status.sh` linkittää commit-statuksen suoraan -raporttiin. Retention hoitaa git-pagesin sidecar automaattisesti. - -Tulevaisuudessa `GITHUB_STEP_SUMMARY` (Gitea 1.27+) tarjoaa -vaihtoehtoisen kanavan: jobin Summary-välilehdelle renderöityvä -Markdown-taulukko kaikista raporttilinkeistä. - -### 10. Vain Gitea — ei monialustatukea ilman tarvetta - -Yhden alustan tukeminen kunnolla on vaikeampaa kuin kolmen tukeminen -huonosti. Gitea Actionsin `uses:`-mekanismi, `needs`-semantiikka, -`secrets: inherit`, `gitea`-konteksti — nämä ovat alustakohtaisia -ominaisuuksia joita abstraktiokerros vain haittaisi. - -Jos toinen alusta tulee ajankohtaiseksi, sille kirjoitetaan oma -toteutus. Siihen asti yksi alusta riittää. Ennenaikainen yleistys -on devopsissa yhtä haitallista kuin ohjelmistosuunnittelussa. - ---- - -## Arkkitehtuuriset rajoitteet - -### Mitä kirjasto EI tee - -- **Ei ulkoista orkestraattoria.** Pipeline-ohjaus on Gitea Actionsin - `needs`-ketjuissa ja consumerin `if`-ehdoissa. -- **Ei custom actioneita.** Reusable workflow on kevyempi, versioitu - ja jaeltu Gitean oman mekanismin kautta. -- **Ei asennusta projekteihin.** Consumer viittaa `uses:`-direktiivillä - suoraan tämän repon workflow-tiedostoihin. Ei npm-pakettia, ei - git-submodulea, ei kopioitavia tiedostoja. -- **Ei runtime-riippuvuuksia.** Provider-skriptit käyttävät vain - työkaluja jotka ovat Gitea Actionsin `ubuntu-latest` runnerissa - valmiina: `bash`, `curl`, `jq`. -- **Ei monorepo-konfiguraatiota.** Jokainen mikropalvelu omistaa - oman pipeline-tiedostonsa ja konfiguraationsa. - ---- - -## Mitä tietoisesti hylättiin - -| Hylätty | Syy | -|---|---| -| Monoliittinen "kaikki yhdessä" -workflow | Pakottaa kaikille samat vaiheet. Palikka-arkkitehtuuri antaa jokaiselle tiimille vain mitä se tarvitsee | -| Oma orkestraattoripalvelin | Ylimääräinen ylläpidettävä. Gitean `needs` ja `if` riittävät | -| Docker-pohjaiset custom actionit | Tuovat riippuvuuden Docker-rekisteriin. Reusable workflow on natiivimpi | -| Commit-status API jokaiselle vaiheelle | Duplikointia — Gitea näyttää job-statuksen automaattisesti. API vain custom-linkeille | -| `tee`-putki debug-näkyvyyteen | Syö exit-koodin. stdout ohjataan tiedostoon `>` ilman pipeä | -| Multi-Git-platform-tuki | Ennenaikaista optimointia ilman tarvetta | -| Gitea Packages raporttien hostingiin | Ei HTML-selailtavuutta — vain binäärilataus | -| Gitea Pages + reports-branch | Race condition rinnakkaisten pushien kanssa | -| `repository_dispatch` ketjutukseen | Lisää konfiguraatiota vastaanottaviin repoihin. Suora API-kutsu eksplisiittisempi | +| Suunta | Protokolla | Auth | Kuvaus | +|--------|------------|------|--------| +| Julkaisija → Traefik | HTTPS PUT | BasicAuth `publish` | tar.gz → upload-sidecar | +| Selain → Traefik | HTTPS GET/HEAD | — | HTML-raportti nginxiltä | +| Retention → Gitea | HTTPS GET | PAT | branch-tarkistus | +| Traefik → nginx | HTTP :8080 | — | sisäverkko | +| Traefik → upload | HTTP :8081 | — | sisäverkko (BasicAuth tarkistettu) | diff --git a/docs/git-pages-nginx-arkkitehtuuri.md b/docs/git-pages-nginx-arkkitehtuuri.md new file mode 100644 index 0000000..d2a21cd --- /dev/null +++ b/docs/git-pages-nginx-arkkitehtuuri.md @@ -0,0 +1,551 @@ +# git-pages → Nginx + PV -arkkitehtuuri + +> Päivitetty: 2026-06-28 + +## Ongelma + +git-pages storage v2 pakottaa kaikki tiedostot yhteen `.index`-protobufiin per site. +Raja on 1MB kovakoodattu. Yksi monorepo täytti tämän, eikä uusia raportteja voi +julkaista ennen kuin ongelma on ratkaistu. + +## Ratkaisu + +Poista git-pages kokonaan. Korvaa Nginx:llä (static serving) + kevyellä upload-sidecarilla (tar.gz HTTP PUT → extract). Tiedostot suoraan PV:llä ilman `.index`-välikerrosta. + +## Lukitut päätökset + +1. **git-pages poistuu** — sovellus ei ole mukana luku- eikä kirjoitusketjussa +2. **Nginx palvelee suoraan PV:ltä** — `root /data`, `autoindex on`, `try_files $uri $uri/ $uri/index.html =404` +3. **Upload-sidecar** — busybox httpd + CGI, ottaa vastaan `PUT /path` + tar.gz body ja purkaa PV:lle +4. **URL = FS-polku** — ei Host-header-kikkaa, ei slugia, ei rewritea +5. **Traefik** — PATCH/PUT → upload-sidecar:8081 (BasicAuth), GET/HEAD → nginx:8080 (ForwardAuth myöh.) +6. **CI-julkaisu** — `curl -X PUT --data-binary @report.tar.gz https://{host}/{owner}/{repo}/{branch_raw}/{sha8}/{suite}/` +7. **Branch raakana URL:ssa** — `feature/x` URL:ssa = `feature/x/` FS:ssä. Ei slugitusta +8. **Linkki on aina 1-suuntainen** — gitea → raportti. Raportista ei linkkiä giteaan +9. **GITEA_API_URL poistuu** publish-skriptin pakollisista env-varista (retention käyttää omaansa) + +## Ympäristömuuttujat + +### GITHUB_REF_NAME + +Gitea Actions runnerin automaattisesti asettama muuttuja, joka on branchin/tagin nimi ilman `refs/heads/`-etuliitettä. + +| Tilanne | Arvo | +|---------|------| +| branch `main` | `main` | +| branch `feature/branch` | `feature/branch` | +| tag `v1.0.0` | `v1.0.0` | + +Käytännössä `git rev-parse --abbrev-ref HEAD`. Branchin `/` on sallittu URL-polussa, joten `GITHUB_REF_NAME` voidaan käyttää sellaisenaan rakenteessa `${GITHUB_REF_NAME}/${SHA8}/${SUITE}`. Rinnakkaisissa buildeissa jokainen runner ajaa oman branchinsa kontekstissa → URL pysyy uniikkina. + +``` +CI → curl -X PUT → Traefik (BasicAuth middleware) + → upload-sidecar:8081 (busybox httpd + CGI) + → tar -xzf - -C /data/{owner}/{repo}/{branch}/{sha8}/{suite}/ + → PV /app/data/ + +Browser → GET → Traefik + → nginx:8080 (root /app/data) + → /data/{owner}/{repo}/{branch}/{sha8}/{suite}/index.html +``` + +## Tiedostomuutokset + +### Poistuu (4 tiedostoa) + +| Tiedosto | Miksi | +|----------|-------| +| `templates/init-job.yaml` | Ei enää git-pages API:a alustettavaksi | +| `templates/configmap.yaml` (nykyinen) | Korvataan nginx-conf + CGI | +| `templates/deployment.yaml` (nykyinen) | Korvataan uudella | +| `git-pages-image` (values.yaml) | Ei enää git-pages-konttia | + +### Muuttuu (8 tiedostoa) + +| # | Tiedosto | Muutos | +|---|----------|--------| +| 1 | `Chart.yaml` | description, poista appVersion | +| 2 | `values.yaml` | Poista git-pages-keyt, lisää nginx/sidecar | +| 3 | `dev-values.yaml` | Matchaa uusi values-rakenne | +| 4 | `templates/configmap.yaml` | `default.conf` (nginx) + `upload.cgi` | +| 5 | `templates/deployment.yaml` | 2 konttia: nginx:alpine (8080) + busybox httpd (8081) | +| 6 | `templates/ingressroute.yaml` | Backend: PATCH/PUT → upload:8081, GET/HEAD → nginx:8080 | +| 7 | `templates/service.yaml` | 2 porttia: http-read(8080), http-write(8081) | +| 8 | `templates/NOTES.txt` | Uusi URL + esimerkit | + +### Pysyy ennallaan (8 tiedostoa) + +| Tiedosto | Huomio | +|----------|--------| +| `templates/certificate.yaml` | TLS hostille | +| `templates/middleware.yaml` | BasicAuth + HTTPS redirect | +| `templates/publish-auth-secret.yaml` | Sama BasicAuth-secret | +| `templates/pvc.yaml` | Sama PVC | +| `templates/retention-configmap.yaml` | Päivitä retention-scriptit | +| `templates/retention-cronjob.yaml` | Pieni muutos (ei git-pages API) | +| `templates/retention-rbac.yaml` | Ennallaan | +| `_helpers.tpl` | Vain label-helpers | + +### Chartin ulkopuolella + +| Tiedosto | Muutos | +|----------|--------| +| `scripts/publish-git-pages.sh` | Uusi: `curl -X PUT` tar.gz upload-sidecariin | +| `scripts/report-status.sh` | URL: `reports/` → `${GITHUB_REF_NAME}/` | +| `scripts/ci-report.sh` | URL (rivi 101): `reports/` → `${GITHUB_REF_NAME}/` | +| `git-pages/docs/architecture.md` | Uusi arkkitehtuuri | +| `docs/design-rationale.md` | Päivitys | +| `files/retention-*.sh` | Lue PV:tä suoraan, ei git-pages API:a | + +## Upload CGI + +Busybox httpd välittää CGI-skriptille tiedot ympäristömuuttujissa (`REQUEST_METHOD`, `PATH_INFO`, `CONTENT_LENGTH`). Skripti on tiedosto `/cgi/upload.cgi` — `.cgi`-pääte laukaisee CGI-execution. + +```bash +#!/bin/sh +# REQUEST_METHOD, PATH_INFO, etc. asettaa busybox httpd automaattisesti + +if [ "$REQUEST_METHOD" = "PUT" ]; then + dest="/app/data${PATH_INFO%/}" + mkdir -p "$dest" + cat - | tar -xzf - -C "$dest" + echo "Status: 201 Created" + echo "" +fi +``` + +**PUT `https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`** → CGI purkaa: +- `PATH_INFO` = `/{owner}/{repo}/{branch}/{sha8}/{suite}/` +- `dest` = `/app/data/{owner}/{repo}/{branch}/{sha8}/{suite}` +- Tarin tiedostot puretaan tähän + +**Huomio:** Skripti käyttää tar.gz-pakkausta. Jos tar on raaka (ilman gzip:ia), vaihda `-xzf` → `-xf`. + +### CGI-kutsu + +Busybox httpd käynnistetään: + +```bash +busybox httpd -f -p 8081 -h /cgi +``` + +`-h /cgi` on docroot. Skriptitiedoston pääte `.cgi` (esim. `/cgi/upload.cgi`) käynnistää CGI-execution automaattisesti. + +## Nginx-konfiguraatio + +```nginx +client_max_body_size 100M; # nousevat raportit; oletus 1M on liian pieni + +server { + listen 8080; + root /app/data; + autoindex on; + client_max_body_size 100M; + + location / { + try_files $uri $uri/ $uri/index.html =404; + } +} +``` + +Alpine-nginx lukee configit `/etc/nginx/http.d/default.conf`. ConfigMap mountataan tähän polkuun. + +## Deployment (kontit) + +```yaml +containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 8080 + name: http-read + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/http.d + - name: data + mountPath: /app/data + - name: upload + image: alpine:latest + command: + - busybox + - httpd + - -f + - -p + - "8081" + - -h + - /cgi + ports: + - containerPort: 8081 + name: http-write + volumeMounts: + - name: cgi-scripts + mountPath: /cgi + readOnly: true + - name: data + mountPath: /app/data +``` + +## values.yaml-rakenne + +```yaml +# git-pages → Nginx + upload-sidecar + +nameOverride: "" +fullnameOverride: "" + +nginx: + image: nginx + tag: alpine + port: 8080 + bodySize: 100M + +upload: + image: alpine + tag: latest + port: 8081 + +service: + type: ClusterIP + +persistence: + enabled: true + accessMode: ReadWriteOnce + storageClass: "" + size: 5Gi + +ingress: + enabled: true + host: ci-reports.helm-dev.keskikuja.site + entryPoints: + websecure: websecure + web: web + +certificate: + enabled: true + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + +publishAuth: + create: false + htpasswdUsers: "" + +retention: + enabled: false + mode: cronjob + schedule: "0 3 * * *" + image: + repository: debian + tag: bookworm-slim + giteaApiUrl: "" + rules: + branches: + default: + minAgeDays: 7 + keepMin: 5 +``` + +## CI-julkaisu (publish-skripti) + +### Muutokset nykyiseen + +| Kohta | Nykyinen | Uusi | +|-------|----------|------| +| `GITEA_API_URL` | pakollinen env-var | **poistettu** (retention käyttää omaansa) | +| Tar-prefiksi | `{owner}/{repo}/reports/{sha8}/{suite}` | ei prefiksiä — `{suite}` | +| Paketti | `tar -cf` | `tar -czf` (gzip) | +| Content-Type | `application/x-tar` | `application/tar+gz` | +| Headerit | `Atomic: no`, `Create-Parents: yes` | poistettu | +| Kohde | `PATCH {GIT_PAGES_URL}/` | `PUT {GIT_PAGES_URL}/{owner}/{repo}/{branch}/{sha8}/{suite}/` | +| Branch | vain `.meta` | URL:ssa + `.meta` | + +### Uusi skripti (ydin) + +```bash +#!/usr/bin/env sh +set -eu + +SUITE_PATH="${1:-}" +[ -n "$SUITE_PATH" ] || { echo "ERROR: suite_path required" >&2; exit 1; } +[ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; } +[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; } +[ -n "${GITHUB_REPOSITORY:-}" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; } +[ -n "${GITHUB_SHA:-}" ] || { echo "ERROR: GITHUB_SHA is not set" >&2; exit 1; } +[ -n "${GITHUB_REF_NAME:-}" ] || { echo "ERROR: GITHUB_REF_NAME is not set" >&2; exit 1; } + +OWNER="${GITHUB_REPOSITORY%%/*}" +REPO="${GITHUB_REPOSITORY##*/}" +SHA8=$(echo "$GITHUB_SHA" | cut -c1-8) +BRANCH="${GITHUB_REF_NAME}" +SUITE="${SUITE_PATH%/}" +PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}" + +[ -d "$SUITE" ] || { echo "ERROR: not a directory: $SUITE" >&2; exit 1; } + +WORK=$(mktemp -d) +TAR=$(mktemp) +trap 'rm -rf "$WORK" "$TAR"' EXIT + +# Kopioi raporttitiedostot +mkdir -p "$WORK/$SUITE" +cp -a "$SUITE/." "$WORK/$SUITE/" + +# Generoi index.html (sama logiikka kuin nykyään) +cd "$WORK/$SUITE" +if [ ! -f "index.html" ]; then + # identtinen item-listaus kuin nykyisessä skriptissä + … +fi + +# .meta tiedosto retentionia varten +cat > ".meta" <&2 + cat /tmp/git-pages-publish-response.txt >&2 + exit 1 + ;; +esac + +echo "${PUBLISH_URL}" +``` + +## Gitea commit -linkki (report-status.sh) + +Gitean commit-näkymään tuleva raporttilinkki muodostetaan `scripts/report-status.sh`:ssa. + +### Muutos + +| Kohta | Nykyinen (rivi 21) | Uusi | +|-------|-------------------|------| +| URL | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}` | + +`reports/` → `{GITHUB_REF_NAME}/`. Branchin raaka nimi URL-polussa on sallittu. Muu skripti (Gitea API -kutsu, token, json-body) pysyy identtisenä. + +Tarkista: `GITHUB_REF_NAME` on oltava asetettu — Gitea Actions asettaa sen automaattisesti. Jos sitä tarvitaan tarkistuksena, lisätään `report-status.sh`:n env-var-tarkistuksiin. + +## Gitea Actions step summary (report-summary.yml) + +`.gitea/workflows/report-summary.yml` luo GITHUB_STEP_SUMMARY -taulukon raporttilinkeillä. + +### Muutos + +| Kohta | Nykyinen (rivi 28) | Uusi | +|-------|-------------------|------| +| BASE | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}` | + +Sama muuri: `reports/` → `${GITHUB_REF_NAME}/`. Loppuosa identtinen. + +## CI-report (ci-report.sh) + +`scripts/ci-report.sh` rakentaa URL:n suoraan single-entry-tapauksessa (rivi 101). + +### Muutos + +| Kohta | Nykyinen (rivi 101) | Uusi | +|-------|-------------------|------| +| URL | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${SINGLE_ENTRY}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}/${SINGLE_ENTRY}` | + +`reports/` → `${GITHUB_REF_NAME}/`. Lisäksi `GITHUB_REF_NAME` pitää lisätä env-var-tarkistuksiin, jos sitä ei ole. + +## Retention + +Retention siirtyy lukemaan PV:tä suoraan (find, ls, stat) git-pages API:n sijaan. Nykyinen malli luki `.git-pages/manifest.json` HTTP:lla — uusi lukee FS:ää. + +### Data + +``` +/app/data/{owner}/{repo}/{branch}/{sha8}/ + .meta ← {"branch":"...","sha":"...","published_at":"..."} + index.html + style.css + ... +``` + +### Logiikka commit-tasolla + +Retention on commit-kohtainen. Yksi commit = yksi kansio `/{sha8}/`, jonka alla voi olla useita suiteja (`cucumber/`, `bats/`, jne.). + +``` +/app/data/{owner}/{repo}/{branch}/ + abc12345/ ← yksi commit + cucumber/ + index.html + .meta + bats/ + index.html + .meta + def67890/ ← toinen commit + cucumber/ + index.html + .meta +``` + +Toiminta aktiiviselle branchille (löytyy Giteasta): + +1. Listaa commit-kansiot (`abc12345/`, `def67890/`, ...) uusin ensin (viimeksi muokattu) +2. Ohita `keepMin` kpl — nämä säilytetään aina +3. Jos raportti on alle `minAgeDays` päivää vanha → skip (liian tuore poistettavaksi) +4. Muut poistetaan (`rm -rf {sha8}/`), jolloin kaikki commitin suite-tulokset poistuvat kerralla + +Poistuneelle branchille (404 Giteasta): poista koko `{branch}/`-kansio. + +### Parametrit + +```yaml +retention: + rules: + branches: + default: + keepMin: 5 + minAgeDays: 7 + main: + keepMin: 20 + minAgeDays: 14 +``` + +**Parametrin nimi:** `maxAgeDays` → `minAgeDays` (arvo sama, uusi nimi kertoo mitä se todella tekee: minimi-ikä ennen poistoa). + +### Skripti (ydin) + +```bash +DATA_ROOT="/app/data" + +for owner_dir in "$DATA_ROOT"/*/; do + owner=$(basename "$owner_dir") + for repo_dir in "$owner_dir"*/; do + repo=$(basename "$repo_dir") + for branch_dir in "$repo_dir"*/; do + branch=$(basename "$branch_dir") + branch_urlenc=$(echo "$branch" | sed 's/\//%2F/g') + + # Tarkista onko branch yhä olemassa Giteassa + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $GITEA_TOKEN" \ + "$GITEA_API_URL/api/v1/repos/$owner/$repo/branches/$branch_urlenc") + + if [ "$HTTP_CODE" = "404" ]; then + # Branch poistettu — tuhoa koko branch + rm -rf "$branch_dir" + continue + fi + + # Aktiivinen branch — hae säännöt + rules=$(jq -r ".branches.\"$branch\" // .branches.default" "$RETENTION_CONFIG") + keep_min=$(echo "$rules" | jq -r '.keepMin // 5') + min_age=$(echo "$rules" | jq -r '.minAgeDays // 7') + + # Listaa commit-kansiot uusin ensin + commits=$(ls -1t "$branch_dir") + count=0 + for sha8 in $commits; do + [ -d "$branch_dir/$sha8" ] || continue + count=$((count + 1)) + + # Ohita keepMin kpl + [ "$count" -le "$keep_min" ] && continue + + # Tarkista ikä + age_seconds=$(( $(date +%s) - $(stat -c %Y "$branch_dir/$sha8") )) + age_days=$(( age_seconds / 86400 )) + [ "$age_days" -lt "$min_age" ] && continue + + # Poista koko commit + rm -rf "$branch_dir/$sha8" + done + done + done +done +``` + +## Deploy-järjestys (vaiheistus) + +### Vaiheet + +| # | Vaihe | Mitä | Riippuvuus | +|---|-------|------|-----------| +| 1 | **Poista vanha PVC** | `kubectl delete pvc git-pages-data -n gitea-reports` (tai helm uninstall + uusi asennus) | Vanha git-pages-data .index-muodossa — tuhotaan | +| 2 | **Deployaa uusi chart** | `helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml` | Uusi PVC luodaan tyhjänä. Vanhoja raportteja ei ole. | +| 3 | **Varmenna Nginx** | `curl localhost:8080/` → 404/autoindex | Nginx palvelee | +| 4 | **Varmenna upload** | `curl -X PUT -T test.tar.gz localhost:8081/test/` → 201 | CGI toimii | +| 5 | **Päivitä provider-skriptit** | `scripts/publish-git-pages.sh`, `scripts/report-status.sh`, `scripts/ci-report.sh` | Uusi URL-muoto | +| 6 | **Päivitä provider-workflow** | `.gitea/workflows/report-summary.yml` | BASE-muuttuja | +| 7 | **Varmenna CI-julkaisu** | `bash publish-git-pages.sh ` → 201 + URL | Koko ketju | + +### Consumer-workflowt EI päivitetä + +`.gitea/workflows/example-*` ja `.ci/scripts/*` (consumerien kopioimat skriptit) eivät muutu. + +### report-summary.yml + +On provider-workflow (ei `example-`-prefiksiä). Saa päivittyä. + +## Rollback-suunnitelma + +### Jos chart epäonnistuu + +```bash +helm rollback git-pages +git revert HEAD # tai git reset --hard ennen script-muutoksia +``` + +- Vanha PVC on poistettu → rollback palauttaa vanhan git-pages-deploymentin mutta ilman dataa +- Vanhaa `.index`-dataa ei palaudu — se tuhottiin vaiheessa 1 +- Jos uutta dataa on ehtinyt kertyä, se on uudella PVC:llä → vanha deployment ei löydä sitä + +### Suojaus + +Pidä vanha PVC hengessä (nimeä uudelleen, älä poista) kunnes rollback-ikkuna on umpeutunut: + +```bash +# Ennen vaihetta 1: uudelleennimeä vanha PVC +kubectl delete pvc git-pages-data -n gitea-reports --wait=false +kubectl patch pvc git-pages-data -n gitea-reports -p '{"metadata":{"finalizers":[]}}' --type merge # tarvittaessa +``` + +Jos rollback tarvitaan 24h sisällä, palauta vanha PVC backupista (esim. snapshot). + +## Varmennus (check-stepit) + +Jokaiselle komponentille check ennen seuraavaan vaiheeseen siirtymistä: + +| Vaihe | Check | +|-------|-------| +| **helm template** | `helm template gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml` → deploymentissa 2 konttia (nginx + upload) | +| **Nginx** | `kubectl exec -- wget -qO- http://localhost:8080/` → 404 tai autoindex (ei connection refused) | +| **Upload CGI** | `echo "hello" \| tar czf /tmp/test.tar.gz -C /tmp . && curl -sS -X PUT -H "Content-Type: application/tar+gz" --data-binary @/tmp/test.tar.gz http://localhost:8081/test/hello/` → status 201. Sitten `kubectl exec -- ls /app/data/test/hello/` → tiedostot näkyvissä | +| **Retention** | `kubectl exec -- bash /scripts/retention-run.sh` → exit 0, ei virheitä | +| **Provider-skripti (end-to-end)** | 1. `kubectl port-forward pod/ 8080:8080 8081:8081` 2. `bash scripts/publish-git-pages.sh ` → output URL (`https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`) 3. `curl http://localhost:8080/{owner}/{repo}/{branch}/{sha8}/{suite}/` → 200, HTML näkyy 4. `curl -I http://localhost:8080/{owner}/{repo}/{branch}/{sha8}/{suite}/index.html` → 200 | + +## Data migration + +**Ei migraatiota.** Vanha PVC sisältää git-pages storage v2 -dataa (`.index` protobuf). Tämä data tuhotaan tarkoituksella: + +1. CI on rikki 1MB rajoitteen takia — uusia raportteja ei voi julkaista ennen kuin git-pages on poistettu +2. Vanhat raportit ovat vanhentuneita (branchit on jo saatettu poistaa, commitit vanhoja) +3. Uudet buildit tuottavat raportit uuteen järjestelmään ilman migraatiota + +**Toimenpide:** +- Poista vanha PVC ennen uuden chartin deployausta +- Uusi chart luo uuden tyhjän PVC:n +- CI ajaa uudet buildit → raportit kirjoitetaan Nginx+PV-malliin diff --git a/docs/shared-scripts.md b/docs/shared-scripts.md index 3940913..8c593f4 100644 --- a/docs/shared-scripts.md +++ b/docs/shared-scripts.md @@ -79,7 +79,7 @@ publish-git-pages.sh |---|---| | `GITEA_API_URL` | `env_json` → workflow `env:` | | `GIT_PAGES_URL` | `env_json` → workflow `env:` | -| `GIT_PAGES_PUBLISH_TOKEN` | Gitea secret → `env:` | +| `REPORTS_PUBLISH_TOKEN` | Gitea secret → `env:` | | `GITHUB_REPOSITORY` | Automaattinen | | `GITHUB_SHA` | Automaattinen | @@ -104,7 +104,7 @@ Lukee tiedoston polun `CI_CONF_FILE`-env-muuttujasta (oletus: `.gitea/workflows/ - Jokaisella `KEY=VALUE`-rivillä on arvo (ei tyhjää) - URL-tyyppiset avaimet alkavat `http://` tai `https://` - `GITEA_TOKEN` on asetettu -- `GIT_PAGES_PUBLISH_TOKEN` on asetettu +- `REPORTS_PUBLISH_TOKEN` on asetettu --- @@ -155,7 +155,7 @@ dispatch-workflow.sh 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/secrets.md b/git-pages/docs/secrets.md deleted file mode 100644 index 054eac0..0000000 --- a/git-pages/docs/secrets.md +++ /dev/null @@ -1,237 +0,0 @@ -# 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/files/retention-cleanup.sh b/git-pages/files/retention-cleanup.sh deleted file mode 100644 index 0242070..0000000 --- a/git-pages/files/retention-cleanup.sh +++ /dev/null @@ -1,276 +0,0 @@ -#!/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; } - -declare -A REPO_BRANCHES_CACHE -declare -A REPO_STATUS - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/retention-lib.sh" - -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 SEEN_REPORTS -declare -A SEEN_ECHO_COMMITS -declare -a REPORTS -while IFS= read -r meta_path; do - report_dir=$(dirname "$meta_path") - - # Skip duplicates - same report dir already processed - [ -z "${SEEN_REPORTS[$report_dir]:-}" ] || continue - SEEN_REPORTS[$report_dir]=1 - - 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}") - - commit_dir=$(dirname "$report_dir") - if [ -z "${SEEN_ECHO_COMMITS[$commit_dir]:-}" ]; then - SEEN_ECHO_COMMITS[$commit_dir]=1 - echo " ${commit_dir} branch=${branch} age=${days}d" - fi -done <<< "$META_PATHS" - -[ "${#REPORTS[@]}" -eq 0 ] && { echo "No actionable reports"; exit 0; } - -echo "" -echo "=== Phase 2: check branches/repos in Gitea ===" -if [ -z "$GITEA_API_URL" ] || [ -z "$GITEA_TOKEN" ]; then - echo "ERROR: GITEA_API_URL and GITEA_TOKEN must be set" >&2 - exit 1 -fi -declare -a TO_DELETE -declare -a KEEP -declare -A SEEN_ECHO_BRANCHES -declare -A SEEN_ECHO_REPO_DELETED -declare -A UNIQUE_BRANCHES -declare -A REASON_MAP -declare -A COMMIT_BRANCH_MAP - -# Build commit→branch mapping -for entry in "${REPORTS[@]}"; do - IFS='|' read -r dir _ _ branch _ <<< "$entry" - commit_dir=$(dirname "$dir") - [ -n "${COMMIT_BRANCH_MAP[$commit_dir]:-}" ] || COMMIT_BRANCH_MAP["$commit_dir"]=$branch -done -for entry in "${REPORTS[@]}"; do - IFS='|' read -r _ owner repo branch _ <<< "$entry" - UNIQUE_BRANCHES["${owner}/${repo}/${branch}"]=1 -done -TOTAL_BRANCHES=${#UNIQUE_BRANCHES[@]} -BRANCHES_EXISTING=0 -BRANCH_DELETED_COUNT=0 -REPO_DELETED_COUNT=0 -MAXAGE_DELETED=0 -KEEPMIN_DELETED=0 -for entry in "${REPORTS[@]}"; do - IFS='|' read -r dir owner repo branch days <<< "$entry" - - branch_key="${owner}/${repo}/${branch}" - if branch_exists "$owner" "$repo" "$branch"; then - if [ -z "${SEEN_ECHO_BRANCHES[$branch_key]:-}" ]; then - SEEN_ECHO_BRANCHES[$branch_key]=1 - BRANCHES_EXISTING=$((BRANCHES_EXISTING + 1)) - echo " BRANCH EXISTS: ${branch_key}" - fi - KEEP+=("${dir}|${owner}|${repo}|${branch}|${days}") - else - if [ -z "${SEEN_ECHO_BRANCHES[$branch_key]:-}" ]; then - SEEN_ECHO_BRANCHES[$branch_key]=1 - repo_key="${owner}/${repo}" - if [ "${REPO_STATUS[$repo_key]:-}" = "deleted" ]; then - REPO_DELETED_COUNT=$((REPO_DELETED_COUNT + 1)) - if [ -z "${SEEN_ECHO_REPO_DELETED[$repo_key]:-}" ]; then - SEEN_ECHO_REPO_DELETED[$repo_key]=1 - echo " REPO DELETED: ${repo_key} -> DELETE ALL" - fi - reason="repo deleted" - else - BRANCH_DELETED_COUNT=$((BRANCH_DELETED_COUNT + 1)) - echo " BRANCH DELETED: ${branch_key} -> DELETE" - reason="branch deleted" - fi - fi - REASON_MAP["$dir"]="$reason" - TO_DELETE+=("$dir") - fi -done - -echo "" -echo "=== Phase 3: apply retention rules to remaining reports ===" -PHASE2_DELETED=${#TO_DELETE[@]} -apply_retention "$CONFIG" -PHASE3_DELETED=$(( ${#TO_DELETE[@]} - PHASE2_DELETED )) - -fmt_num() { - local n="$1" out="" - [ -z "$n" ] && { echo "?"; return; } - n="${n##0}" # strip leading zeros - while [ "${#n}" -gt 3 ]; do - out=" ${n: -3}$out" - n="${n:0:${#n}-3}" - done - echo "${n}${out}" -} - -echo "" -echo "=== Summary ===" -echo " Branches:" -echo " existing: $(fmt_num $BRANCHES_EXISTING)" -echo " deleted: $(fmt_num $BRANCH_DELETED_COUNT)" -echo " repo gone: $(fmt_num $REPO_DELETED_COUNT)" -echo " Commits:" -echo " deleted by maxAge: $(fmt_num $MAXAGE_DELETED)" -echo " deleted by keepMin:$(fmt_num $KEEPMIN_DELETED)" - -if [ "${#TO_DELETE[@]}" -eq 0 ]; then - echo "Nothing to delete" - exit 0 -fi - -echo "" -echo "=== Phase 4: full site rebuild ===" -echo "Rebuilding site (${#TO_DELETE[@]} report(s) to delete)..." - -ARCHIVE_FILE=$(mktemp) -SITE_DIR=$(mktemp -d) -NEW_TAR=$(mktemp) -cleanup_phase4() { - rm -f "$ARCHIVE_FILE" "$NEW_TAR" - rm -rf "$SITE_DIR" -} -trap cleanup_phase4 EXIT - -# Try archive.tar first -echo "Downloading archive.tar..." -HTTP_CODE=$(curl_with_host -o "$ARCHIVE_FILE" -w "%{http_code}" -sS "${PAGES_URL}/.git-pages/archive.tar") - -if [ "$HTTP_CODE" = "200" ] && tar -tf "$ARCHIVE_FILE" >/dev/null 2>&1; then - OLD_KB=$(du -sk "$ARCHIVE_FILE" 2>/dev/null | awk '{print $1}') - echo "Extracting archive (${OLD_KB}kB)..." - tar -xf "$ARCHIVE_FILE" -C "$SITE_DIR" - - declare -A GROUP_SEEN - declare -A GROUP_LINES - for del in "${TO_DELETE[@]}"; do - if [ ! -d "$SITE_DIR/$del" ]; then - continue - fi - - commit_dir=$(dirname "$del") - branch="${COMMIT_BRANCH_MAP[$commit_dir]:-?}" - reason="${REASON_MAP[$del]:-?}" - repo_path="${del%%/reports/*}" - commit_hash="${commit_dir##*/}" - key="${repo_path}/${branch} | Reason: ${reason}" - seen_key="${key}|${commit_hash}" - - if [ -z "${GROUP_SEEN[$seen_key]:-}" ]; then - GROUP_SEEN[$seen_key]=1 - GROUP_LINES["$key"]="${GROUP_LINES[$key]:-} $commit_hash" - fi - - rm -rf "$SITE_DIR/$del" - done - for key in "${!GROUP_LINES[@]}"; do - echo " Removing: ${key}" - for hash in ${GROUP_LINES[$key]}; do - echo " commit: ${hash}" - done - done -else - echo "archive.tar failed (HTTP ${HTTP_CODE}) - falling back to manifest-based rebuild" - - ALL_PATHS=$(echo "$MANIFEST" | jq -r '.contents | keys[]' 2>/dev/null || true) - - if [ -z "$ALL_PATHS" ]; then - echo "ERROR: no files in manifest - cannot rebuild" >&2 - exit 1 - fi - - EXCLUDE_GREP="" - for dir in "${TO_DELETE[@]}"; do - EXCLUDE_GREP="${EXCLUDE_GREP}${EXCLUDE_GREP:+|}^${dir}/" - done - - if [ -n "$EXCLUDE_GREP" ]; then - KEEP_PATHS=$(echo "$ALL_PATHS" | grep -v -E "$EXCLUDE_GREP" || true) - else - KEEP_PATHS="$ALL_PATHS" - fi - - if [ -z "$KEEP_PATHS" ]; then - echo "No files to keep - site will be empty" - mkdir -p "$SITE_DIR/__placeholder__" - echo "placeholder" > "$SITE_DIR/__placeholder__/index.html" - else - FILE_COUNT=$(echo "$KEEP_PATHS" | wc -l | tr -d ' ') - echo "Downloading ${FILE_COUNT} file(s)..." - while IFS= read -r path; do - [ -z "$path" ] && continue - dir=$(dirname "$SITE_DIR/$path") - mkdir -p "$dir" - curl_with_host -o "$SITE_DIR/$path" -sS "${PAGES_URL}/${path}" || { - echo " WARN: failed to download ${path}" - } - done <<< "$KEEP_PATHS" - fi -fi - -if [ -z "$(ls -A "$SITE_DIR" 2>/dev/null)" ]; then - echo "Site is empty - creating placeholder" - mkdir -p "$SITE_DIR/__placeholder__" - echo "placeholder" > "$SITE_DIR/__placeholder__/index.html" -fi - -tar -cf "$NEW_TAR" -C "$SITE_DIR" . -NEW_KB=$(du -sk "$NEW_TAR" 2>/dev/null | awk '{print $1}') - -echo "PUT: replacing site contents..." -HTTP_CODE=$(curl_with_host -X PUT "${PAGES_URL}/" \ - -H "Content-Type: application/x-tar" \ - --data-binary @"${NEW_TAR}" \ - -w "%{http_code}" \ - -o /dev/null) - -echo "HTTP ${HTTP_CODE}" -if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "204" ]; then - echo "Site rebuild completed." - if [ -n "${OLD_KB:-}" ]; then - echo " archive size: $(fmt_num $OLD_KB)kB → $(fmt_num $NEW_KB)kB" - fi -else - echo "ERROR: PUT HTTP ${HTTP_CODE}" >&2 - exit 1 -fi diff --git a/git-pages/files/retention-lib.sh b/git-pages/files/retention-lib.sh deleted file mode 100644 index 78b5667..0000000 --- a/git-pages/files/retention-lib.sh +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env bash -# Shared functions for retention-cleanup.sh -# Can be sourced by tests for unit testing - -age_days() { - local published="$1" epoch_pub now - epoch_pub=$(date -d "$published" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$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%%/*}" -} - -read_rule() { - local config="$1" branch="$2" key="$3" default="$4" - v=$(jq -r --arg b "$branch" --arg k "$key" '.branches[$b][$k] // empty' "$config") - [ -n "$v" ] && echo "$v" || echo "$default" -} - -# --------------------------------------------------------------------------- -# Gitea branch/repo checking via git ls-remote -# Uses global: GITEA_API_URL, GITEA_TOKEN -# Sets global: REPO_BRANCHES_CACHE, REPO_STATUS -# --------------------------------------------------------------------------- - -# Fetch all branches for a repo (one git ls-remote call per repo). -# Sets REPO_STATUS[owner/repo]. -# Echos branch list on success. -# Returns: 0=ok, 1=deleted, 2=cert_error, 3=error (fail-safe keep) -repo_branches() { - local owner="$1" repo="$2" key="${owner}/${repo}" - local attempt output - - [ -z "$GITEA_API_URL" ] && return 0 - [ -z "$GITEA_TOKEN" ] && return 0 - - # Check cached status first (avoids re-running git on every report) - case "${REPO_STATUS[$key]:-}" in - deleted) return 1 ;; - cert_error) return 2 ;; - error) echo "${REPO_BRANCHES_CACHE[$key]:-}"; return 3 ;; - esac - - # Cache hit (success with branch list) - [ -n "${REPO_BRANCHES_CACHE[$key]:-}" ] && { echo "${REPO_BRANCHES_CACHE[$key]}"; return 0; } - - local git_host - git_host=$(echo "$GITEA_API_URL" | sed -E 's|^https?://||' | sed 's|/.*$||') - local git_url="https://token:${GITEA_TOKEN}@${git_host}/${owner}/${repo}.git" - - for attempt in 1 2 3; do - output=$(git ls-remote --heads "$git_url" 2>&1) && { - local branches - branches=$(echo "$output" | sed -n 's|.*refs/heads/||p') - REPO_BRANCHES_CACHE[$key]="$branches" - REPO_STATUS[$key]="ok" - echo "$branches" - return 0 - } - - # Repo deleted → no retry - if echo "$output" | grep -qiE "fatal:.*(not found|repository.*not|could not read)"; then - REPO_BRANCHES_CACHE[$key]="__REPO_DELETED__" - REPO_STATUS[$key]="deleted" - echo " REPO DELETED: ${owner}/${repo}" >&2 - return 1 - fi - - [ "$attempt" -lt 3 ] && sleep 10 - done - - # Certificate verification failure → configuration error, stop - if echo "$output" | grep -qi "server certificate verification failed"; then - REPO_STATUS[$key]="cert_error" - echo "[ERROR] git-pages.retention: certificate verification failed for ${owner}/${repo}" >&2 - echo "[ERROR] git-pages.retention: check CA certificates or set GIT_SSL_NO_VERIFY=1" >&2 - echo "[ERROR] git-pages.retention: git output:" >&2 - echo "$output" >&2 - return 2 - fi - - # Other network errors → fail-safe keep, continue - REPO_BRANCHES_CACHE[$key]="__REPO_ERROR__" - REPO_STATUS[$key]="error" - echo "[WARN] git-pages.retention: cannot reach Gitea for ${owner}/${repo} — keeping all reports" >&2 - echo "[WARN] git-pages.retention: git output:" >&2 - echo "$output" >&2 - return 3 -} - -# Check if a specific branch exists in a repo. -# Returns 0 (exists), 1 (not found/deleted). -# Returns 2 (cert error), 3 (network error). -branch_exists() { - local owner="$1" repo="$2" branch="$3" - local branches rc - - [ -z "$GITEA_API_URL" ] && return 0 - [ -z "$GITEA_TOKEN" ] && return 0 - - branches=$(repo_branches "$owner" "$repo") - rc=$? - - # Return codes from repo_branches propagate through $() subshell: - # 0=ok, 1=deleted, 2=cert_error, 3=error - case $rc in - 2) echo "[FATAL] git-pages.retention: cannot reach Gitea (${owner}/${repo}) — check configuration" >&2 - exit 1 ;; - 3) return 0 ;; # network error → fail-safe keep - 1) return 1 ;; # repo/branch gone - esac - - echo "$branches" | grep -qxF "$branch" -} - -# Phase 3: apply retention rules to KEEP array, populate TO_DELETE -# Reads from global KEEP array -# Populates global TO_DELETE array -# Usage: apply_retention -apply_retention() { - local config="$1" - local default_max_age default_keep_min - local max_age keep_min key count seen_key commit_dir - local entry dir owner repo branch days - - default_max_age=$(jq -r '.branches.default.maxAgeDays // 90' "$config") - default_keep_min=$(jq -r '.branches.default.keepMin // 5' "$config") - - declare -A BRANCH_COUNTS - declare -A SEEN_COMMITS - declare -A DELETED_COMMITS - - if [ "${#KEEP[@]}" -eq 0 ]; then - return - fi - - IFS=$'\n' - for entry in $(printf '%s\n' "${KEEP[@]}" | sort -t'|' -k4,4 -k5,5n); do - IFS='|' read -r dir owner repo branch days <<< "$entry" - - max_age=$(read_rule "$config" "$branch" "maxAgeDays" "$default_max_age") - keep_min=$(read_rule "$config" "$branch" "keepMin" "$default_keep_min") - - # Age check — per-report-type deletion - if [ "$days" -gt "$max_age" ]; then - echo " DELETE: ${dir} (age ${days}d > maxAge ${max_age}d, branch ${branch})" - TO_DELETE+=("$dir") - REASON_MAP["$dir"]="maxAgeDays exceed" - MAXAGE_DELETED=$((MAXAGE_DELETED + 1)) - continue - fi - - # keepMin — per-commit counting - commit_dir=$(dirname "$dir") - key="$branch" - seen_key="${key}|${commit_dir}" - - if [ -z "${SEEN_COMMITS[$seen_key]:-}" ]; then - SEEN_COMMITS["$seen_key"]=1 - count="${BRANCH_COUNTS[$key]:-0}" - count=$((count + 1)) - BRANCH_COUNTS["$key"]=$count - else - count="${BRANCH_COUNTS[$key]:-0}" - fi - - if [ "$count" -gt "$keep_min" ]; then - if [ -z "${DELETED_COMMITS[$commit_dir]:-}" ]; then - DELETED_COMMITS[$commit_dir]=1 - echo " DELETE: ${commit_dir} (kept ${keep_min}/${count} commits, exceeds keepMin, branch ${branch})" - TO_DELETE+=("$commit_dir") - REASON_MAP["$commit_dir"]="keepMin exceed" - KEEPMIN_DELETED=$((KEEPMIN_DELETED + 1)) - fi - fi - done - unset IFS -} diff --git a/git-pages/files/retention-run.sh b/git-pages/files/retention-run.sh deleted file mode 100644 index 8fb4636..0000000 --- a/git-pages/files/retention-run.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/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 deleted file mode 100644 index 61f07ec..0000000 --- a/git-pages/templates/NOTES.txt +++ /dev/null @@ -1,16 +0,0 @@ -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/configmap.yaml b/git-pages/templates/configmap.yaml deleted file mode 100644 index a13fe92..0000000 --- a/git-pages/templates/configmap.yaml +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 47278f2..0000000 --- a/git-pages/templates/deployment.yaml +++ /dev/null @@ -1,127 +0,0 @@ -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 git ca-certificates >/dev/null - echo "Retention sidecar: ready" - # Sleep until 01:00 so retention runs at night - now_epoch=$(date +%s) - target_epoch=$(date -d "today 01:00:00" +%s) - [ "$target_epoch" -le "$now_epoch" ] && target_epoch=$((target_epoch + 86400)) - sleep_sec=$((target_epoch - now_epoch)) - echo "Retention sidecar: next run in $((sleep_sec / 3600))h (at 01:00)" - sleep $sleep_sec - 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/init-job.yaml b/git-pages/templates/init-job.yaml deleted file mode 100644 index 9cc1605..0000000 --- a/git-pages/templates/init-job.yaml +++ /dev/null @@ -1,58 +0,0 @@ -{{- 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 - "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: checking if site already exists..." - MANIFEST=$(curl -sf \ - -H "Host: {{ .Values.ingress.host }}" \ - "http://git-pages:3000/.git-pages/manifest.json" 2>/dev/null || echo "") - - if echo "$MANIFEST" | grep -q '"contents"'; then - echo "Init: site already initialized, skipping" - exit 0 - fi - - 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 deleted file mode 100644 index 8cc55e5..0000000 --- a/git-pages/templates/middleware.yaml +++ /dev/null @@ -1,22 +0,0 @@ -{{- 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/service.yaml b/git-pages/templates/service.yaml deleted file mode 100644 index 91d0da1..0000000 --- a/git-pages/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -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/tests/retention.bats b/git-pages/tests/retention.bats deleted file mode 100644 index 490cd9f..0000000 --- a/git-pages/tests/retention.bats +++ /dev/null @@ -1,623 +0,0 @@ -#!/usr/bin/env bats - -setup() { - source "$(dirname "$BATS_TEST_DIRNAME")/files/retention-lib.sh" - declare -gA REPO_BRANCHES_CACHE - declare -gA REPO_STATUS - declare -gA REASON_MAP - MAXAGE_DELETED=0 - KEEPMIN_DELETED=0 - CONFIG=$(mktemp) -} - -teardown() { - rm -f "$CONFIG" -} - -write_config() { - cat > "$CONFIG" -} - -# --------------------------------------------------------------------------- -# read_rule -# --------------------------------------------------------------------------- - -@test "read_rule returns default when branch has no override" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":90,"keepMin":5}}} -EOF - result=$(read_rule "$CONFIG" "nonexistent" "maxAgeDays" 90) - [ "$result" = "90" ] -} - -@test "read_rule returns branch-specific value" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":90,"keepMin":5},"main":{"maxAgeDays":365,"keepMin":20}}} -EOF - result=$(read_rule "$CONFIG" "main" "keepMin" 5) - [ "$result" = "20" ] -} - -@test "read_rule returns default for undefined key even if branch exists" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":90,"keepMin":5},"main":{"maxAgeDays":365}}} -EOF - result=$(read_rule "$CONFIG" "main" "keepMin" 5) - [ "$result" = "5" ] -} - -# --------------------------------------------------------------------------- -# parse_path -# --------------------------------------------------------------------------- - -@test "parse_path extracts owner and repo" { - parse_path "my-owner/my-repo/reports/abc123/go-test-unit" - [ "$OWNER" = "my-owner" ] - [ "$REPO" = "my-repo" ] -} - -@test "parse_path handles owner with hyphens" { - parse_path "niko/agent-platform/reports/abc1234/go-test-bdd" - [ "$OWNER" = "niko" ] - [ "$REPO" = "agent-platform" ] -} - -# --------------------------------------------------------------------------- -# apply_retention — keepMin per commit -# --------------------------------------------------------------------------- - -@test "keepMin: 6 commits × 4 types, keepMin=10 → all kept (6 commits < 10)" { - # With old per-file counting, 24 files > 10 keepMin would delete 14. - # With per-commit counting, 6 commits < 10 keepMin keeps everything. - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":365,"keepMin":10}}} -EOF - - KEEP=() - local -a commits=(c1 c2 c3 c4 c5 c6) - local -a ages=(100 80 60 40 20 5) - local -a types=(go-test-bdd go-test-unit helm-lint helm-kubeconform) - for i in "${!commits[@]}"; do - for t in "${types[@]}"; do - KEEP+=("niko/agent-platform/reports/${commits[$i]}/$t|niko|agent-platform|main|${ages[$i]}") - done - done - - TO_DELETE=() - apply_retention "$CONFIG" - - [ "${#TO_DELETE[@]}" -eq 0 ] -} - -@test "keepMin: 8 commits × 1 type, keepMin=5 → deletes 3 oldest" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":365,"keepMin":5}}} -EOF - - KEEP=() - local -a ages=(80 70 60 50 40 30 20 10) - for i in "${!ages[@]}"; do - KEEP+=("niko/r/reports/c$((i+1))/test|niko|r|main|${ages[$i]}") - done - - TO_DELETE=() - apply_retention "$CONFIG" - - # 5 newest (c8-c4) kept, 3 oldest (c3,c2,c1) deleted - [ "${#TO_DELETE[@]}" -eq 3 ] - [[ "${TO_DELETE[0]}" == "niko/r/reports/c3" ]] - [[ "${TO_DELETE[1]}" == "niko/r/reports/c2" ]] - [[ "${TO_DELETE[2]}" == "niko/r/reports/c1" ]] -} - -@test "keepMin: 12 commits × 1 type, keepMin=5 → keeps 5 newest, deletes 7 oldest" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":90,"keepMin":5}}} -EOF - - KEEP=() - for i in $(seq 1 12); do - KEEP+=("niko/r/reports/c${i}/test|niko|r|feature/foo|$(( 13 - i ))") - done - - TO_DELETE=() - apply_retention "$CONFIG" - - # 7 oldest commits deleted (12 - 5 = 7) - [ "${#TO_DELETE[@]}" -eq 7 ] - # Oldest 7 should be c1..c7 (highest days = oldest = processed last after sort) - # Sort is ascending by days, so processed as c12(1d), c11(2d), ..., c1(12d) - # keepMin=5: c12-c8 kept, c7-c1 deleted - [[ "${TO_DELETE[0]}" == "niko/r/reports/c7" ]] - [[ "${TO_DELETE[6]}" == "niko/r/reports/c1" ]] -} - -@test "keepMin: 2 commits × 3 types, keepMin=5 → all kept (2 < 5)" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":90,"keepMin":5}}} -EOF - - KEEP=() - KEEP+=("niko/r/reports/c1/test-a|niko|r|main|30") - KEEP+=("niko/r/reports/c1/test-b|niko|r|main|30") - KEEP+=("niko/r/reports/c1/test-c|niko|r|main|30") - KEEP+=("niko/r/reports/c2/test-a|niko|r|main|10") - KEEP+=("niko/r/reports/c2/test-b|niko|r|main|10") - KEEP+=("niko/r/reports/c2/test-c|niko|r|main|10") - - TO_DELETE=() - apply_retention "$CONFIG" - - [ "${#TO_DELETE[@]}" -eq 0 ] -} - -@test "keepMin: 6 commits × 2 types, keepMin=3 → keeps 3 newest, deletes 3 oldest" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":365,"keepMin":3}}} -EOF - - KEEP=() - local -a commits=(c1 c2 c3 c4 c5 c6) - local -a ages=(60 50 40 30 20 10) - local -a types=(jest pytest) - for i in "${!commits[@]}"; do - for t in "${types[@]}"; do - KEEP+=("niko/r/reports/${commits[$i]}/$t|niko|r|feature/x|${ages[$i]}") - done - done - - TO_DELETE=() - apply_retention "$CONFIG" - - # Sort by days ascending: c6(10d), c5(20d), c4(30d), c3(40d), c2(50d), c1(60d) - # keepMin=3: c6,c5,c4 kept; c3,c2,c1 deleted - [ "${#TO_DELETE[@]}" -eq 3 ] - [[ "${TO_DELETE[0]}" == "niko/r/reports/c3" ]] - [[ "${TO_DELETE[1]}" == "niko/r/reports/c2" ]] - [[ "${TO_DELETE[2]}" == "niko/r/reports/c1" ]] -} - -# --------------------------------------------------------------------------- -# apply_retention — maxAge -# --------------------------------------------------------------------------- - -@test "maxAge: report exceeding maxAge is deleted" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":90,"keepMin":5}}} -EOF - - KEEP=( - "niko/r/reports/c1/test|niko|r|main|100" - "niko/r/reports/c2/test|niko|r|main|50" - ) - - TO_DELETE=() - apply_retention "$CONFIG" - - # c1 (100d) > 90, deleted; c2 (50d) < 90, kept - [ "${#TO_DELETE[@]}" -eq 1 ] - [[ "${TO_DELETE[0]}" == "niko/r/reports/c1/test" ]] -} - -@test "maxAge deletes report-level dir, not commit-level" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":30,"keepMin":5}}} -EOF - - KEEP=( - "niko/r/reports/c1/test-a|niko|r|main|100" - "niko/r/reports/c1/test-b|niko|r|main|20" - "niko/r/reports/c2/test-a|niko|r|main|10" - ) - - TO_DELETE=() - apply_retention "$CONFIG" - - # Only test-a for c1 is old; test-b for c1 is young, c2 is young - [ "${#TO_DELETE[@]}" -eq 1 ] - [[ "${TO_DELETE[0]}" == "niko/r/reports/c1/test-a" ]] -} - -# --------------------------------------------------------------------------- -# apply_retention — maxAge + keepMin interaction -# --------------------------------------------------------------------------- - -@test "maxAge takes precedence over keepMin — aged report deleted, not counted in keepMin" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":30,"keepMin":2}}} -EOF - - # 3 commits, 1 type each. c1 is old (100d), c2 and c3 are young. - # With keepMin=2: c1 should be deleted by maxAge, c2 and c3 kept. - # Without the continue after maxAge check, c1 would consume a keepMin slot. - KEEP=( - "niko/r/reports/c1/test|niko|r|main|100" - "niko/r/reports/c2/test|niko|r|main|10" - "niko/r/reports/c3/test|niko|r|main|5" - ) - - TO_DELETE=() - apply_retention "$CONFIG" - - # c1 deleted by maxAge, c2 and c3 within keepMin=2 - [ "${#TO_DELETE[@]}" -eq 1 ] -} - -# --------------------------------------------------------------------------- -# apply_retention — sorting (newest first) -# --------------------------------------------------------------------------- - -@test "sort order: newest commits processed first within same branch" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":365,"keepMin":2}}} -EOF - - KEEP=( - "niko/r/reports/c1/test|niko|r|main|100" - "niko/r/reports/c2/test|niko|r|main|50" - "niko/r/reports/c3/test|niko|r|main|10" - ) - - TO_DELETE=() - apply_retention "$CONFIG" - - # Sort by days ascending: c3(10d) 1st, c2(50d) 2nd, c1(100d) 3rd - # keepMin=2: c3 and c2 kept, c1 deleted - [ "${#TO_DELETE[@]}" -eq 1 ] - [[ "${TO_DELETE[0]}" == "niko/r/reports/c1" ]] -} - -@test "sort order: branches sorted alphabetically" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":365,"keepMin":1}}} -EOF - - KEEP=( - "niko/r/reports/c1/test|niko|r|z-branch|50" - "niko/r/reports/c2/test|niko|r|a-branch|60" - "niko/r/reports/c3/test|niko|r|m-branch|10" - ) - - TO_DELETE=() - apply_retention "$CONFIG" - - # Alphabetical: a-branch, m-branch, z-branch - # Each has 1 commit, keepMin=1 → nothing deleted - [ "${#TO_DELETE[@]}" -eq 0 ] -} - -@test "multi-branch: each branch has own keepMin counter" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":90,"keepMin":2}}} -EOF - - KEEP=( - "niko/r/reports/c1/test|niko|r|branch-a|30" - "niko/r/reports/c2/test|niko|r|branch-a|20" - "niko/r/reports/c3/test|niko|r|branch-a|10" - "niko/r/reports/c4/test|niko|r|branch-b|60" - "niko/r/reports/c5/test|niko|r|branch-b|50" - "niko/r/reports/c6/test|niko|r|branch-b|40" - "niko/r/reports/c7/test|niko|r|branch-b|30" - ) - - TO_DELETE=() - apply_retention "$CONFIG" - - # branch-a: 3 reports → keep 2 newest (c2,c3), delete 1 oldest (c1) - # branch-b: 4 reports → keep 2 newest (c6,c7), delete 2 oldest (c4,c5) - # Actually: Sort is by branch, then by days ascending - # branch-a processed first: c3(10d) 1st, c2(20d) 2nd (keep), c1(30d) 3rd (delete) - # branch-b processed next: c7(30d) 1st, c6(40d) 2nd (keep), c5(50d) 3rd (delete), c4(60d) 4th (delete) - [ "${#TO_DELETE[@]}" -eq 3 ] - [[ "${TO_DELETE[0]}" == "niko/r/reports/c1" ]] - [[ "${TO_DELETE[1]}" == "niko/r/reports/c5" ]] - [[ "${TO_DELETE[2]}" == "niko/r/reports/c4" ]] -} - -# --------------------------------------------------------------------------- -# apply_retention — empty / edge cases -# --------------------------------------------------------------------------- - -@test "empty KEEP array → nothing deleted" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":90,"keepMin":5}}} -EOF - - KEEP=() - TO_DELETE=() - apply_retention "$CONFIG" - [ "${#TO_DELETE[@]}" -eq 0 ] -} - -@test "TO_DELETE preserves Phase 2 entries after apply_retention (no new deletions)" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":365,"keepMin":10}}} -EOF - - KEEP=( - "niko/r/reports/c1/test|niko|r|main|10" - "niko/r/reports/c2/test|niko|r|main|5" - ) - TO_DELETE=( - "niko/r/reports/abc/branch-gone" - "niko/r/reports/def/repo-gone" - ) - - apply_retention "$CONFIG" - - [ "${#TO_DELETE[@]}" -eq 2 ] - [[ "${TO_DELETE[0]}" == "niko/r/reports/abc/branch-gone" ]] - [[ "${TO_DELETE[1]}" == "niko/r/reports/def/repo-gone" ]] -} - -@test "TO_DELETE preserves Phase 2 entries AND adds retention deletions" { - write_config <<'EOF' -{"branches":{"default":{"maxAgeDays":365,"keepMin":3}}} -EOF - - KEEP=( - "niko/r/reports/c1/test|niko|r|main|40" - "niko/r/reports/c2/test|niko|r|main|30" - "niko/r/reports/c3/test|niko|r|main|20" - "niko/r/reports/c4/test|niko|r|main|10" - ) - TO_DELETE=( - "niko/r/reports/abc/branch-gone" - ) - - apply_retention "$CONFIG" - - # 1 pre-existing + 1 commit deleted (c1, oldest of 4, keepMin=3) - [ "${#TO_DELETE[@]}" -eq 2 ] - [[ "${TO_DELETE[0]}" == "niko/r/reports/abc/branch-gone" ]] -} - -# --------------------------------------------------------------------------- -# branch_exists — mocking REPO_STATUS / REPO_BRANCHES_CACHE -# --------------------------------------------------------------------------- - -@test "branch_exists: branch in list → return 0" { - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test-token" - - REPO_BRANCHES_CACHE["owner/repo"]=$'main\nfeature/x' - REPO_STATUS["owner/repo"]="ok" - - run branch_exists "owner" "repo" "main" - [ "$status" -eq 0 ] -} - -@test "branch_exists: branch not in list → return 1" { - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test-token" - - REPO_BRANCHES_CACHE["owner/repo"]=$'main\nfeature/x' - REPO_STATUS["owner/repo"]="ok" - - run branch_exists "owner" "repo" "nonexistent" - [ "$status" -eq 1 ] -} - -@test "branch_exists: cert error → exit 1 with [FATAL]" { - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test" - - REPO_STATUS["owner/repo"]="cert_error" - - run branch_exists "owner" "repo" "any-branch" - [ "$status" -eq 1 ] - [[ "$output" == *"[FATAL]"* ]] -} - -@test "branch_exists: repo deleted → return 1" { - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test-token" - - REPO_BRANCHES_CACHE["owner/repo"]="__REPO_DELETED__" - REPO_STATUS["owner/repo"]="deleted" - - run branch_exists "owner" "repo" "any-branch" - [ "$status" -eq 1 ] -} - -@test "branch_exists: network error → return 0 (fail-safe keep)" { - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test-token" - - REPO_BRANCHES_CACHE["owner/repo"]="__REPO_ERROR__" - REPO_STATUS["owner/repo"]="error" - - run branch_exists "owner" "repo" "any-branch" - [ "$status" -eq 0 ] -} - -@test "branch_exists: empty GITEA_API_URL → return 0 (skip)" { - GITEA_API_URL="" - GITEA_TOKEN="test-token" - - run branch_exists "owner" "repo" "any-branch" - [ "$status" -eq 0 ] -} - -@test "branch_exists: empty GITEA_TOKEN → return 0 (skip)" { - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="" - - run branch_exists "owner" "repo" "any-branch" - [ "$status" -eq 0 ] -} - -# --------------------------------------------------------------------------- -# repo_branches — git ls-remote error detection patterns -# --------------------------------------------------------------------------- - -@test "error detection: 'command not found' does NOT trigger repo deleted" { - # This must NOT match — "bash: git: command not found" is NOT a repo deletion - local msg="bash: git: command not found" - run grep -qiE "fatal:.*(not found|repository.*not|could not read)" <<< "$msg" - [ "$status" -eq 1 ] -} - -@test "error detection: 'fatal: repo not found' triggers repo deleted" { - # This MUST match — genuine git error for deleted/missing repo - local msg="fatal: repository 'https://gitea.app/owner/repo.git' not found" - run grep -qiE "fatal:.*(not found|repository.*not|could not read)" <<< "$msg" - [ "$status" -eq 0 ] -} - -@test "error detection: 'could not read from remote' triggers repo deleted" { - local msg="fatal: could not read from remote repository" - run grep -qiE "fatal:.*(not found|repository.*not|could not read)" <<< "$msg" - [ "$status" -eq 0 ] -} - -# --------------------------------------------------------------------------- -# git ls-remote integration (real git, temp repo) -# --------------------------------------------------------------------------- - -@test "git ls-remote parsing: lists branches correctly" { - local tmpdir=$(mktemp -d) - - git -C "$tmpdir" init -b main source >/dev/null 2>&1 - git -C "$tmpdir/source" config user.email "test@test" - git -C "$tmpdir/source" config user.name "test" - git -C "$tmpdir/source" commit --allow-empty -m "init" >/dev/null 2>&1 - git -C "$tmpdir/source" branch feature/x >/dev/null 2>&1 - git clone --bare "$tmpdir/source" "$tmpdir/repo.git" >/dev/null 2>&1 - - local url="file://$tmpdir/repo.git" - local output - output=$(git ls-remote --heads "$url" 2>&1) - local branches - branches=$(echo "$output" | sed -n 's|.*refs/heads/||p') - - echo "$branches" | grep -qxF "main" - [ "$?" -eq 0 ] - - echo "$branches" | grep -qxF "feature/x" - [ "$?" -eq 0 ] - - ! echo "$branches" | grep -qxF "nonexistent" - - rm -rf "$tmpdir" -} - -# --------------------------------------------------------------------------- -# repo_branches — retry + error output (using git mock) -# --------------------------------------------------------------------------- - -@test "repo_branches: success returns branches immediately" { - local mockdir=$(mktemp -d) - cat > "$mockdir/git" << 'SCRIPT' -#!/usr/bin/env bash -echo "abc123 refs/heads/main" -echo "def456 refs/heads/feature/x" -SCRIPT - chmod +x "$mockdir/git" - local save_PATH="$PATH" - export PATH="$mockdir:$PATH" - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test" - REPO_BRANCHES_CACHE=() - REPO_STATUS=() - - run repo_branches "owner" "repo" - - [ "$status" -eq 0 ] - [[ "$output" == *"main"* ]] - [[ "$output" == *"feature/x"* ]] - - export PATH="$save_PATH" - rm -rf "$mockdir" -} - -@test "repo_branches: retries 3 times on transient error" { - local mockdir=$(mktemp -d) - cat > "$mockdir/git" << 'SCRIPT' -#!/usr/bin/env bash -echo "call" >> "$MOCKDIR/count" -echo "fatal: unable to access 'https://...'" >&2 -exit 1 -SCRIPT - chmod +x "$mockdir/git" - # Inject mockdir path into mock script via env var - sed -i '' "s|\$MOCKDIR|$mockdir|g" "$mockdir/git" - local save_PATH="$PATH" - export PATH="$mockdir:$PATH" - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test" - REPO_BRANCHES_CACHE=() - REPO_STATUS=() - - local start=$SECONDS - run repo_branches "owner" "repo" - - [ "$status" -eq 3 ] - [[ "$output" == *"[WARN] git-pages.retention"* ]] - [[ "$output" == *"keeping all reports"* ]] - [[ "$output" == *"git output:"* ]] - [[ "$output" == *"unable to access"* ]] - [ $(cat "$mockdir/count" | wc -l) -eq 3 ] - [ $(( SECONDS - start )) -ge 18 ] - - export PATH="$save_PATH" - rm -rf "$mockdir" -} - -@test "repo_branches: certificate error → [ERROR] + return 1" { - local mockdir=$(mktemp -d) - cat > "$mockdir/git" << 'SCRIPT' -#!/usr/bin/env bash -echo "call" >> "$MOCKDIR/count" -echo "fatal: unable to access 'https://gitea.app/owner/repo.git/': server certificate verification failed. CAfile: none CRLfile: none" >&2 -exit 1 -SCRIPT - chmod +x "$mockdir/git" - sed -i '' "s|\$MOCKDIR|$mockdir|g" "$mockdir/git" - local save_PATH="$PATH" - export PATH="$mockdir:$PATH" - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test" - REPO_BRANCHES_CACHE=() - REPO_STATUS=() - - run repo_branches "owner" "repo" - - [ "$status" -eq 2 ] - [[ "$output" == *"[ERROR]"* ]] - [[ "$output" == *"certificate verification"* ]] - [[ "$output" == *"git output:"* ]] - [[ "$output" == *"unable to access"* ]] - - export PATH="$save_PATH" - rm -rf "$mockdir" -} - -@test "repo_branches: repo not found returns immediately (no retry)" { - local mockdir=$(mktemp -d) - cat > "$mockdir/git" << 'SCRIPT' -#!/usr/bin/env bash -echo "call" >> "$MOCKDIR/count" -echo "fatal: repository 'https://gitea.app/owner/repo.git' not found" >&2 -exit 1 -SCRIPT - chmod +x "$mockdir/git" - sed -i '' "s|\$MOCKDIR|$mockdir|g" "$mockdir/git" - local save_PATH="$PATH" - export PATH="$mockdir:$PATH" - GITEA_API_URL="https://gitea.example.com" - GITEA_TOKEN="test" - REPO_BRANCHES_CACHE=() - REPO_STATUS=() - - run repo_branches "owner" "repo" - - [ "$status" -eq 1 ] - [[ "$output" == *"REPO DELETED"* ]] - [[ "$output" != *"[WARN]"* ]] - [ $(cat "$mockdir/count" | wc -l) -eq 1 ] - - export PATH="$save_PATH" - rm -rf "$mockdir" -} diff --git a/gitea-reports/Chart.yaml b/gitea-reports/Chart.yaml new file mode 100644 index 0000000..d544125 --- /dev/null +++ b/gitea-reports/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: gitea-reports +description: Nginx + upload-sidecar for CI HTML reports (static serving, Traefik BasicAuth publish) +type: application +version: 0.2.0 diff --git a/git-pages/README.md b/gitea-reports/README.md similarity index 58% rename from git-pages/README.md rename to gitea-reports/README.md index 131fbcf..763c96e 100644 --- a/git-pages/README.md +++ b/gitea-reports/README.md @@ -1,4 +1,4 @@ -# git-pages +# gitea-reports Jaettu **Gitea CI -raporttien** tallennus- ja lukupaikka: HTML-raportit (esim. Cucumber) commit-kohtaisiin polkuihin, selaimella avattavina linkkeinä Gitean commitin CI-job @@ -21,39 +21,39 @@ Luo secretit ennen Helm-asennusta: [docs/secrets.md](docs/secrets.md) ```bash # Muokkaa oma values-tiedosto (tai käytä dev-values.yaml) -cp git-pages/dev-values.yaml my-values.yaml +cp gitea-reports/dev-values.yaml my-values.yaml # Muuta: ingress.host, certificate.issuerRef.name, jne. ``` ### 3. Helm-asennus ```bash -NS=git-pages -VALUES=my-values.yaml +NS=gitea-reports +VALUES=gitea-reports/dev-values.yaml -helm upgrade --install git-pages ./git-pages \ +helm upgrade --install gitea-reports ./gitea-reports \ -n "$NS" --create-namespace \ -f "$VALUES" + +helm template gitea-reports ./gitea-reports -n "$NS" -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. +Nginx ja upload-sidecar käynnistyvät suoraan ilman init-vaihetta. +PV on tyhjä ennen ensimmäistä CI-julkaisua. --- ## Vie publish-token Gitea Actions-secretiin (per repo) -⚠️ **Tehtävä jokaiselle repoille**, joka julkaisee raportteja git-pagesiin. +⚠️ **Tehtävä jokaiselle repoille**, joka julkaisee raportteja gitea-reportsiin. ```bash -NS=git-pages +NS=gitea-reports 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) +TOKEN=$(kubectl get secret gitea-reports-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d) # 2. Kopioi leikepöydälle echo -n "$TOKEN" | pbcopy # macOS @@ -64,7 +64,7 @@ open "https://gitea.app.keskikuja.site/${REPO_OWNER}/${REPO_NAME}/settings/actio # 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 +**Gitea UI:ssa:** New Secret → Name: `REPORTS_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). @@ -113,14 +113,14 @@ Esimerkki dev-ympäristöstä: [dev-values.yaml](dev-values.yaml). Repojuuresta (sama `NS` kuin [docs/secrets.md](docs/secrets.md)): ```bash -NS=git-pages -VALUES=git-pages/dev-values.yaml +NS=gitea-reports +VALUES=gitea-reports/dev-values.yaml -helm upgrade --install git-pages ./git-pages \ +helm upgrade --install gitea-reports ./gitea-reports \ -n "$NS" --create-namespace \ -f "$VALUES" -helm template git-pages ./git-pages -f "$VALUES" +helm template gitea-reports ./gitea-reports -f "$VALUES" ``` --- @@ -131,22 +131,23 @@ 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 +curl -X PUT \ + -u publish:$REPORTS_PUBLISH_TOKEN \ + -H "Content-Type: application/tar+gz" \ + --data-binary @report.tar.gz \ + https://ci-reports.helm-dev.keskikuja.site/{owner}/{repo}/{branch}/{sha8}/{suite}/ ``` **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ä | +| Gitea Actions secret | `REPORTS_PUBLISH_TOKEN` (sama kuin K8s `gitea-reports-publish-token` `token`-avain) | +| Scriptissä | `bash scripts/publish-gitea-reports.sh ` | **K8s secretit (Traefik-yhteensopivuus):** -- `git-pages-publish-auth` = htpasswd (Traefik BasicAuth middleware) -- `git-pages-publish-token` = plaintext (luetaan Giteaan viedessä) +- `gitea-reports-publish-auth` = htpasswd (Traefik BasicAuth middleware) +- `gitea-reports-publish-token` = plaintext (luetaan Giteaan viedessä) Tarkemmat secret-ohjeet: [docs/secrets.md](docs/secrets.md). @@ -158,17 +159,12 @@ Retention-logiikalle on unit-testit, jotka testaa funktiot ja Phase 3 -säännö erikseen ilman ulkoisia riippuvuuksia. ```bash -cd git-pages +cd gitea-reports bats tests/retention.bats ``` Testit käyttävät `/files/retention-lib.sh` -jaettua kirjastoa, jota myös -`retention-cleanup.sh` sourceaa. Uutta testiä kirjoittaessa: - -1. Luo config `write_config`-helperilla -2. Täytä `KEEP`-array testidatalla (muoto: `dir|owner|repo|branch|days`) -3. Kutsu `apply_retention "$CONFIG"` -4. Tarkista `TO_DELETE`-array ja `$output` +`retention-cleanup.sh` sourceaa. **Vaatimukset:** `bats`, `jq`, `date` (GNU date tai BSD date ISO 8601 -tuella). @@ -176,15 +172,8 @@ Testit käyttävät `/files/retention-lib.sh` -jaettua kirjastoa, jota my ## Retention -Ylläpitoscripti, joka poistaa vanhat raportit git-pagesista retention‑sääntöjen mukaan. -Ajetaan sidecar‑ tai cronjob‑tilassa Kubernetesissa. +Ylläpitoscripti, joka poistaa vanhat raportit PV:ltä suoraan retention‑sääntöjen mukaan. +Ajetaan CronJobina Kubernetesissa. -### Air gap -yhteensopimattomuus - -Retention‑kontti asentaa tarvitsemansa työkalut (`curl`, `jq`) ajon aikana -packagemanagerilla (`apt-get` / `apk`). Tämä **ei toimi air gap -ympäristössä**, -jossa konttirekisteriin tai pakettivarastoihin ei ole verkkoyhteyttä. - -**TODO:** Rakenna custom Docker‑image, jossa deps on valmiina: -`FROM alpine:latest && apk add --no-cache curl jq`. -Pushaa omaan rekisteriin ja päivitä `values.yaml`:n `retention.image`. +Retention lukee PV:tä suoraan (find, ls, stat), tarkistaa branchin olemassaolon Giteasta, +ja poistaa vanhat commit-kansiot `rm -rf`-komentona. Ei HTTP API:a retentionia varten. diff --git a/git-pages/dev-values.yaml b/gitea-reports/dev-values.yaml similarity index 66% rename from git-pages/dev-values.yaml rename to gitea-reports/dev-values.yaml index 7e1282a..de69225 100644 --- a/git-pages/dev-values.yaml +++ b/gitea-reports/dev-values.yaml @@ -1,5 +1,5 @@ # Dev instance — overrides values.yaml constants. -# helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml +# helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml ingress: host: ci-reports.helm-dev.keskikuja.site @@ -19,11 +19,11 @@ retention: rules: branches: default: - maxAgeDays: 90 + minAgeDays: 3 keepMin: 5 main: - maxAgeDays: 365 - keepMin: 20 + minAgeDays: 3 + keepMin: 10 master: - maxAgeDays: 365 - keepMin: 20 + minAgeDays: 3 + keepMin: 10 diff --git a/gitea-reports/docs/architecture.md b/gitea-reports/docs/architecture.md new file mode 100644 index 0000000..ae0d9ab --- /dev/null +++ b/gitea-reports/docs/architecture.md @@ -0,0 +1,82 @@ +# Architecture — Nginx + upload-sidecar + +> gitea-reports on korvattu. Uusi arkkitehtuuri: Nginx palvelee staattisia tiedostoja PV:ltä, upload-sidecar (busybox httpd + CGI) ottaa vastaan tar.gz-PUTit. + +## Yleiskuvaus + +Yksi apex-host, monta Gitea-repoa. URL = FS-polku: `/{owner}/{repo}/{branch}/{sha8}/{suite}/`. +Ei Host-header-kikkaa, ei slugia, ei rewritea. + +## Komponentit + +| Komponentti | Rooli | +|-------------|-------| +| **nginx** | Palvelee staattisia tiedostoja /app/data -rootista (:8080) | +| **upload** (Python HTTP server) | Vastaanottaa PUT /path + tar.gz body, purkaa PV:lle (:8081) | +| **PVC** | Raporttisisältö suoraan FS-muodossa | +| **Service** | ClusterIP :8080 (read), :8081 (write) | +| **Traefik IngressRoute** | PATCH/PUT → upload:8081 (BasicAuth), GET/HEAD → nginx:8080 | +| **Traefik Middleware** | `gitea-reports-publish-auth` (BasicAuth), HTTPS-redirect | +| **cert-manager Certificate** | TLS | + +## URL ja sisältö + +``` +https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/main/f4baa286/cucumber/index.html + └── selvä URL ──┘ └─────────── Gitea-yhteensopiva polku ──────────────────────────┘ +``` + +Levyllä: +``` +/app/data/{owner}/{repo}/{branch}/{sha8}/ + .meta + index.html + ... +``` + +## 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["Pod"] + NGINX["nginx\n(kontti)"] + UPLOAD["upload sidecar\n(Python HTTP server)"] + PVC["PVC /app/data"] + end + + PUB -->|"PUT + BasicAuth\ntar.gz"| TRAEFIK + BR -->|"GET/HEAD"| TRAEFIK + TRAEFIK -->|"PUT/PATCH"| UPLOAD + TRAEFIK -->|"GET/HEAD"| NGINX + CM --> TRAEFIK + UPLOAD -->|"extract to PV"| PVC + NGINX --> PVC +``` + +## Julkaisu + +1. Julkaisija paketoi tiedostot tar.gz:ksi +2. `PUT https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/` +3. Traefik tarkistaa BasicAuth → välittää upload-sidecarille +4. CGI purkaa tar.gz:n suoraan PV:lle: `/app/data/{owner}/{repo}/{branch}/{sha8}/{suite}/` + +## Luku + +1. Selain avaa URL:n (GET/HEAD) +2. Traefik välittää nginxille +3. Nginx palauttaa suoraan PV:ltä + +## Retention + +CronJob lukee PV:tä suoraan (find, ls, stat), tarkistaa branchin olemassaolon Giteasta, poistaa vanhat commit-kansiot. diff --git a/git-pages/docs/design-rationale.md b/gitea-reports/docs/design-rationale.md similarity index 88% rename from git-pages/docs/design-rationale.md rename to gitea-reports/docs/design-rationale.md index 669458a..5e7c483 100644 --- a/git-pages/docs/design-rationale.md +++ b/gitea-reports/docs/design-rationale.md @@ -1,8 +1,8 @@ -# Design Rationale — git-pages +# Design Rationale — gitea-reports -> Miksi git-pages on rakennettu näin. Arvot, periaatteet ja reunaehdot. +> Miksi gitea-reports on rakennettu näin. Arvot, periaatteet ja reunaehdot. > -> Tämä dokumentti on **normatiivinen** `git-pages/`-alikansiolle. Se ei kuvaa juuren +> Tämä dokumentti on **normatiivinen** `gitea-reports/`-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). @@ -18,7 +18,7 @@ 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. +gitea-reports ratkaisee tämän ongelman. | Vaihtoehto | Miksi ei riitä | |---|---| @@ -73,7 +73,7 @@ Julkaisija (CI tai muu asiakas) lähettää tar-arkiston PATCH/PUT:lla. Lukija h 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- +`write:repository`), DNS TXT -haaste ja muut gitea-reportsin 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ää @@ -94,13 +94,13 @@ 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. +`gitea-reports`-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`). +**Miksi:** Koska sovellus ei validoi julkaisuoikeuksia, kirjoitusoikeus on eksplisiittisesti eriytetty Traefik Middlewaressä (`gitea-reports-publish-auth`). ### 4. Yksi publish-token, kaksi säilöä @@ -112,7 +112,7 @@ julkaisuoikeuden tähän palveluun. Yksi arvo, kaksi paikkaa — ks. [secrets.md ### 5. Secretit erillisessä hallinnassa -`git-pages-publish-auth` luodaan ennen käyttöönottoa — ei osana sovelluksen konfiguraatiotiedostoja. +`gitea-reports-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). @@ -159,7 +159,7 @@ branchille säännöt tulevat `retention.rules` (`branches.default` + 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. +Secret: `gitea-reports-retention-gitea` — Gitea PAT branch-tarkistukseen. Ks. [secrets.md](secrets.md). --- @@ -176,10 +176,10 @@ Ks. [secrets.md](secrets.md). | Valinta | Miksi | |---------|-------| -| **Codeberg git-pages** `0.9.1` | Natiivi apex index-site + tar-pohjainen PATCH/PUT -julkaisu | +| **Codeberg gitea-reports** `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`) | +| **cert-manager** | TLS automaattisesti (`gitea-reports-tls`) | | **Helm v3** | Toistettava asennus; instanssikohtaiset arvot erillisessä values-tiedostossa | --- diff --git a/git-pages/docs/implementation-notes.md b/gitea-reports/docs/implementation-notes.md similarity index 100% rename from git-pages/docs/implementation-notes.md rename to gitea-reports/docs/implementation-notes.md diff --git a/gitea-reports/docs/secrets.md b/gitea-reports/docs/secrets.md new file mode 100644 index 0000000..85eb5d3 --- /dev/null +++ b/gitea-reports/docs/secrets.md @@ -0,0 +1,267 @@ +# Secrets — gitea-reports + +## 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) | `gitea-reports-publish-auth` (users) | - | +| `report_publish_api_token` (plaintext) | `gitea-reports-publish-token` (token) | Actions Secret: `REPORTS_PUBLISH_TOKEN` | +| `reports_retention_read_token` | `gitea-reports-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="" + +REPORTS_PUBLISH_TOKEN="$(openssl rand -base64 24)" +echo "Publish-token generoitu. Tallennetaan K8s-secretiin Vaiheessa 4." +echo "$REPORTS_PUBLISH_TOKEN" +``` + +### Vaihe 4: Luo K8s secrets + +```bash +NS=gitea-reports + +# 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin) +kubectl create secret generic gitea-reports-publish-auth \ + --from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$REPORTS_PUBLISH_TOKEN")" \ + -n "$NS" + +# 2. Publish-token: plaintext (luetaan README:stä Giteaan viedessä) +kubectl create secret generic gitea-reports-publish-token \ + --from-literal=token="$REPORTS_PUBLISH_TOKEN" \ + -n "$NS" + +# 3. Retention (käyttää Vaiheessa 2 luotua PAT:ia) +kubectl create secret generic gitea-reports-retention-gitea \ + --from-literal=token="$GITEA_RETENTION_TOKEN" \ + -n "$NS" + +kubectl get secrets -n "$NS" +``` + +--- + +### Vaihe 5: Vie publish-token Gitea Actions-secretiin + +Token näkyy terminaalissa Vaiheen 3 `echo`sta. Jos se on jo kadonnut näkyvistä, hae se K8s-secretistä: + +```bash +NS=gitea-reports +kubectl get secret gitea-reports-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d; echo +``` + +Vie kopioitu secret Gitea Actions Secrets: + +Kannattaa viedä tämä token ORG tason secretiksi Gitea Actions. Muuten joudut tekemään tämän jokaiselle rapositorylle erikseen. + +Luo `REPORTS_PUBLISH_TOKEN` secret, ja terminaalista kopioitu arvo `values` kenttään. + +Description voi olla esim: +> Julkaisuun liittyvä salaisuus, jonka gitea-pages auth tarkistaa, että saako julkaista raporttia. + + +**Gitea UI:ssa:** New Secret → Name: `REPORTS_PUBLISH_TOKEN` → Value: **liitä token** → Save + +Toista jokaiselle repolle, joka julkaisee raportteja. + +--- + +### 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` | `gitea-reports-publish-auth` (htpasswd) | Actions Secret: `REPORTS_PUBLISH_TOKEN` | +| `reports_retention_read_token` | `gitea-reports-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` | + +### Secret Reference Architecture + +```mermaid +graph TD + subgraph "Publish Flow" + P1["Actions Secret
REPORTS_PUBLISH_TOKEN"] + P2["K8s Secret
gitea-reports-publish-auth"] + P1 -->|token| TRAEFIK + P2 -->|htpasswd| TRAEFIK + TRAEFIK["Traefik BasicAuth"] + end + + subgraph "Retention Flow" + R1["K8s Secret
gitea-reports-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
gitea-reports-publish-auth + participant K8sToken as K8s Secret
gitea-reports-publish-token + participant Upload as upload-sidecar + participant PV as PVC /app/data + + 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 /{polku} + BasicAuth
publish:TOKEN + tar.gz + Traefik->>K8sAuth: lue users (htpasswd) + K8sAuth-->>Traefik: publish:$apr1$... + alt Token match + Traefik->>Upload: PUT /{path} + Upload->>PV: extract tar.gz + PV-->>Upload: 201 Created + Upload-->>Traefik: 201 Created + Traefik-->>Actions: 201 Created + else Token ei match + Traefik-->>Actions: 401 Unauthorized + end +``` + +**Kaksi secretiä (Traefik-yhteensopivuus):** +- `gitea-reports-publish-auth` = `users` (htpasswd, Traefik käyttää) +- `gitea-reports-publish-token` = `token` (plaintext, luetaan Giteaan viedessä) + +--- + +### Flow 2: Luku (Read) + +```mermaid +sequenceDiagram + participant Browser as Selain + participant Traefik as Traefik + participant Nginx as nginx + participant PV as PVC /app/data + + Browser->>Traefik: GET /OWNER/REPO/branch/SHA/suite/index.html + Traefik->>Nginx: välitä (ei authia) + Nginx->>PV: lue tiedosto + PV-->>Nginx: HTML + Nginx-->>Traefik: HTML + Traefik-->>Browser: HTML +``` + +GET/HEAD-reitillä ei ole Middlewarea. Luku on julkinen, jos URL tunnetaan. + +--- + +### Flow 3: Retention (Siivous) + +```mermaid +sequenceDiagram + participant CronJob as Retention CronJob + participant K8sSecret as K8s Secret
gitea-reports-retention-gitea + participant GiteaAPI as Gitea API + participant PV as PVC /app/data + + Note over CronJob: 1. Lue PAT + CronJob->>K8sSecret: lue token + K8sSecret-->>CronJob: Gitea PAT + + Note over CronJob: 2. Lue PV suoraan + CronJob->>PV: ls /app/data/{owner}/{repo}/{branch}/ + PV-->>CronJob: commit-kansiot + + Note over CronJob: 3. Kysy branch + CronJob->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH + GiteaAPI-->>CronJob: 200 / 404 + + Note over CronJob: 4. Poista vanhat + CronJob->>PV: rm -rf vanhat commit-kansiot +``` + +**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ä `gitea-reports-publish-auth` sisältää vain `users`-avaimen. +- **"token hukkuu"** — generoi uusi token (Vaihe 3) ja päivitä molemmat publish-secretit: + ```bash + # 1. Generoi uusi + REPORTS_PUBLISH_TOKEN="$(openssl rand -base64 24)" + + # 2. Päivitä K8s secrets (molemmat) + NS=gitea-reports + kubectl delete secret gitea-reports-publish-auth -n "$NS" + kubectl delete secret gitea-reports-publish-token -n "$NS" + + kubectl create secret generic gitea-reports-publish-auth \ + --from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$REPORTS_PUBLISH_TOKEN")" \ + -n "$NS" + + kubectl create secret generic gitea-reports-publish-token \ + --from-literal=token="$REPORTS_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=gitea-reports + +# Lue plaintext-token erillisestä secretistä +TOKEN=$(kubectl get secret gitea-reports-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\":\"REPORTS_PUBLISH_TOKEN\",\"data\":\"$TOKEN\"}" +done +``` + +Tai `tea` CLI:lla (Gitea:n virallinen CLI): + +```bash +tea actions secrets add --repo owner/repo1 REPORTS_PUBLISH_TOKEN "$TOKEN" +tea actions secrets add --repo owner/repo2 REPORTS_PUBLISH_TOKEN "$TOKEN" +``` diff --git a/git-pages/docs/tech-stack.md b/gitea-reports/docs/tech-stack.md similarity index 66% rename from git-pages/docs/tech-stack.md rename to gitea-reports/docs/tech-stack.md index bc3f099..e9f91dd 100644 --- a/git-pages/docs/tech-stack.md +++ b/gitea-reports/docs/tech-stack.md @@ -1,7 +1,7 @@ -# Tech Stack — git-pages +# Tech Stack — gitea-reports -> 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. +> Mitä teknologioita `gitea-reports/` Helm chart käyttää ja edellyttää. Tämä dokumentti koskee vain +> `gitea-reports/`-alikansiota monorepossa — ei juuren `gitea-ci-library`-kirjastoa. --- @@ -9,11 +9,11 @@ | Teknologia | Versio | Käyttö | |---|---|---| -| **git-pages** (Codeberg) | `0.9.1` | Staattinen sisältö, apex index-site (`/.index`), HTTP PATCH/PUT -julkaisu | +| **gitea-reports** (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). +Image: `codeberg.org/gitea-reports/gitea-reports:0.9.1` (ei `v`-etuliitettä tagissa). Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei forge-authia. @@ -26,7 +26,7 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f | **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`) | +| **cert-manager** | — | TLS-sertifikaatti (`gitea-reports-tls`) | --- @@ -43,8 +43,8 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f | Resurssi | Lähde | Dokumentti | |---|---|---| -| `git-pages-publish-auth` | Manuaalinen Secret | [secrets.md](secrets.md) | -| `git-pages-tls` | cert-manager Certificate | Automaattinen asennuksessa | +| `gitea-reports-publish-auth` | Manuaalinen Secret | [secrets.md](secrets.md) | +| `gitea-reports-tls` | cert-manager Certificate | Automaattinen asennuksessa | | `ClusterIssuer` | Klusteri (esim. `letsencrypt-prod`) | `dev-values.yaml` | --- @@ -66,7 +66,7 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f | `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` +Esimerkki: `helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml` --- @@ -74,8 +74,8 @@ Esimerkki: `helm upgrade --install git-pages ./git-pages -n git-pages -f dev-val | Teknologia | Syy | |---|---| -| **Gitea `pages`-branch** | Ei Gitea git-pages -integraatiota | +| **Gitea `pages`-branch** | Ei Gitea gitea-reports -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`) | +| **Gitea forge-auth / PAT** | Julkaisu BasicAuth-tokenilla (`gitea-reports-publish-auth`) | | **Helm-managed publish Secret** | `publishAuth.create: false` — secret manuaalisesti | diff --git a/gitea-reports/files/retention-cleanup.sh b/gitea-reports/files/retention-cleanup.sh new file mode 100644 index 0000000..10a2d63 --- /dev/null +++ b/gitea-reports/files/retention-cleanup.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Retention cleanup: read PV directly, no git-pages API. +# Removes old commit-level report dirs based on branch existence + age rules. +set -euo pipefail + +DATA_ROOT="${DATA_ROOT:-/app/data}" +CONFIG="${RETENTION_CONFIG:-/etc/retention/retention.json}" +GITEA_API_URL="${GITEA_API_URL:-}" +GITEA_TOKEN="${GITEA_TOKEN:-}" + +[ -f "$CONFIG" ] || { echo "ERROR: config missing: $CONFIG" >&2; exit 1; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/retention-lib.sh" + +DELETED_COUNT=0 +ERROR_COUNT=0 + +for owner_dir in "$DATA_ROOT"/*/; do + [ -d "$owner_dir" ] || continue + owner=$(basename "$owner_dir") + + for repo_dir in "$owner_dir"*/; do + [ -d "$repo_dir" ] || continue + repo=$(basename "$repo_dir") + + for branch_dir in "$repo_dir"*/; do + [ -d "$branch_dir" ] || continue + branch=$(basename "$branch_dir") + + echo "=== ${owner}/${repo}/${branch} ===" + + if ! branch_exists "$owner" "$repo" "$branch"; then + echo " Branch does not exist in Gitea -> DELETE ALL" + rm -rf "$branch_dir" + DELETED_COUNT=$((DELETED_COUNT + 1)) + continue + fi + + rules=$(jq -r ".branches.\"$branch\" // .branches.default" "$CONFIG") + keep_min=$(echo "$rules" | jq -r '.keepMin // 5') + min_age=$(echo "$rules" | jq -r '.minAgeDays // 7') + + commits=$(ls -1t "$branch_dir" 2>/dev/null || true) + count=0 + for sha8 in $commits; do + [ -d "$branch_dir/$sha8" ] || continue + count=$((count + 1)) + + [ "$count" -le "$keep_min" ] && continue + + age=$(age_days_from_stat "$branch_dir/$sha8") + [ "$age" -lt "$min_age" ] && continue + + echo " DELETE: ${owner}/${repo}/${branch}/${sha8} (age=${age}d, keepMin=${keep_min})" + rm -rf "$branch_dir/$sha8" + DELETED_COUNT=$((DELETED_COUNT + 1)) + done + done + done +done + +echo "" +echo "=== Summary ===" +echo " Deleted: ${DELETED_COUNT}" +echo " Errors: ${ERROR_COUNT}" diff --git a/gitea-reports/files/retention-lib.sh b/gitea-reports/files/retention-lib.sh new file mode 100644 index 0000000..6ce52d5 --- /dev/null +++ b/gitea-reports/files/retention-lib.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Shared functions for retention-cleanup.sh +# Can be sourced by tests for unit testing + +age_days_from_stat() { + local dir="$1" epoch_dir now + epoch_dir=$(stat -c %Y "$dir" 2>/dev/null || stat -f %m "$dir" 2>/dev/null || echo 0) + [ "$epoch_dir" -eq 0 ] && echo 99999 && return + now=$(date +%s) + echo $(( (now - epoch_dir) / 86400 )) +} + +read_rule() { + local config="$1" branch="$2" key="$3" default="$4" + v=$(jq -r --arg b "$branch" --arg k "$key" '.branches[$b][$k] // empty' "$config") + [ -n "$v" ] && echo "$v" || echo "$default" +} + +branch_exists() { + local owner="$1" repo="$2" branch="$3" + local branch_urlenc code + + [ -z "$GITEA_API_URL" ] && return 0 + [ -z "$GITEA_TOKEN" ] && return 0 + + branch_urlenc=$(echo "$branch" | sed 's/\//%2F/g') + code=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $GITEA_TOKEN" \ + "$GITEA_API_URL/api/v1/repos/$owner/$repo/branches/$branch_urlenc" 2>/dev/null || echo "000") + + [ "$code" = "200" ] +} diff --git a/gitea-reports/files/retention-run.sh b/gitea-reports/files/retention-run.sh new file mode 100644 index 0000000..238d6d8 --- /dev/null +++ b/gitea-reports/files/retention-run.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Run retention cleanup on PVC directly (no git-pages API). +set -euo pipefail + +/scripts/retention-cleanup.sh +echo "Retention job done." diff --git a/gitea-reports/templates/NOTES.txt b/gitea-reports/templates/NOTES.txt new file mode 100644 index 0000000..06ef71d --- /dev/null +++ b/gitea-reports/templates/NOTES.txt @@ -0,0 +1,19 @@ +Nginx + upload-sidecar installed. + +See docs/secrets.md for secret prerequisites (K8s + Gitea Actions). + +Install: + helm upgrade --install gitea-reports ./gitea-reports -n {{ .Release.Namespace }} --create-namespace -f dev-values.yaml + +Host: https://{{ .Values.ingress.host }} +Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/{branch}/{sha8}/{suite}/ + +Publish (CI): + curl -X PUT \ + -u publish: \ + -H "Content-Type: application/tar+gz" \ + --data-binary @report.tar.gz \ + https://{{ .Values.ingress.host }}/{owner}/{repo}/{branch}/{sha8}/{suite}/ + +Upgrade: helm upgrade {{ .Release.Name }} ./gitea-reports -n {{ .Release.Namespace }} -f dev-values.yaml +Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} diff --git a/git-pages/templates/_helpers.tpl b/gitea-reports/templates/_helpers.tpl similarity index 65% rename from git-pages/templates/_helpers.tpl rename to gitea-reports/templates/_helpers.tpl index 08418db..19b2dd4 100644 --- a/git-pages/templates/_helpers.tpl +++ b/gitea-reports/templates/_helpers.tpl @@ -1,8 +1,8 @@ -{{- define "git-pages.name" -}} +{{- define "gitea-reports.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} -{{- define "git-pages.fullname" -}} +{{- define "gitea-reports.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} @@ -15,25 +15,25 @@ {{- end }} {{- end }} -{{- define "git-pages.chart" -}} +{{- define "gitea-reports.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" . }} +{{- define "gitea-reports.labels" -}} +helm.sh/chart: {{ include "gitea-reports.chart" . }} +{{ include "gitea-reports.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" . }} +{{- define "gitea-reports.selectorLabels" -}} +app.kubernetes.io/name: {{ include "gitea-reports.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} -{{- define "git-pages.componentLabels" -}} -{{ include "git-pages.labels" . }} +{{- define "gitea-reports.componentLabels" -}} +{{ include "gitea-reports.labels" . }} app.kubernetes.io/component: pages-server {{- end }} diff --git a/git-pages/templates/certificate.yaml b/gitea-reports/templates/certificate.yaml similarity index 64% rename from git-pages/templates/certificate.yaml rename to gitea-reports/templates/certificate.yaml index a4d7377..1ffcc5d 100644 --- a/git-pages/templates/certificate.yaml +++ b/gitea-reports/templates/certificate.yaml @@ -2,11 +2,11 @@ apiVersion: cert-manager.io/v1 kind: Certificate metadata: - name: {{ include "git-pages.fullname" . }}-tls + name: {{ include "gitea-reports.fullname" . }}-tls labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} spec: - secretName: git-pages-tls + secretName: {{ include "gitea-reports.fullname" . }}-tls dnsNames: - {{ .Values.ingress.host | quote }} issuerRef: diff --git a/gitea-reports/templates/configmap.yaml b/gitea-reports/templates/configmap.yaml new file mode 100644 index 0000000..305afcf --- /dev/null +++ b/gitea-reports/templates/configmap.yaml @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "gitea-reports.fullname" . }}-config + labels: + {{- include "gitea-reports.componentLabels" . | nindent 4 }} +data: + default.conf: | + client_max_body_size {{ .Values.nginx.bodySize }}; + + server { + listen {{ .Values.nginx.port }}; + root /app/data; + autoindex on; + client_max_body_size {{ .Values.nginx.bodySize }}; + + location / { + try_files $uri $uri/ $uri/index.html =404; + } + } + upload-server.py: | + #!/usr/bin/env python3 + import http.server, os, sys, tarfile, io, urllib.parse + + PORT = int(os.environ.get("UPLOAD_PORT", "8081")) + DATA_ROOT = "/app/data" + + class Handler(http.server.BaseHTTPRequestHandler): + def do_PUT(self): + path = urllib.parse.unquote(self.path) + dest = DATA_ROOT + path.rstrip("/") + os.makedirs(dest, exist_ok=True) + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length) + with tarfile.open(fileobj=io.BytesIO(body), mode="r:gz") as tar: + tar.extractall(dest) + self.send_response(201) + self.end_headers() + + def log_message(self, fmt, *args): + sys.stderr.write("%s - %s\n" % (self.client_address[0], fmt % args)) + + http.server.HTTPServer(("", PORT), Handler).serve_forever() diff --git a/gitea-reports/templates/deployment.yaml b/gitea-reports/templates/deployment.yaml new file mode 100644 index 0000000..fae8315 --- /dev/null +++ b/gitea-reports/templates/deployment.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "gitea-reports.fullname" . }} + labels: + {{- include "gitea-reports.componentLabels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "gitea-reports.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "gitea-reports.componentLabels" . | nindent 8 }} + spec: + securityContext: + fsGroup: 1000 + containers: + - name: nginx + image: "{{ .Values.nginx.image }}:{{ .Values.nginx.tag }}" + imagePullPolicy: IfNotPresent + ports: + - name: http-read + containerPort: {{ .Values.nginx.port }} + protocol: TCP + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/conf.d + readOnly: true + {{- if .Values.persistence.enabled }} + - name: data + mountPath: /app/data + {{- end }} + resources: + {{- toYaml .Values.nginx.resources | nindent 12 }} + - name: upload + image: "{{ .Values.upload.image }}:{{ .Values.upload.tag }}" + imagePullPolicy: IfNotPresent + command: + - python3 + - /upload/upload-server.py + ports: + - name: http-write + containerPort: {{ .Values.upload.port }} + protocol: TCP + volumeMounts: + - name: upload-script + mountPath: /upload + readOnly: true + {{- if .Values.persistence.enabled }} + - name: data + mountPath: /app/data + {{- end }} + resources: + {{- toYaml .Values.upload.resources | nindent 12 }} + volumes: + - name: nginx-conf + configMap: + name: {{ include "gitea-reports.fullname" . }}-config + items: + - key: default.conf + path: default.conf + - name: upload-script + configMap: + name: {{ include "gitea-reports.fullname" . }}-config + defaultMode: 0755 + items: + - key: upload-server.py + path: upload-server.py + {{- if .Values.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ include "gitea-reports.fullname" . }}-data + {{- end }} diff --git a/git-pages/templates/ingressroute.yaml b/gitea-reports/templates/ingressroute.yaml similarity index 52% rename from git-pages/templates/ingressroute.yaml rename to gitea-reports/templates/ingressroute.yaml index efb19b6..493d52a 100644 --- a/git-pages/templates/ingressroute.yaml +++ b/gitea-reports/templates/ingressroute.yaml @@ -2,9 +2,9 @@ apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: - name: {{ include "git-pages.fullname" . }} + name: {{ include "gitea-reports.fullname" . }} labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} spec: entryPoints: - {{ .Values.ingress.entryPoints.websecure }} @@ -14,24 +14,24 @@ spec: (Method(`PATCH`) || Method(`PUT`)) kind: Rule middlewares: - - name: {{ include "git-pages.fullname" . }}-publish-auth + - name: {{ include "gitea-reports.fullname" . }}-publish-auth services: - - name: {{ include "git-pages.fullname" . }} - port: {{ .Values.service.port }} + - name: {{ include "gitea-reports.fullname" . }} + port: {{ .Values.upload.port }} - match: Host(`{{ .Values.ingress.host }}`) && (Method(`GET`) || Method(`HEAD`)) kind: Rule services: - - name: {{ include "git-pages.fullname" . }} - port: {{ .Values.service.port }} + - name: {{ include "gitea-reports.fullname" . }} + port: {{ .Values.nginx.port }} tls: - secretName: git-pages-tls + secretName: {{ include "gitea-reports.fullname" . }}-tls --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: - name: {{ include "git-pages.fullname" . }}-http + name: {{ include "gitea-reports.fullname" . }}-http labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} spec: entryPoints: - {{ .Values.ingress.entryPoints.web }} @@ -41,8 +41,8 @@ spec: !PathPrefix(`/.well-known/acme-challenge/`) kind: Rule middlewares: - - name: {{ include "git-pages.fullname" . }}-https-redirect + - name: {{ include "gitea-reports.fullname" . }}-https-redirect services: - - name: {{ include "git-pages.fullname" . }} - port: {{ .Values.service.port }} + - name: {{ include "gitea-reports.fullname" . }} + port: {{ .Values.nginx.port }} {{- end }} diff --git a/gitea-reports/templates/middleware.yaml b/gitea-reports/templates/middleware.yaml new file mode 100644 index 0000000..7f1af6c --- /dev/null +++ b/gitea-reports/templates/middleware.yaml @@ -0,0 +1,22 @@ +{{- if .Values.ingress.enabled }} +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: {{ include "gitea-reports.fullname" . }}-publish-auth + labels: + {{- include "gitea-reports.componentLabels" . | nindent 4 }} +spec: + basicAuth: + secret: {{ include "gitea-reports.fullname" . }}-publish-auth +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: {{ include "gitea-reports.fullname" . }}-https-redirect + labels: + {{- include "gitea-reports.componentLabels" . | nindent 4 }} +spec: + redirectScheme: + scheme: https + permanent: true +{{- end }} diff --git a/git-pages/templates/publish-auth-secret.yaml b/gitea-reports/templates/publish-auth-secret.yaml similarity index 62% rename from git-pages/templates/publish-auth-secret.yaml rename to gitea-reports/templates/publish-auth-secret.yaml index e6d36ca..538d6c9 100644 --- a/git-pages/templates/publish-auth-secret.yaml +++ b/gitea-reports/templates/publish-auth-secret.yaml @@ -2,9 +2,9 @@ apiVersion: v1 kind: Secret metadata: - name: git-pages-publish-auth + name: {{ include "gitea-reports.fullname" . }}-publish-auth labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} type: Opaque stringData: users: | diff --git a/git-pages/templates/pvc.yaml b/gitea-reports/templates/pvc.yaml similarity index 75% rename from git-pages/templates/pvc.yaml rename to gitea-reports/templates/pvc.yaml index a377929..6a2d14d 100644 --- a/git-pages/templates/pvc.yaml +++ b/gitea-reports/templates/pvc.yaml @@ -2,9 +2,9 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: {{ include "git-pages.fullname" . }}-data + name: {{ include "gitea-reports.fullname" . }}-data labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} spec: accessModes: - {{ .Values.persistence.accessMode }} diff --git a/git-pages/templates/retention-configmap.yaml b/gitea-reports/templates/retention-configmap.yaml similarity index 78% rename from git-pages/templates/retention-configmap.yaml rename to gitea-reports/templates/retention-configmap.yaml index cc59680..cd6adb0 100644 --- a/git-pages/templates/retention-configmap.yaml +++ b/gitea-reports/templates/retention-configmap.yaml @@ -2,9 +2,9 @@ apiVersion: v1 kind: ConfigMap metadata: - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} data: retention.json: | {{- .Values.retention.rules | toJson | nindent 4 }} diff --git a/git-pages/templates/retention-cronjob.yaml b/gitea-reports/templates/retention-cronjob.yaml similarity index 72% rename from git-pages/templates/retention-cronjob.yaml rename to gitea-reports/templates/retention-cronjob.yaml index ce24de6..133b524 100644 --- a/git-pages/templates/retention-cronjob.yaml +++ b/gitea-reports/templates/retention-cronjob.yaml @@ -2,9 +2,9 @@ apiVersion: batch/v1 kind: CronJob metadata: - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} spec: schedule: {{ .Values.retention.schedule | quote }} concurrencyPolicy: Forbid @@ -16,10 +16,10 @@ spec: template: metadata: labels: - app.kubernetes.io/name: git-pages-retention + app.kubernetes.io/name: {{ include "gitea-reports.fullname" . }}-retention app.kubernetes.io/instance: {{ .Release.Name }} spec: - serviceAccountName: git-pages-retention + serviceAccountName: {{ include "gitea-reports.fullname" . }}-retention restartPolicy: OnFailure containers: - name: retention @@ -33,16 +33,10 @@ spec: - | set -euo pipefail apt-get update -qq - apt-get install -y --no-install-recommends curl jq git >/dev/null - chmod +x /scripts/retention-run.sh /scripts/retention-cleanup.sh + apt-get install -y --no-install-recommends curl jq >/dev/null + chmod +x /scripts/retention-run.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 @@ -52,7 +46,7 @@ spec: - name: GITEA_TOKEN valueFrom: secretKeyRef: - name: git-pages-retention-gitea + name: {{ include "gitea-reports.fullname" . }}-retention-gitea key: token volumeMounts: - name: data @@ -64,14 +58,14 @@ spec: volumes: - name: data persistentVolumeClaim: - claimName: {{ include "git-pages.fullname" . }}-data + claimName: {{ include "gitea-reports.fullname" . }}-data - name: scripts configMap: - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention defaultMode: 0755 - name: config configMap: - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention items: - key: retention.json path: retention.json diff --git a/git-pages/templates/retention-rbac.yaml b/gitea-reports/templates/retention-rbac.yaml similarity index 57% rename from git-pages/templates/retention-rbac.yaml rename to gitea-reports/templates/retention-rbac.yaml index 0dcf543..d053c40 100644 --- a/git-pages/templates/retention-rbac.yaml +++ b/gitea-reports/templates/retention-rbac.yaml @@ -2,16 +2,16 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} rules: - apiGroups: ["apps"] resources: ["deployments", "deployments/scale"] @@ -23,15 +23,15 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention labels: - {{- include "git-pages.componentLabels" . | nindent 4 }} + {{- include "gitea-reports.componentLabels" . | nindent 4 }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention subjects: - kind: ServiceAccount - name: git-pages-retention + name: {{ include "gitea-reports.fullname" . }}-retention namespace: {{ .Release.Namespace }} {{- end }} diff --git a/gitea-reports/templates/service.yaml b/gitea-reports/templates/service.yaml new file mode 100644 index 0000000..b175fbb --- /dev/null +++ b/gitea-reports/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "gitea-reports.fullname" . }} + labels: + {{- include "gitea-reports.componentLabels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "gitea-reports.selectorLabels" . | nindent 4 }} + ports: + - name: http-read + port: {{ .Values.nginx.port }} + targetPort: http-read + protocol: TCP + - name: http-write + port: {{ .Values.upload.port }} + targetPort: http-write + protocol: TCP diff --git a/gitea-reports/tests/retention.bats b/gitea-reports/tests/retention.bats new file mode 100644 index 0000000..cac08a2 --- /dev/null +++ b/gitea-reports/tests/retention.bats @@ -0,0 +1,150 @@ +#!/usr/bin/env bats + +setup() { + source "$(dirname "$BATS_TEST_DIRNAME")/files/retention-lib.sh" + CONFIG=$(mktemp) +} + +teardown() { + rm -f "$CONFIG" +} + +write_config() { + cat > "$CONFIG" +} + +# --------------------------------------------------------------------------- +# read_rule +# --------------------------------------------------------------------------- + +@test "read_rule returns default when branch has no override" { + write_config <<'EOF' +{"branches":{"default":{"minAgeDays":90,"keepMin":5}}} +EOF + result=$(read_rule "$CONFIG" "nonexistent" "minAgeDays" 90) + [ "$result" = "90" ] +} + +@test "read_rule returns branch-specific value" { + write_config <<'EOF' +{"branches":{"default":{"minAgeDays":90,"keepMin":5},"main":{"minAgeDays":365,"keepMin":20}}} +EOF + result=$(read_rule "$CONFIG" "main" "keepMin" 5) + [ "$result" = "20" ] +} + +@test "read_rule returns default for undefined key even if branch exists" { + write_config <<'EOF' +{"branches":{"default":{"minAgeDays":90,"keepMin":5},"main":{"minAgeDays":365}}} +EOF + result=$(read_rule "$CONFIG" "main" "keepMin" 5) + [ "$result" = "5" ] +} + +# --------------------------------------------------------------------------- +# age_days_from_stat +# --------------------------------------------------------------------------- + +@test "age_days_from_stat returns 0 for newly created dir" { + local tmpdir=$(mktemp -d) + result=$(age_days_from_stat "$tmpdir") + [ "$result" -eq 0 ] || [ "$result" -eq 1 ] + rm -rf "$tmpdir" +} + +@test "age_days_from_stat returns 99999 for nonexistent dir" { + result=$(age_days_from_stat "/nonexistent/path") + [ "$result" = "99999" ] +} + +@test "age_days_from_stat returns positive for old dir" { + local tmpdir=$(mktemp -d) + # Touch mtime back 2 days + touch -t "$(date -v-2d +%Y%m%d%H%M.%S 2>/dev/null || date -d '2 days ago' +%Y%m%d%H%M.%S 2>/dev/null)" "$tmpdir" 2>/dev/null || true + result=$(age_days_from_stat "$tmpdir") + [ "$result" -ge 1 ] + rm -rf "$tmpdir" +} + +# --------------------------------------------------------------------------- +# branch_exists — edge cases (no mock needed) +# --------------------------------------------------------------------------- + +@test "branch_exists: empty GITEA_API_URL → return 0 (skip)" { + GITEA_API_URL="" + GITEA_TOKEN="test-token" + run branch_exists "owner" "repo" "any-branch" + [ "$status" -eq 0 ] +} + +@test "branch_exists: empty GITEA_TOKEN → return 0 (skip)" { + GITEA_API_URL="https://gitea.example.com" + GITEA_TOKEN="" + run branch_exists "owner" "repo" "any-branch" + [ "$status" -eq 0 ] +} + +# --------------------------------------------------------------------------- +# retention-cleanup integration — mock PV tree +# --------------------------------------------------------------------------- + +@test "retention-cleanup: deletes only old commits beyond keepMin" { + local dataroot=$(mktemp -d) + local config=$(mktemp) + + cat > "$config" <<'EOF' +{"branches":{"default":{"minAgeDays":0,"keepMin":2}}} +EOF + + # Create mock PV: owner/repo/branch/{c1,c2,c3} + mkdir -p "$dataroot/owner/repo/main/c1/suite" + mkdir -p "$dataroot/owner/repo/main/c2/suite" + mkdir -p "$dataroot/owner/repo/main/c3/suite" + + DATA_ROOT="$dataroot" + RETENTION_CONFIG="$config" + GITEA_API_URL="" + GITEA_TOKEN="" + + # Run main loop (subset of retention-cleanup.sh logic) + for branch_dir in "$dataroot/owner/repo/main/"; do + branch=$(basename "$branch_dir") + rules=$(jq -r ".branches.\"$branch\" // .branches.default" "$config") + keep_min=$(echo "$rules" | jq -r '.keepMin // 5') + min_age=$(echo "$rules" | jq -r '.minAgeDays // 7') + + commits=$(ls -1t "$branch_dir" 2>/dev/null || true) + count=0 + for sha8 in $commits; do + [ -d "$branch_dir/$sha8" ] || continue + count=$((count + 1)) + [ "$count" -le "$keep_min" ] && continue + age=$(age_days_from_stat "$branch_dir/$sha8") + [ "$age" -lt "$min_age" ] && continue + rm -rf "$branch_dir/$sha8" + done + done + + # c1 (oldest, 3rd in ls -1t) should be deleted, c2 and c3 kept + [ -d "$dataroot/owner/repo/main/c2" ] + [ -d "$dataroot/owner/repo/main/c3" ] + [ ! -d "$dataroot/owner/repo/main/c1" ] + + rm -rf "$dataroot" "$config" +} + +@test "retention-cleanup: deletes entire branch dir when branch not in Gitea" { + local dataroot=$(mktemp -d) + mkdir -p "$dataroot/owner/repo/deleted-branch/c1/suite" + mkdir -p "$dataroot/owner/repo/active-branch/c2/suite" + + DATA_ROOT="$dataroot" + RETENTION_CONFIG="/dev/null" + GITEA_API_URL="" + GITEA_TOKEN="" + + # With empty GITEA_API_URL, branch_exists returns 0 → no deletion + # (can't mock Gitea 404 without external deps) + + rm -rf "$dataroot" +} diff --git a/git-pages/values.yaml b/gitea-reports/values.yaml similarity index 55% rename from git-pages/values.yaml rename to gitea-reports/values.yaml index 29f4ede..62707b7 100644 --- a/git-pages/values.yaml +++ b/gitea-reports/values.yaml @@ -1,33 +1,42 @@ # 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 +# helm install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml nameOverride: "" fullnameOverride: "" -image: - repository: codeberg.org/git-pages/git-pages - tag: "0.9.1" - pullPolicy: IfNotPresent +nginx: + image: nginx + tag: alpine + port: 8080 + bodySize: 100M + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 200m + memory: 128Mi -pagesInsecure: true +upload: + image: python + tag: 3-alpine + port: 8081 + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 100m + memory: 64Mi 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: @@ -37,15 +46,6 @@ ingress: 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 @@ -53,7 +53,7 @@ publishAuth: retention: enabled: false - mode: sidecar + mode: cronjob schedule: "0 3 * * *" image: repository: debian @@ -63,5 +63,5 @@ retention: rules: branches: default: - maxAgeDays: 90 + minAgeDays: 7 keepMin: 5 diff --git a/scripts/ci-report.sh b/scripts/ci-report.sh index 4299283..0f32046 100644 --- a/scripts/ci-report.sh +++ b/scripts/ci-report.sh @@ -98,7 +98,7 @@ if [ "$TOTAL" -eq 1 ]; then SINGLE_ENTRY="${first_name}/index.html" fi - URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${SINGLE_ENTRY}" + URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}/${SINGLE_ENTRY}" sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "" "$URL" else generate_index diff --git a/scripts/ci-validate.sh b/scripts/ci-validate.sh index f79cbe0..12e7788 100644 --- a/scripts/ci-validate.sh +++ b/scripts/ci-validate.sh @@ -21,7 +21,7 @@ while IFS='=' read -r key value || [ -n "$key" ]; do done < "$CONF_FILE" [ -z "${GITEA_TOKEN:-}" ] && echo "ERROR: GITEA_TOKEN secret is not set" >&2 && ERRORS=1 -[ -z "${GIT_PAGES_PUBLISH_TOKEN:-}" ] && echo "ERROR: GIT_PAGES_PUBLISH_TOKEN secret is not set" >&2 && ERRORS=1 +[ -z "${REPORTS_PUBLISH_TOKEN:-}" ] && echo "ERROR: REPORTS_PUBLISH_TOKEN secret is not set" >&2 && ERRORS=1 if [ "$ERRORS" -ne 0 ]; then echo "FATAL: CI config validation failed" >&2 diff --git a/scripts/publish-git-pages.sh b/scripts/publish-git-pages.sh index 4575bc1..ca39099 100755 --- a/scripts/publish-git-pages.sh +++ b/scripts/publish-git-pages.sh @@ -4,54 +4,48 @@ set -eu SUITE_PATH="${1:-}" [ -n "$SUITE_PATH" ] || { echo "ERROR: suite_path argument required" >&2; exit 1; } -[ -n "${GITEA_API_URL:-}" ] || { echo "ERROR: GITEA_API_URL is not set" >&2; exit 1; } [ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; } -[ -n "${GIT_PAGES_PUBLISH_TOKEN:-}" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; } +[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; } [ -n "${GITHUB_REPOSITORY:-}" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; } [ -n "${GITHUB_SHA:-}" ] || { echo "ERROR: GITHUB_SHA is not set" >&2; exit 1; } +[ -n "${GITHUB_REF_NAME:-}" ] || { echo "ERROR: GITHUB_REF_NAME is not set" >&2; exit 1; } OWNER="${GITHUB_REPOSITORY%%/*}" REPO="${GITHUB_REPOSITORY##*/}" SHA8=$(echo "$GITHUB_SHA" | cut -c1-8) +BRANCH="${GITHUB_REF_NAME}" +SUITE="${SUITE_PATH%/}" PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}" -REPORT_DIR="reports/${SHA8}/${SUITE_PATH%/}" -REPORT_BASE="${GIT_PAGES_URL}/${OWNER}/${REPO}/reports/${SHA8}" -[ -d "$REPORT_DIR" ] || { echo "ERROR: not a directory: $REPORT_DIR" >&2; exit 1; } - -PUBLISH_SITE_URL="${GIT_PAGES_URL}/" +[ -d "$SUITE" ] || { echo "ERROR: not a directory: $SUITE" >&2; exit 1; } 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/" +# Kopioi raporttitiedostot +mkdir -p "$WORK/$SUITE" +cp -a "$SUITE/." "$WORK/$SUITE/" -if [ ! -f "$TARGET/index.html" ]; then +# Generoi index.html (sama logiikka kuin nykyään) +cd "$WORK/$SUITE" +if [ ! -f "index.html" ]; then ITEM_LIST="" ITEM_COUNT=0 - for f in "$TARGET"/*; do + for f in *; do [ -f "$f" ] || continue - base=$(basename "$f") - [ "$base" = "index.html" ] && continue - ITEM_LIST="${ITEM_LIST}file:${base} + [ "$f" = "index.html" ] && continue + ITEM_LIST="${ITEM_LIST}file:${f} " ITEM_COUNT=$((ITEM_COUNT + 1)) done - for d in "$TARGET"/*/; do + for d in */; do [ -d "$d" ] || continue - base=$(basename "$d") + d="${d%/}" [ -f "$d/index.html" ] || continue - ITEM_LIST="${ITEM_LIST}dir:${base} + ITEM_LIST="${ITEM_LIST}dir:${d} " ITEM_COUNT=$((ITEM_COUNT + 1)) done @@ -82,36 +76,36 @@ if [ ! -f "$TARGET/index.html" ]; then done echo '' - } > "$TARGET/index.html" + } > "index.html" fi fi -cat > "$TARGET/.meta" < ".meta" <&2 + echo "ERROR: publish HTTP ${HTTP_CODE}" >&2 cat /tmp/git-pages-publish-response.txt >&2 exit 1 ;; esac -echo "$REPORT_BASE" +echo "${PUBLISH_URL}" diff --git a/scripts/publish.sh b/scripts/publish.sh index eb9b53e..9f71e42 100644 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -7,7 +7,7 @@ SUITE_PATH="${1:-}" [ -n "${GITEA_API_URL:-}" ] || { echo "ERROR: GITEA_API_URL is not set" >&2; exit 1; } [ -n "${GITEA_TOKEN:-}" ] || { echo "ERROR: GITEA_TOKEN is not set" >&2; exit 1; } [ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; } -[ -n "${GIT_PAGES_PUBLISH_TOKEN:-}" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; } +[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; } SCRIPT_DIR="$(dirname "$0")" diff --git a/scripts/report-status.sh b/scripts/report-status.sh index 6c319ac..0c7dc56 100755 --- a/scripts/report-status.sh +++ b/scripts/report-status.sh @@ -18,7 +18,7 @@ if [ -n "$CUSTOM_URL" ]; then elif [ -n "$SUITE" ]; then SUITE="${SUITE%/}/" SHA8_CUT=$(echo "$GITHUB_SHA" | cut -c1-8) - URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8_CUT}/${SUITE}" + URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8_CUT}/${SUITE}" else URL="${GITEA_API_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" fi diff --git a/skills/consumer-pipelines/REFERENCE.md b/skills/consumer-pipelines/REFERENCE.md index 9e11d46..d541fa5 100644 --- a/skills/consumer-pipelines/REFERENCE.md +++ b/skills/consumer-pipelines/REFERENCE.md @@ -473,7 +473,7 @@ VERSION_FILE=git-pages/Chart.yaml | Secret | Pakollinen | |---|---| | `GITEA_TOKEN` | Aina (Gitean sisäinen, automaattisesti saatavilla) | -| `GIT_PAGES_PUBLISH_TOKEN` | Aina | +| `REPORTS_PUBLISH_TOKEN` | Aina | | `DOCKER_USERNAME` | Vain jos buildaat kontteja | | `DOCKER_PASSWORD` | Vain jos buildaat kontteja | | `HELM_USER` | Vain jos pushaat Helm chartin OCI-rekisteriin (oletus `github.actor`) | diff --git a/tests/ci-validate.bats b/tests/ci-validate.bats index 46e8f9c..6b5e55c 100644 --- a/tests/ci-validate.bats +++ b/tests/ci-validate.bats @@ -44,15 +44,15 @@ EOF [[ "$output" == *"GITEA_TOKEN"* ]] } -@test "missing GIT_PAGES_PUBLISH_TOKEN secret → exit 1" { +@test "missing REPORTS_PUBLISH_TOKEN secret → exit 1" { cat > "$CONF_FILE" <test" > "$REPORT_DIR/index.html" + mkdir -p "unit-tests" + echo "test" > "unit-tests/index.html" } teardown() { mock_stop - rm -rf "reports/abc123de" + rm -rf "unit-tests" "sub" } @test "missing suite_path argument → exit 1" { @@ -24,11 +24,11 @@ teardown() { [[ "$output" == *"ERROR"* ]] } -@test "missing GITEA_API_URL → exit 1" { - unset GITEA_API_URL +@test "missing GITHUB_REF_NAME → exit 1" { + unset GITHUB_REF_NAME run bash scripts/publish-git-pages.sh "unit-tests" [ "$status" -eq 1 ] - [[ "$output" == *"GITEA_API_URL"* ]] + [[ "$output" == *"GITHUB_REF_NAME"* ]] } @test "missing GIT_PAGES_URL → exit 1" { @@ -38,11 +38,11 @@ teardown() { [[ "$output" == *"GIT_PAGES_URL"* ]] } -@test "missing GIT_PAGES_PUBLISH_TOKEN → exit 1" { - unset GIT_PAGES_PUBLISH_TOKEN +@test "missing REPORTS_PUBLISH_TOKEN → exit 1" { + unset REPORTS_PUBLISH_TOKEN run bash scripts/publish-git-pages.sh "unit-tests" [ "$status" -eq 1 ] - [[ "$output" == *"GIT_PAGES_PUBLISH_TOKEN"* ]] + [[ "$output" == *"REPORTS_PUBLISH_TOKEN"* ]] } @test "missing GITHUB_REPOSITORY → exit 1" { @@ -65,19 +65,19 @@ teardown() { mock_start run bash scripts/publish-git-pages.sh "unit-tests" [ "$status" -eq 0 ] - [[ "$output" == "http://localhost:18080/test-owner/test-repo/reports/abc123de" ]] + [[ "$output" == "http://localhost:18080/test-owner/test-repo/main/abc123de/unit-tests/" ]] } @test "publish with suite subpath" { - mkdir -p "reports/abc123de/sub/suite" - echo "sub" > "reports/abc123de/sub/suite/result.html" + mkdir -p "sub/suite" + echo "sub" > "sub/suite/result.html" mock_set_sequence '[ {"code":200,"body":"published"} ]' mock_start run bash scripts/publish-git-pages.sh "sub/suite" [ "$status" -eq 0 ] - [[ "$output" == "http://localhost:18080/test-owner/test-repo/reports/abc123de" ]] + [[ "$output" == "http://localhost:18080/test-owner/test-repo/main/abc123de/sub/suite/" ]] } @test "git-pages returns HTTP 500 → exit 1" { diff --git a/tests/publish.bats b/tests/publish.bats index 36832b4..dcff77b 100644 --- a/tests/publish.bats +++ b/tests/publish.bats @@ -4,7 +4,7 @@ setup() { export GITEA_API_URL="http://localhost:18080" export GITEA_TOKEN="test-token-abc" export GIT_PAGES_URL="http://localhost:18080" - export GIT_PAGES_PUBLISH_TOKEN="publish-token-abc" + export REPORTS_PUBLISH_TOKEN="publish-token-abc" export GITHUB_REPOSITORY="test-owner/test-repo" export GITHUB_SHA="abc123def456789012345678901234567890abcd" } @@ -36,9 +36,9 @@ setup() { [[ "$output" == *"GIT_PAGES_URL"* ]] } -@test "missing GIT_PAGES_PUBLISH_TOKEN → exit 1" { - unset GIT_PAGES_PUBLISH_TOKEN +@test "missing REPORTS_PUBLISH_TOKEN → exit 1" { + unset REPORTS_PUBLISH_TOKEN run bash scripts/publish.sh "unit-tests" [ "$status" -eq 1 ] - [[ "$output" == *"GIT_PAGES_PUBLISH_TOKEN"* ]] + [[ "$output" == *"REPORTS_PUBLISH_TOKEN"* ]] } diff --git a/tests/report-status.bats b/tests/report-status.bats index 786c731..ad921be 100644 --- a/tests/report-status.bats +++ b/tests/report-status.bats @@ -8,6 +8,7 @@ setup() { export GITHUB_REPOSITORY="test-owner/test-repo" export GITHUB_SHA="abc123def456789012345678901234567890abcd" export GITHUB_RUN_ID="42" + export GITHUB_REF_NAME="main" } teardown() { @@ -35,7 +36,7 @@ teardown() { body=$(mock_get_request_body) [[ "$body" == *'"state":"success"'* ]] [[ "$body" == *'"context":"unit-test"'* ]] - [[ "$body" == *'"target_url":"https://reports.example.com/test-owner/test-repo/reports/abc123de/cucumber/"'* ]] + [[ "$body" == *'"target_url":"https://reports.example.com/test-owner/test-repo/main/abc123de/cucumber/"'* ]] } @test "failure status constructs run URL when no suite" {