From 179f6a1b43aa8e191b8b99d881203e11e3b6f265 Mon Sep 17 00:00:00 2001 From: moilanik Date: Sun, 28 Jun 2026 05:27:42 +0300 Subject: [PATCH] =?UTF-8?q?publish=20->=20link=20->=20read=20testattu=20"e?= =?UTF-8?q?2e"=20port=20forward,=20local=20publish=20ja=20luettu=20t=C3=A4?= =?UTF-8?q?m=C3=A4n=20repon=20linkin=20luomisen=20mallisella=20url=20julka?= =?UTF-8?q?istu=20raportti?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/report-summary.yml | 2 +- docs/design-rationale.md | 239 +------ docs/git-pages-nginx-arkkitehtuuri.md | 10 +- git-pages/Chart.yaml | 6 - git-pages/docs/architecture.md | 142 ---- git-pages/files/retention-cleanup.sh | 276 -------- git-pages/files/retention-lib.sh | 184 ------ git-pages/files/retention-run.sh | 19 - git-pages/templates/configmap.yaml | 20 - git-pages/templates/deployment.yaml | 127 ---- git-pages/templates/init-job.yaml | 58 -- git-pages/tests/retention.bats | 623 ------------------ gitea-reports/Chart.yaml | 5 + {git-pages => gitea-reports}/README.md | 73 +- {git-pages => gitea-reports}/dev-values.yaml | 2 +- gitea-reports/docs/architecture.md | 82 +++ .../docs/design-rationale.md | 0 .../docs/implementation-notes.md | 0 {git-pages => gitea-reports}/docs/secrets.md | 57 +- .../docs/tech-stack.md | 2 +- gitea-reports/files/retention-cleanup.sh | 66 ++ gitea-reports/files/retention-lib.sh | 32 + gitea-reports/files/retention-run.sh | 6 + .../templates/NOTES.txt | 13 +- .../templates/_helpers.tpl | 0 .../templates/certificate.yaml | 0 gitea-reports/templates/configmap.yaml | 42 ++ gitea-reports/templates/deployment.yaml | 75 +++ .../templates/ingressroute.yaml | 6 +- .../templates/middleware.yaml | 0 .../templates/publish-auth-secret.yaml | 0 .../templates/pvc.yaml | 0 .../templates/retention-configmap.yaml | 0 .../templates/retention-cronjob.yaml | 10 +- .../templates/retention-rbac.yaml | 0 .../templates/service.yaml | 10 +- gitea-reports/tests/retention.bats | 150 +++++ {git-pages => gitea-reports}/values.yaml | 52 +- scripts/ci-report.sh | 2 +- scripts/publish-git-pages.sh | 74 +-- scripts/report-status.sh | 2 +- 41 files changed, 638 insertions(+), 1829 deletions(-) delete mode 100644 git-pages/Chart.yaml delete mode 100644 git-pages/docs/architecture.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/configmap.yaml delete mode 100644 git-pages/templates/deployment.yaml delete mode 100644 git-pages/templates/init-job.yaml delete mode 100644 git-pages/tests/retention.bats create mode 100644 gitea-reports/Chart.yaml rename {git-pages => gitea-reports}/README.md (61%) rename {git-pages => gitea-reports}/dev-values.yaml (84%) create mode 100644 gitea-reports/docs/architecture.md rename {git-pages => gitea-reports}/docs/design-rationale.md (100%) rename {git-pages => gitea-reports}/docs/implementation-notes.md (100%) rename {git-pages => gitea-reports}/docs/secrets.md (84%) rename {git-pages => gitea-reports}/docs/tech-stack.md (96%) 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 rename {git-pages => gitea-reports}/templates/NOTES.txt (51%) rename {git-pages => gitea-reports}/templates/_helpers.tpl (100%) rename {git-pages => gitea-reports}/templates/certificate.yaml (100%) create mode 100644 gitea-reports/templates/configmap.yaml create mode 100644 gitea-reports/templates/deployment.yaml rename {git-pages => gitea-reports}/templates/ingressroute.yaml (91%) rename {git-pages => gitea-reports}/templates/middleware.yaml (100%) rename {git-pages => gitea-reports}/templates/publish-auth-secret.yaml (100%) rename {git-pages => gitea-reports}/templates/pvc.yaml (100%) rename {git-pages => gitea-reports}/templates/retention-configmap.yaml (100%) rename {git-pages => gitea-reports}/templates/retention-cronjob.yaml (86%) rename {git-pages => gitea-reports}/templates/retention-rbac.yaml (100%) rename {git-pages => gitea-reports}/templates/service.yaml (60%) create mode 100644 gitea-reports/tests/retention.bats rename {git-pages => gitea-reports}/values.yaml (55%) 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/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 index 7812d47..27e0ebb 100644 --- a/docs/git-pages-nginx-arkkitehtuuri.md +++ b/docs/git-pages-nginx-arkkitehtuuri.md @@ -484,8 +484,8 @@ done | # | Vaihe | Mitä | Riippuvuus | |---|-------|------|-----------| -| 1 | **Poista vanha PVC** | `kubectl delete pvc git-pages-data -n git-pages` (tai helm uninstall + uusi asennus) | Vanha git-pages-data .index-muodossa — tuhotaan | -| 2 | **Deployaa uusi chart** | `helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml` | Uusi PVC luodaan tyhjänä. Vanhoja raportteja ei ole. | +| 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 | @@ -519,8 +519,8 @@ Pidä vanha PVC hengessä (nimeä uudelleen, älä poista) kunnes rollback-ikkun ```bash # Ennen vaihetta 1: uudelleennimeä vanha PVC -kubectl delete pvc git-pages-data -n git-pages --wait=false -kubectl patch pvc git-pages-data -n git-pages -p '{"metadata":{"finalizers":[]}}' --type merge # tarvittaessa +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). @@ -531,7 +531,7 @@ Jokaiselle komponentille check ennen seuraavaan vaiheeseen siirtymistä: | Vaihe | Check | |-------|-------| -| **helm template** | `helm template git-pages ./git-pages -n git-pages -f dev-values.yaml` → deploymentissa 2 konttia (nginx + upload) | +| **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ä | diff --git a/git-pages/Chart.yaml b/git-pages/Chart.yaml deleted file mode 100644 index 7455afb..0000000 --- a/git-pages/Chart.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v2 -name: git-pages -description: Codeberg git-pages for CI HTML reports (apex site, Traefik BasicAuth publish) -type: application -version: 0.1.0 -appVersion: "0.9.1" diff --git a/git-pages/docs/architecture.md b/git-pages/docs/architecture.md deleted file mode 100644 index 8e82bb4..0000000 --- a/git-pages/docs/architecture.md +++ /dev/null @@ -1,142 +0,0 @@ -# Architecture — git-pages - -> Komponentit, datavirrat ja rajapinnat. Miksi näin on rakennettu: [design-rationale.md](design-rationale.md). -> Secretit: [secrets.md](secrets.md). Teknologiat: [tech-stack.md](tech-stack.md). - -Tämä dokumentti koskee vain `git-pages/`-palvelua — ei juuren `gitea-ci-library`-kirjastoa. - ---- - -## Yleiskuvaus - -git-pages on jaettu **HTML-raporttiarkisto**: yksi apex-host, monta Gitea-repoa, commit-kohtaiset -raporttipolut. Julkaisija (esim. CI) puskaa sisällön tar-arkistona; lukija avaa raportin -selaimella commit-linkistä. - -Codeberg git-pages ajaa `PAGES_INSECURE=1` — sovellus ei tee forge-authia. Julkaisu- ja -TLS-rajaukset ovat Kubernetes-kerroksessa (Traefik, cert-manager, Secretit). - ---- - -## Komponentit - -| Komponentti | Rooli | -|-------------|-------| -| **git-pages Pod** | Codeberg git-pages `0.9.1`, filesystem-storage `/app/data` | -| **retention sidecar** | Samassa podissa, HTTP API localhost:3000, siivoaa vanhat raportit | -| **PVC** | Raporttisisältö (storage v2 — `.index` + blob) | -| **Service** | ClusterIP :3000 git-pagesille | -| **Traefik IngressRoute** | Julkaisu (PATCH/PUT + BasicAuth) ja luku (GET/HEAD) eri säännöillä | -| **Traefik Middleware** | `git-pages-publish-auth` (BasicAuth), HTTPS-redirect | -| **cert-manager Certificate** | TLS → Secret `git-pages-tls` | - -| Secret | Rooli | -|--------|-------| -| `git-pages-publish-auth` | htpasswd julkaisuun (Traefik) | -| `git-pages-publish-token` | plaintext token (Gitea Actions -secretiin vietäväksi) | -| `git-pages-retention-gitea` | Gitea PAT branch-tarkistukseen (sidecar) - ---- - -## URL ja sisältö - -Julkinen osoite: - -``` -https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/reports/f4baa286/cucumber/index.html - └────────── selvä URL ─────────┘ └───────────────── Gitea-yhteensopiva polku ─────────────────────────┘ -``` - -Levyllä (apex index-site): - -``` -/app/data/site/{host}/ - .index # Protobuf-manifesti (storage v2 — kaikki tiedostot tässä yhdessä tiedostossa) -``` - -Tiedostot eivät ole flat-FS:nä — katso `implementation-notes.md`. - -Apex-juuri `/` on tyhjä — ei landing-sivua. - ---- - -## Järjestelmäkaavio - -```mermaid -flowchart TB - subgraph ext["Ulkoiset"] - PUB["Julkaisija\n(CI)"] - BR["Selain"] - GITEA["Gitea API\n(branch-lista)"] - end - - subgraph edge["Reuna"] - TRAEFIK["Traefik\nIngressRoute + Middleware"] - CM["cert-manager\nTLS"] - end - - subgraph cluster["git-pages Pod"] - GP["git-pages\n(kontti)"] - RT["retention sidecar\n(kontti)\nHTTP API localhost:3000"] - PVC["PVC /app/data"] - end - - PUB -->|"PATCH/PUT + BasicAuth\ntar"| TRAEFIK - BR -->|"GET/HEAD"| TRAEFIK - TRAEFIK --> GP - CM --> TRAEFIK - GP --> PVC - RT -->|"reads .git-pages/manifest.json\nHTTP localhost"| GP - RT -->|"check branches"| GITEA -``` - ---- - -## Julkaisu - -1. Julkaisija paketoi `{owner}/{repo}/reports/{sha8}/` tar-arkistoksi (sis. `.meta`) -2. `PATCH` tai `PUT` apex-URL:iin (`https://{host}/`) + `Content-Type: application/x-tar` -3. Traefik tarkistaa BasicAuth (`publish` + token) → välittää git-pagesille -4. git-pages kirjoittaa PVC:lle - -Julkaisu kulkee aina julkisen ingressin kautta — ei suoraa ClusterIP-kirjoitusta ulkopuolelta. - ---- - -## Luku - -1. Selain avaa commit-statuslinkin (GET/HEAD) -2. Traefik välittää git-pagesille ilman julkaisu-Middlewarea -3. git-pages palauttaa HTML:n polusta - -Luku-auth (OIDC) ei ole toteutettu — GET/HEAD on julkinen, jos URL tunnetaan. -Katso [design-rationale.md — Luku-auth](design-rationale.md#luku-auth). - ---- - -## Retention - -Sidecar-kontti samassa podissa, ajaa retention-cleanup.sh 24h välein: - -1. Lukee `.git-pages/manifest.json` HTTP:lla localhost:3000 -2. Etsii `.meta`-tiedostot, tarkistaa iän ja branchin -3. **Poistettu branch** — jos `.meta.branch` ei ole Giteassa → whiteout PATCH -4. **Aktiivinen branch** — `maxAgeDays` + `keepMin` (`retention.rules`) -5. Whiteout-tar → PATCH localhost:3000 — poistaa raportit - -Gitea API: `GET /api/v1/repos/{owner}/{repo}/branches/{branch}` — `read:repository` PAT. -Katso [secrets.md](secrets.md). - ---- - -## Rajapinnat - -| Suunta | Protokolla | Auth | Kuvaus | -|--------|------------|------|--------| -| Julkaisija → Traefik | HTTPS PATCH/PUT | BasicAuth `publish` | tar → apex site | -| Selain → Traefik | HTTPS GET/HEAD | — (tänään) | HTML-raportti | -| Sidecar → Gitea | HTTPS GET | PAT `read:repository` | branch-tarkistus per repo | -| Sidecar → git-pages | HTTP :3000 | — (PAGES_INSECURE) | manifestin luku + whiteout PATCH | -| Traefik → git-pages | HTTP :3000 | — | sisäverkko | - -git-pages ei käytä Gitea forge-API:a julkaisuun eikä `pages`-branchia. diff --git a/git-pages/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/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/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..b5e147a --- /dev/null +++ b/gitea-reports/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: git-pages +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 61% rename from git-pages/README.md rename to gitea-reports/README.md index 131fbcf..d177039 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 @@ -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:$GIT_PAGES_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 | `GIT_PAGES_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 84% rename from git-pages/dev-values.yaml rename to gitea-reports/dev-values.yaml index a8372a8..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 diff --git a/gitea-reports/docs/architecture.md b/gitea-reports/docs/architecture.md new file mode 100644 index 0000000..0db7db9 --- /dev/null +++ b/gitea-reports/docs/architecture.md @@ -0,0 +1,82 @@ +# Architecture — Nginx + upload-sidecar + +> git-pages 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** | `git-pages-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 100% rename from git-pages/docs/design-rationale.md rename to gitea-reports/docs/design-rationale.md 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/git-pages/docs/secrets.md b/gitea-reports/docs/secrets.md similarity index 84% rename from git-pages/docs/secrets.md rename to gitea-reports/docs/secrets.md index 054eac0..e86394e 100644 --- a/git-pages/docs/secrets.md +++ b/gitea-reports/docs/secrets.md @@ -40,7 +40,7 @@ echo "$GIT_PAGES_PUBLISH_TOKEN" ### Vaihe 4: Luo K8s secrets ```bash -NS=git-pages +NS=gitea-reports # 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin) kubectl create secret generic git-pages-publish-auth \ @@ -110,20 +110,23 @@ sequenceDiagram 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 + 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 / + BasicAuth
publish:TOKEN + repo-url + Actions->>Traefik: PUT /{polku} + BasicAuth
publish:TOKEN + tar.gz Traefik->>K8sAuth: lue users (htpasswd) K8sAuth-->>Traefik: publish:$apr1$... alt Token match - Traefik->>GP: välitä - GP-->>Traefik: 200 OK - Traefik-->>Actions: 200 OK + 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 @@ -141,11 +144,14 @@ sequenceDiagram sequenceDiagram participant Browser as Selain participant Traefik as Traefik - participant GP as git-pages + participant Nginx as nginx + participant PV as PVC /app/data - Browser->>Traefik: GET /OWNER/REPO/commit/SHA/raportti/index.html - Traefik->>GP: välitä (ei authia) - GP-->>Traefik: HTML + 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 ``` @@ -157,26 +163,25 @@ GET/HEAD-reitillä ei ole Middlewarea. Luku on julkinen, jos URL tunnetaan. ```mermaid sequenceDiagram - participant Sidecar as Retention Sidecar + participant CronJob as Retention CronJob participant K8sSecret as K8s Secret
git-pages-retention-gitea participant GiteaAPI as Gitea API - participant GP as git-pages (localhost:3000) + participant PV as PVC /app/data - Note over Sidecar: 1. Lue PAT - Sidecar->>K8sSecret: lue token - K8sSecret-->>Sidecar: Gitea PAT + Note over CronJob: 1. Lue PAT + CronJob->>K8sSecret: lue token + K8sSecret-->>CronJob: Gitea PAT - Note over Sidecar: 2. Lue manifest - Sidecar->>GP: GET .git-pages/manifest.json - GP-->>Sidecar: sisällysluettelo + Note over CronJob: 2. Lue PV suoraan + CronJob->>PV: ls /app/data/{owner}/{repo}/{branch}/ + PV-->>CronJob: commit-kansiot - Note over Sidecar: 3. Kysy branch - Sidecar->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH - GiteaAPI-->>Sidecar: 200 / 404 + Note over CronJob: 3. Kysy branch + CronJob->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH + GiteaAPI-->>CronJob: 200 / 404 - Note over Sidecar: 4. Luo whiteout-tar + PATCH - Sidecar->>GP: PATCH / (whiteout) - GP-->>Sidecar: 200 OK + Note over CronJob: 4. Poista vanhat + CronJob->>PV: rm -rf vanhat commit-kansiot ``` **Huomio:** Retention-PAT:in omistajalla on oltava lukuoikeus KAIKKIIN repoihin, @@ -195,7 +200,7 @@ joista raportteja on PVC:llä. GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)" # 2. Päivitä K8s secrets (molemmat) - NS=git-pages + NS=gitea-reports kubectl delete secret git-pages-publish-auth -n "$NS" kubectl delete secret git-pages-publish-token -n "$NS" @@ -216,7 +221,7 @@ Jos repoja on monta, voit käyttää Gitea API:ta (vaatii admin-tokenin): ```bash ADMIN_TOKEN="" -NS=git-pages +NS=gitea-reports # Lue plaintext-token erillisestä secretistä TOKEN=$(kubectl get secret git-pages-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d) diff --git a/git-pages/docs/tech-stack.md b/gitea-reports/docs/tech-stack.md similarity index 96% rename from git-pages/docs/tech-stack.md rename to gitea-reports/docs/tech-stack.md index bc3f099..f1b9982 100644 --- a/git-pages/docs/tech-stack.md +++ b/gitea-reports/docs/tech-stack.md @@ -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` --- 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/git-pages/templates/NOTES.txt b/gitea-reports/templates/NOTES.txt similarity index 51% rename from git-pages/templates/NOTES.txt rename to gitea-reports/templates/NOTES.txt index 61f07ec..9365218 100644 --- a/git-pages/templates/NOTES.txt +++ b/gitea-reports/templates/NOTES.txt @@ -1,16 +1,19 @@ -git-pages installed. +Nginx + upload-sidecar 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 + helm upgrade --install git-pages ./git-pages -n {{ .Release.Namespace }} --create-namespace -f dev-values.yaml Host: https://{{ .Values.ingress.host }} -Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/reports/{sha8}/index.html +Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/{branch}/{sha8}/{suite}/ Publish (CI): - PATCH https://{{ .Values.ingress.host }}/ - Authorization: Basic publish: + 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 }} ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} diff --git a/git-pages/templates/_helpers.tpl b/gitea-reports/templates/_helpers.tpl similarity index 100% rename from git-pages/templates/_helpers.tpl rename to gitea-reports/templates/_helpers.tpl diff --git a/git-pages/templates/certificate.yaml b/gitea-reports/templates/certificate.yaml similarity index 100% rename from git-pages/templates/certificate.yaml rename to gitea-reports/templates/certificate.yaml diff --git a/gitea-reports/templates/configmap.yaml b/gitea-reports/templates/configmap.yaml new file mode 100644 index 0000000..d7aeebb --- /dev/null +++ b/gitea-reports/templates/configmap.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "git-pages.fullname" . }}-config + labels: + {{- include "git-pages.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 + + PORT = int(os.environ.get("UPLOAD_PORT", "8081")) + DATA_ROOT = "/app/data" + + class Handler(http.server.BaseHTTPRequestHandler): + def do_PUT(self): + dest = DATA_ROOT + self.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..e460f5b --- /dev/null +++ b/gitea-reports/templates/deployment.yaml @@ -0,0 +1,75 @@ +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: 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 "git-pages.fullname" . }}-config + items: + - key: default.conf + path: default.conf + - name: upload-script + configMap: + name: {{ include "git-pages.fullname" . }}-config + defaultMode: 0755 + items: + - key: upload-server.py + path: upload-server.py + {{- if .Values.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ include "git-pages.fullname" . }}-data + {{- end }} diff --git a/git-pages/templates/ingressroute.yaml b/gitea-reports/templates/ingressroute.yaml similarity index 91% rename from git-pages/templates/ingressroute.yaml rename to gitea-reports/templates/ingressroute.yaml index efb19b6..ca255c9 100644 --- a/git-pages/templates/ingressroute.yaml +++ b/gitea-reports/templates/ingressroute.yaml @@ -17,12 +17,12 @@ spec: - name: {{ include "git-pages.fullname" . }}-publish-auth services: - name: {{ include "git-pages.fullname" . }} - port: {{ .Values.service.port }} + 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 }} + port: {{ .Values.nginx.port }} tls: secretName: git-pages-tls --- @@ -44,5 +44,5 @@ spec: - name: {{ include "git-pages.fullname" . }}-https-redirect services: - name: {{ include "git-pages.fullname" . }} - port: {{ .Values.service.port }} + port: {{ .Values.nginx.port }} {{- end }} diff --git a/git-pages/templates/middleware.yaml b/gitea-reports/templates/middleware.yaml similarity index 100% rename from git-pages/templates/middleware.yaml rename to gitea-reports/templates/middleware.yaml diff --git a/git-pages/templates/publish-auth-secret.yaml b/gitea-reports/templates/publish-auth-secret.yaml similarity index 100% rename from git-pages/templates/publish-auth-secret.yaml rename to gitea-reports/templates/publish-auth-secret.yaml diff --git a/git-pages/templates/pvc.yaml b/gitea-reports/templates/pvc.yaml similarity index 100% rename from git-pages/templates/pvc.yaml rename to gitea-reports/templates/pvc.yaml diff --git a/git-pages/templates/retention-configmap.yaml b/gitea-reports/templates/retention-configmap.yaml similarity index 100% rename from git-pages/templates/retention-configmap.yaml rename to gitea-reports/templates/retention-configmap.yaml diff --git a/git-pages/templates/retention-cronjob.yaml b/gitea-reports/templates/retention-cronjob.yaml similarity index 86% rename from git-pages/templates/retention-cronjob.yaml rename to gitea-reports/templates/retention-cronjob.yaml index ce24de6..2817611 100644 --- a/git-pages/templates/retention-cronjob.yaml +++ b/gitea-reports/templates/retention-cronjob.yaml @@ -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 diff --git a/git-pages/templates/retention-rbac.yaml b/gitea-reports/templates/retention-rbac.yaml similarity index 100% rename from git-pages/templates/retention-rbac.yaml rename to gitea-reports/templates/retention-rbac.yaml diff --git a/git-pages/templates/service.yaml b/gitea-reports/templates/service.yaml similarity index 60% rename from git-pages/templates/service.yaml rename to gitea-reports/templates/service.yaml index 91d0da1..15dfd6f 100644 --- a/git-pages/templates/service.yaml +++ b/gitea-reports/templates/service.yaml @@ -9,7 +9,11 @@ spec: selector: {{- include "git-pages.selectorLabels" . | nindent 4 }} ports: - - name: http - port: {{ .Values.service.port }} - targetPort: http + - 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/publish-git-pages.sh b/scripts/publish-git-pages.sh index 4575bc1..082e0f3 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 "${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/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