Compare commits

..

38 Commits

Author SHA1 Message Date
niko 535a5fed0e feature/docker-build-arg (#50)
CI Main / Config load (push) Successful in 16s
CI Main / Latest versio (push) Successful in 15s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m12s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m19s
ci-docker-build-push Docker push 0.2.37
CI Main / Build & Push Docker (push) Successful in 37s
gitops/gitea-ci-library GitOps: 0.2.37
CI Main / GitOps (push) Successful in 34s
CI Main / Report Summary (push) Successful in 4s
CI Main / Move provider version tag (push) Successful in 10s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #50
2026-06-28 13:05:48 +03:00
niko f3abd42f17 Fix/git pages gitea reports 1 (#49)
CI Main / Config load (push) Successful in 23s
CI Gitea Reports Main / Config load (push) Successful in 23s
CI Gitea Reports Main / Latest version (push) Successful in 20s
CI Main / Latest versio (push) Successful in 21s
ci-helm-build-push Helm push 0.2.1
CI Gitea Reports Main / Build & Push Helm chart (push) Successful in 41s
unit-tests Bats test report
acc-tests Cucumber test report
CI Gitea Reports Main / Report Summary (push) Successful in 3s
ci-docker-build-push Docker push 0.2.36
CI Main / Build & Push Docker (push) Successful in 32s
CI Main / Report Summary (push) Successful in 3s
CI Main / Move provider version tag (push) Successful in 10s
gitops/gitea-ci-library/gitea-reports GitOps: gitea-reports 0.2.1
CI Gitea Reports Main / GitOps (push) Successful in 33s
CI Main / Cucumber tests (push) Successful in 1m25s
CI Main / Bats tests (push) Successful in 1m26s
gitops/gitea-ci-library GitOps: 0.2.36
CI Main / GitOps (push) Successful in 32s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #49
2026-06-28 09:25:41 +03:00
niko bd6ed5c2c2 Feature/git pages -> oma nginx perusteinen raporointipalvelu (#48)
CI Gitea Reports Main / Config load (push) Successful in 17s
CI Main / Config load (push) Successful in 20s
CI Gitea Reports Main / Latest version (push) Successful in 17s
CI Main / Latest versio (push) Successful in 19s
ci-helm-build-push Helm push 0.2.0
CI Gitea Reports Main / Build & Push Helm chart (push) Successful in 33s
gitops/gitea-ci-library/gitea-reports GitOps: gitea-reports 0.2.0
CI Gitea Reports Main / GitOps (push) Successful in 33s
CI Gitea Reports Main / Report Summary (push) Successful in 3s
CI Main / Bats tests (push) Failing after 1m16s
CI Main / Cucumber tests (push) Failing after 1m22s
CI Main / Build & Push Docker (push) Has been skipped
CI Main / GitOps (push) Has been skipped
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 3s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #48
2026-06-28 09:10:10 +03:00
niko dd9cdf70b6 Featuer/gitops extra command (#47)
CI Main / Config load (push) Successful in 1m48s
CI Main / Latest versio (push) Successful in 1m38s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 2m15s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 2m46s
ci-docker-build-push Docker push 0.2.35
CI Main / Build & Push Docker (push) Successful in 40s
gitops/gitea-ci-library GitOps: 0.2.35
CI Main / GitOps (push) Successful in 1m34s
CI Main / Report Summary (push) Successful in 3s
CI Main / Move provider version tag (push) Successful in 11s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #47
2026-06-27 05:20:09 +03:00
niko 848ba723e4 Fix/git pages rentetin create tests (#46)
CI Main / Config load (push) Successful in 3m12s
CI Git-Pages Main / Config load (push) Successful in 3m4s
CI Main / Latest versio (push) Successful in 30s
CI Git-Pages Main / Latest version (push) Successful in 27s
ci-helm-build-push Helm push 0.1.9
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m56s
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 1m19s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 3m47s
ci-docker-build-push Docker push 0.2.34
CI Main / Build & Push Docker (push) Successful in 1m14s
gitops/gitea-ci-library/git-pages GitOps: git-pages 0.1.9
CI Git-Pages Main / GitOps (push) Successful in 3m37s
gitops/gitea-ci-library GitOps: 0.2.34
CI Main / GitOps (push) Successful in 40s
CI Main / Report Summary (push) Successful in 4s
CI Main / Move provider version tag (push) Successful in 12s
CI Git-Pages Main / Report Summary (push) Successful in 4s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #46
2026-06-26 08:07:02 +03:00
niko 1978a995a8 Update README.md (#45)
CI Main / Config load (push) Successful in 23s
CI Main / Latest versio (push) Successful in 21s
CI Main / Bats tests (push) Successful in 1m34s
CI Main / Cucumber tests (push) Successful in 1m33s
ci-docker-build-push Docker push 0.2.33
CI Main / Build & Push Docker (push) Successful in 50s
gitops/gitea-ci-library GitOps: 0.2.33
CI Main / GitOps (push) Successful in 47s
CI Main / Report Summary (push) Successful in 7s
CI Main / Move provider version tag (push) Successful in 14s
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 1m5s
unit-tests Bats test report
CI Feature / Bats tests (push) Successful in 2m9s
acc-tests Cucumber test report
CI Feature / Cucumber tests (push) Successful in 2m53s
CI Feature / Report Summary (push) Successful in 7s
Reviewed-on: #45
2026-06-25 09:19:01 +03:00
niko d6343438a3 Feture/gitops7 (#44)
unit-tests Bats test report
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m44s
ci-docker-build-push Docker push 0.2.32
gitops/gitea-ci-library GitOps: 0.2.32
CI Main / Config load (push) Successful in 26s
CI Main / Latest versio (push) Successful in 21s
CI Main / Build & Push Docker (push) Has been skipped
CI Main / GitOps (push) Has been skipped
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 5s
CI Main / Bats tests (push) Has been skipped
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #44
2026-06-23 12:45:42 +03:00
niko ed2703b7d7 commit status kutsujalle gitops repoon (#43)
CI Main / Config load (push) Successful in 19s
CI Main / Latest versio (push) Successful in 19s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m33s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m45s
ci-docker-build-push Docker push 0.2.31
CI Main / Build & Push Docker (push) Successful in 44s
gitops/gitea-ci-library GitOps: 0.2.31
CI Main / GitOps (push) Successful in 35s
CI Main / Report Summary (push) Successful in 7s
CI Main / Move provider version tag (push) Successful in 13s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #43
2026-06-22 14:24:25 +03:00
niko dc4b331ea1 kutsujalle gitops commit (#42)
CI Main / Config load (push) Successful in 26s
CI Git-Pages Main / Latest version (push) Successful in 22s
CI Git-Pages Main / Config load (push) Successful in 25s
CI Main / Latest versio (push) Successful in 21s
ci-helm-build-push Helm push 0.1.8
CI Main / Bats tests (push) Successful in 1m38s
CI Git-Pages Main / GitOps (push) Successful in 1m24s
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 46s
unit-tests Bats test report
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m59s
CI Git-Pages Main / Report Summary (push) Successful in 9s
ci-docker-build-push Docker push 0.2.30
CI Main / Build & Push Docker (push) Successful in 50s
CI Main / Move provider version tag (push) Successful in 14s
CI Main / GitOps (push) Successful in 41s
CI Main / Report Summary (push) Successful in 6s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #42
2026-06-22 13:57:14 +03:00
niko c06015cd9f poc logiikka takaisin (#41)
CI Main / Config load (push) Successful in 19s
CI Main / Latest versio (push) Successful in 17s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m30s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m43s
ci-docker-build-push Docker push 0.2.29
CI Main / Build & Push Docker (push) Successful in 44s
CI Main / GitOps (push) Successful in 39s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 15s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #41
2026-06-22 13:24:09 +03:00
niko 4c73433eab Update scripts/gitops-dispatch.sh (#40)
CI Main / Config load (push) Successful in 21s
CI Main / Latest versio (push) Successful in 19s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m23s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m43s
ci-docker-build-push Docker push 0.2.28
CI Main / Build & Push Docker (push) Successful in 33s
CI Main / GitOps (push) Failing after 18s
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 7s
Reviewed-on: #40
2026-06-22 11:19:56 +03:00
niko b9de1da855 testiä (#39)
ci-helm-build-push Helm push 0.1.7
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 51s
CI Git-Pages Main / Report Summary (push) Successful in 6s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m40s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m48s
CI Git-Pages Main / Config load (push) Successful in 22s
CI Main / Config load (push) Successful in 24s
CI Main / Latest versio (push) Successful in 25s
CI Git-Pages Main / Latest version (push) Successful in 24s
CI Git-Pages Main / GitOps (push) Failing after 20s
ci-docker-build-push Docker push 0.2.27
CI Main / Build & Push Docker (push) Successful in 41s
CI Main / GitOps (push) Failing after 18s
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 6s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #39
2026-06-22 11:14:50 +03:00
niko 9f9af398cc clean (#38)
CI Git-Pages Main / Config load (push) Successful in 24s
CI Main / Config load (push) Successful in 26s
CI Git-Pages Main / Latest version (push) Successful in 22s
CI Main / Latest versio (push) Successful in 23s
ci-helm-build-push Helm push 0.1.6
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 50s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m44s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m58s
ci-docker-build-push Docker push 0.2.26
CI Main / Build & Push Docker (push) Successful in 43s
CI Main / GitOps (push) Failing after 21s
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 6s
CI Git-Pages Main / GitOps (push) Failing after 18s
CI Git-Pages Main / Report Summary (push) Successful in 5s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #38
2026-06-22 10:55:58 +03:00
niko bc6bb78973 Feature/gitops (#37)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 34s
CI Git-Pages Main / Load git-pages.gitea-env.conf to pipeline env (push) Successful in 34s
CI Main / Check existing artifact (push) Successful in 22s
CI Git-Pages Main / Check existing artifact (push) Successful in 21s
ci-helm-build-push Helm push 0.1.5
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 48s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m34s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m45s
CI Git-Pages Main / Update chart to the cluster (push) Failing after 0s
ci-docker-build-push Docker push 0.2.25
CI Git-Pages Main / Report Summary (push) Successful in 7s
CI Main / Build & Push Docker (push) Successful in 44s
CI Main / GitOps (push) Failing after 22s
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 6s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #37
2026-06-22 10:37:15 +03:00
niko a5947551d4 Fix/gitea commit status naming (#36)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 22s
CI Main / Check existing artifact (push) Successful in 20s
CI Git-Pages Main / Load git-pages.gitea-env.conf to pipeline env (push) Successful in 45s
CI Git-Pages Main / Check existing artifact (push) Successful in 31s
ci-helm-build-push Helm push 0.1.4
CI Main / Cucumber tests (push) Successful in 1m10s
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 49s
CI Git-Pages Main / Report Summary (push) Successful in 5s
CI Main / Bats tests (push) Successful in 1m34s
ci-docker-build-push Docker push 0.2.21
CI Main / Build & Push Docker (push) Successful in 48s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 14s
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 33s
acc-tests Cucumber test report
CI Feature / Cucumber tests (push) Successful in 1m23s
unit-tests Bats test report
CI Feature / Bats tests (push) Successful in 1m34s
CI Feature / Report Summary (push) Successful in 5s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #36
2026-06-21 08:43:32 +03:00
niko 4910565547 docker ja helm build context root monorepo ja in folder build (#35)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 25s
CI Git-Pages Main / Load git-pages.gitea-env.conf to pipeline env (push) Successful in 25s
CI Main / Check existing artifact (push) Successful in 24s
CI Git-Pages Main / Check existing artifact (push) Successful in 23s
acc-tests Cucumber test report
ci-helm-build-push Helm chart 0.1.3
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m34s
CI Main / Cucumber tests (push) Successful in 1m33s
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 47s
CI Git-Pages Main / Report Summary (push) Successful in 7s
ci-docker-build-push Docker build & push 0.2.20 OK
CI Main / Build & Push Docker (push) Successful in 51s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 14s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #35
2026-06-21 06:08:22 +03:00
niko 2bef079d03 Fix/ci kontin no internet vaatimus (#34)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 22s
CI Main / Check existing artifact (push) Successful in 18s
acc-tests Cucumber test report
unit-tests Bats test report
CI Main / Cucumber tests (push) Successful in 1m25s
CI Main / Bats tests (push) Successful in 1m26s
ci-docker-build-push Docker build & push 0.2.19 OK
CI Main / Build & Push Docker (push) Successful in 47s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 16s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #34
2026-06-20 14:36:42 +03:00
niko 4f20f5ae2f estetään image karkaaminen docker.io kun parametria ei ole asetettu (#33)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 28s
CI Main / Check existing artifact (push) Successful in 21s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 57s
unit-tests Bats test report
CI Main / Bats tests (push) Failing after 2m13s
CI Main / Build & Push Docker (push) Has been skipped
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 6s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #33
2026-06-20 10:54:23 +03:00
niko bd93ef2f8f monorepo ci filter ohje tarkennus (#32)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 26s
CI Main / Check existing artifact (push) Successful in 23s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 54s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m52s
ci-docker-build-push Docker build & push 0.2.18 OK
CI Main / Build & Push Docker (push) Successful in 43s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 14s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #32
2026-06-19 14:13:14 +03:00
niko f06cb112d8 Fix/umbrella chart support (#31)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 20s
CI Main / Check existing artifact (push) Successful in 17s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m4s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m52s
ci-docker-build-push Docker build & push 0.2.17 OK
CI Main / Build & Push Docker (push) Successful in 41s
CI Main / Report Summary (push) Successful in 5s
CI Main / Move provider version tag (push) Successful in 13s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #31
2026-06-19 13:13:06 +03:00
niko d57f56f196 dependency update ennen paketointia (#30)
CI Git-Pages Main / Load git-pages.gitea-env.conf to pipeline env (push) Successful in 22s
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 24s
CI Git-Pages Main / Check existing artifact (push) Successful in 22s
CI Main / Check existing artifact (push) Successful in 22s
ci-helm-build-push Helm chart 0.1.2
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 42s
CI Git-Pages Main / Report Summary (push) Successful in 5s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m11s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m55s
ci-docker-build-push Docker build & push 0.2.16 OK
CI Main / Build & Push Docker (push) Successful in 31s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 12s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #30
2026-06-19 10:32:40 +03:00
niko 277c0f882d Update .gitea/workflows/git-pages.ci-main.yml (#29)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 21s
CI Git-Pages Main / Load git-pages.gitea-env.conf to pipeline env (push) Successful in 21s
CI Main / Check existing artifact (push) Successful in 19s
CI Git-Pages Main / Check existing artifact (push) Successful in 21s
ci-helm-build-push Helm chart 0.1.1
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 46s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m12s
CI Git-Pages Main / Report Summary (push) Successful in 8s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 2m0s
ci-docker-build-push Docker build & push 0.2.15 OK
CI Main / Build & Push Docker (push) Successful in 42s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 14s
poistettu dev haaran trigger

Reviewed-on: #29
2026-06-19 10:14:32 +03:00
niko cc7f4f0976 ohjeita helm build-push (#28)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 21s
CI Main / Check existing artifact (push) Successful in 18s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m16s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 2m4s
ci-docker-build-push Docker build & push 0.2.15 OK
CI Main / Build & Push Docker (push) Successful in 54s
CI Main / Report Summary (push) Successful in 5s
CI Main / Move provider version tag (push) Successful in 15s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #28
2026-06-19 10:13:24 +03:00
niko 6621b3f336 Fix/helm build node (#27)
CI Git-Pages Main / Load git-pages.gitea-env.conf to pipeline env (push) Successful in 22s
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 23s
CI Main / Check existing artifact (push) Successful in 21s
CI Git-Pages Main / Check existing artifact (push) Successful in 20s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 52s
ci-helm-build-push Helm chart 0.1.0
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 43s
CI Git-Pages Main / Report Summary (push) Successful in 4s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m51s
ci-docker-build-push Docker build & push 0.2.14 OK
CI Main / Build & Push Docker (push) Successful in 31s
CI Main / Report Summary (push) Successful in 3s
CI Main / Move provider version tag (push) Successful in 11s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #27
2026-06-19 08:48:56 +03:00
niko c0012ba6fa Feature/helm chart (#26)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 23s
CI Git-Pages Main / Load git-pages.gitea-env.conf to pipeline env (push) Successful in 22s
CI Main / Check existing artifact (push) Successful in 22s
CI Git-Pages Main / Check existing artifact (push) Successful in 23s
CI Git-Pages Main / Build & Push Helm chart (push) Failing after 21s
CI Git-Pages Main / Report Summary (push) Successful in 6s
CI Main / Cucumber tests (push) Successful in 1m13s
CI Main / Bats tests (push) Successful in 2m1s
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 27s
ci-docker-build-push Docker build & push 0.2.13 OK
CI Main / Build & Push Docker (push) Successful in 53s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 16s
acc-tests Cucumber test report
CI Feature / Cucumber tests (push) Successful in 1m17s
unit-tests Bats test report
CI Feature / Bats tests (push) Successful in 1m59s
CI Feature / Report Summary (push) Successful in 5s
riippuvuudet haetaan main haarasta, ei etene ennen kuin on main haarassa nämä muutokset

---------

Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #26
2026-06-19 08:35:52 +03:00
niko 908aee9f8b job status parametrin käyttö (#25)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 25s
CI Main / Check existing artifact (push) Successful in 14s
CI Main / Cucumber tests (push) Successful in 1m11s
CI Main / Bats tests (push) Successful in 1m41s
ci-docker-build-push Docker build & push 0.2.12 OK
CI Main / Build & Push Docker (push) Successful in 44s
CI Main / Report Summary (push) Successful in 7s
CI Main / Move provider version tag (push) Successful in 17s
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 22s
acc-tests Cucumber test report
CI Feature / Cucumber tests (push) Successful in 58s
unit-tests Bats test report
CI Feature / Bats tests (push) Successful in 1m27s
CI Feature / Report Summary (push) Successful in 4s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #25
2026-06-19 02:56:37 +03:00
niko ad4c2cd570 Fix/ci reports fail param (#24)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 23s
CI Main / Check existing artifact (push) Successful in 14s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m11s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m25s
ci-docker-build-push Docker build & push 0.2.11 OK
CI Main / Build & Push Docker (push) Successful in 32s
CI Main / Report Summary (push) Successful in 7s
CI Main / Move provider version tag (push) Successful in 14s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #24
2026-06-18 16:19:45 +03:00
niko 1e10633e60 Fix/uusi consumer kontti taktiikka (#23)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 17s
CI Main / Check existing artifact (push) Successful in 12s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 49s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m19s
ci-docker-build-push Docker build & push 0.2.10 OK
CI Main / Build & Push Docker (push) Successful in 35s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 13s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #23
2026-06-18 13:54:38 +03:00
niko 737b2fc3f2 Feature/yksinkertaistetaan raportointi logiikkaa (#22)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 18s
CI Main / Check existing artifact (push) Successful in 12s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 2m3s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m21s
ci-docker-build-push Docker build & push 0.2.9 OK
CI Main / Build & Push Docker (push) Successful in 40s
CI Main / Report Summary (push) Successful in 5s
CI Main / Move provider version tag (push) Successful in 12s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #22
2026-06-18 12:55:02 +03:00
niko 65d385f9b9 skill päivitetty (#21)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 17s
CI Main / Check existing artifact (push) Successful in 13s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 1m35s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Successful in 54s
ci-docker-build-push Docker build & push 0.2.8 OK
CI Main / Build & Push Docker (push) Successful in 40s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 13s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #21
2026-06-17 17:02:44 +03:00
niko 2e5a4dca3a Fix/ci container manual start (#20)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 21s
CI Main / Check existing artifact (push) Successful in 13s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 1m34s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Successful in 51s
ci-docker-build-push Docker build & push 0.2.7 OK
CI Main / Build & Push Docker (push) Successful in 41s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 12s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #20
2026-06-17 14:19:05 +03:00
niko 29fde14445 Fix/fragile test (#19)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 17s
CI Main / Check existing artifact (push) Successful in 12s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 2m41s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Successful in 1m44s
ci-docker-build-push Docker build & push 0.2.6 OK
CI Main / Build & Push Docker (push) Successful in 38s
CI Main / Report Summary (push) Successful in 5s
CI Main / Move provider version tag (push) Successful in 12s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #19
2026-06-17 07:41:01 +03:00
niko f17ea7936e Feature/monorepo support (#18)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 19s
CI Main / Check existing artifact (push) Successful in 12s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 1m35s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Successful in 46s
ci-docker-build-push Docker build & push 0.2.6 OK
CI Main / Build & Push Docker (push) Successful in 42s
CI Main / Report Summary (push) Successful in 4s
CI Main / Move provider version tag (push) Successful in 13s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #18
2026-06-17 07:40:24 +03:00
niko 153205be40 cucumber kontin konf (#17)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 18s
CI Main / Check existing artifact (push) Successful in 12s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 1m36s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Successful in 48s
ci-docker-build-push Docker build & push 0.2.5 OK
CI Main / Build & Push Docker (push) Successful in 31s
CI Main / Report Summary (push) Successful in 3s
CI Main / Move provider version tag (push) Successful in 10s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #17
2026-06-16 13:24:29 +03:00
niko f3a54d6ed3 käytetään omia kontteja, eikä rakenneta lennosta! (#16)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 20s
CI Main / Check existing artifact (push) Successful in 11s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 1m37s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Failing after 39s
CI Main / Build & Push Docker (push) Has been skipped
CI Main / Report Summary (push) Successful in 5s
CI Main / Move provider version tag (push) Has been skipped
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #16
2026-06-16 10:17:53 +03:00
niko 8622b6fc4e Feature/ci container (#15)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 19s
CI Main / Check existing artifact (push) Successful in 14s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 1m43s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Successful in 1m9s
ci-docker-build-push Docker build & push 0.2.4 OK
CI Main / Build & Push Docker (push) Successful in 37s
CI Main / Report Summary (push) Successful in 5s
CI Main / Move provider version tag (push) Successful in 13s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #15
2026-06-16 09:13:36 +03:00
niko dd9ad9e2c8 Feature/ci container (#14)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 21s
CI Main / Check existing artifact (push) Successful in 14s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 1m46s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Successful in 1m12s
ci-docker-build-push Docker build & push 0.2.3 OK
CI Main / Build & Push Docker (push) Successful in 36s
CI Main / Report Summary (push) Successful in 5s
CI Main / Move provider version tag (push) Successful in 12s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #14
2026-06-16 07:16:47 +03:00
niko ffc0734b65 tägitys käyttöön consumer viittauksessa provider tiedostoihin (#13)
CI Main / Load example-gitea-env.conf to pipeline env (push) Successful in 19s
CI Main / Check existing artifact (push) Successful in 12s
unit-tests Link to Bats reports
CI Main / Bats tests (push) Successful in 1m44s
acc-tests Link to Cucumber reports
CI Main / Cucumber tests (push) Successful in 1m31s
ci-docker-build-push Docker build & push 0.2.2 OK
CI Main / Build & Push Docker (push) Successful in 54s
CI Main / Report Summary (push) Successful in 13s
CI Main / Move provider version tag (push) Successful in 13s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #13
2026-06-16 06:07:40 +03:00
115 changed files with 5851 additions and 2189 deletions
+20
View File
@@ -0,0 +1,20 @@
.git/
.gitignore
node_modules/
reports/
coverage/
.ai/
.cursor/
.vscode/
tmp/
.DS_Store
*.md
docs/
guides/
.simplecov
cucumber.js
package-lock.json
package.json
CURRENT_PROVIDER_VERSION
README.md
AGENTS.md
+20 -28
View File
@@ -1,37 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
WORKSPACE_VOLUME="${1:-}"
REPORT_DIR="${2:-}"
REPORT_DIR="${1:-}"
COVERAGE_DIR="${REPORT_DIR}/coverage"
[ -n "$WORKSPACE_VOLUME" ] || { echo "ERROR: workspace volume name required" >&2; exit 1; }
[ -n "$REPORT_DIR" ] || { echo "ERROR: report directory required" >&2; exit 1; }
HAS_COVERAGE=false
COVERAGE_SRC=""
if docker run --rm -v "$WORKSPACE_VOLUME":/data alpine sh -c '[ -d /data/coverage ] && ls -A /data/coverage | grep -q .' 2>/dev/null; then
COVERAGE_SRC="/data/coverage"
if [ -d coverage ]; then
mkdir -p "$COVERAGE_DIR"
cp -a coverage/. "$COVERAGE_DIR/"
fi
if [ -n "$COVERAGE_SRC" ]; then
mkdir -p "$REPORT_DIR/coverage"
docker run --rm -v "$WORKSPACE_VOLUME":/data alpine tar c -C "$COVERAGE_SRC" . | tar x -C "$REPORT_DIR/coverage"
HAS_COVERAGE=true
if [ -d "$COVERAGE_DIR" ] && [ ! -f "$COVERAGE_DIR/index.html" ]; then
SHA8="${GITHUB_SHA:0:8}"
{
echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
echo "<title>Coverage report ${SHA8}</title>"
echo '<style>body{font-family:sans-serif;margin:2em}h1{color:#1e293b}ul{list-style:none;padding:0}li{margin:.5em 0}a{color:#2563eb}</style>'
echo "</head><body><h1>Coverage report <code>${SHA8}</code></h1><ul>"
while IFS= read -r -d '' f; do
base=$(basename "$f")
name="${base%.*}"
name="${name//-/ }"
echo "<li><a href=\"${base}\">${name^}</a></li>"
done < <(find "$COVERAGE_DIR" -maxdepth 1 -type f \( -name '*.html' -o -name '*.txt' \) ! -name index.html -print0 2>/dev/null || true)
echo '</ul></body></html>'
} > "$COVERAGE_DIR/index.html"
fi
cat > "$REPORT_DIR/index.html" << EOF
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
<title>Bats report ${GITHUB_SHA:0:8}</title>
<style>body{font-family:sans-serif;margin:2em;max-width:960px}
h1{color:#1e293b}a{color:#2563eb;text-decoration:none}a:hover{text-decoration:underline}
</style></head><body>
<h1>Bats report <code>${GITHUB_SHA:0:8}</code></h1>
<ul>
<li><a href="test-report.html">Test results</a></li>
EOF
if [ "$HAS_COVERAGE" = true ]; then
echo '<li><a href="coverage/index.html">Coverage report</a></li>' >> "$REPORT_DIR/index.html"
fi
echo '</ul></body></html>' >> "$REPORT_DIR/index.html"
+11 -37
View File
@@ -16,6 +16,8 @@ on:
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
VERSION_FILE: ${{ fromJson(inputs.env_json).VERSION_FILE || '' }}
jobs:
check:
@@ -25,45 +27,17 @@ jobs:
version: ${{ steps.set-outputs.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Check existing artifact and calculate version
run: |
if [ -f VERSION ]; then
RAW_VERSION=$(cat VERSION | tr -d '[:space:]')
elif [ -f package.json ]; then
RAW_VERSION=$(jq -r '.version' package.json)
elif [ -f pom.xml ]; then
RAW_VERSION=$(grep -oP '<version>\K[^<]+' pom.xml | head -1)
else
echo "ERROR: No VERSION file, package.json, or pom.xml found" >&2
exit 1
fi
BASE_VERSION=$(echo "$RAW_VERSION" | cut -d'.' -f1-2)
echo "gitea-ci-library - Tunnistettu Major.Minor versio: $BASE_VERSION"
TAGS_JSON=$(curl -s -f -H "Authorization: token $GITEA_TOKEN" \
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/tags")
TAG=$(echo "$TAGS_JSON" | jq -r 'if type == "array" then .[] | select(.commit.sha == "${{ github.sha }}") | .name else empty end' | head -1)
mkdir -p /tmp/build-ctx
if [ -n "$TAG" ]; then
echo "ARTIFACT_EXISTS=true" > /tmp/build-ctx/build.env
echo "NEXT_VERSION=$TAG" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Artefakti löytyi jo tagilla: $TAG."
else
echo "ARTIFACT_EXISTS=false" > /tmp/build-ctx/build.env
HIGHEST_PATCH=$(echo "$TAGS_JSON" | jq -r --arg bv "$BASE_VERSION." '
if type == "array" then .[] | .name | select(startswith($bv)) | sub($bv; "") | tonumber else empty end' | sort -rn | head -1)
if [ -z "$HIGHEST_PATCH" ]; then NEXT_PATCH=0; else NEXT_PATCH=$((HIGHEST_PATCH + 1)); fi
FULL_VERSION="${BASE_VERSION}.${NEXT_PATCH}"
echo "NEXT_VERSION=$FULL_VERSION" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Uusi vapaa versio: $FULL_VERSION"
fi
env:
SERVER_URL: ${{ gitea.server_url }}
REPO: ${{ github.repository }}
SHA: ${{ github.sha }}
run: bash .ci/scripts/check-version.sh
- name: Set job outputs
id: set-outputs
@@ -0,0 +1,59 @@
name: CI Container Build & Push
on:
workflow_call:
inputs:
env_json:
required: true
type: string
dockerfile_path:
required: true
type: string
image_name:
required: true
type: string
tag:
required: false
type: string
default: 'latest'
secrets:
DOCKER_USERNAME:
required: false
DOCKER_PASSWORD:
required: true
jobs:
build-push:
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Build and push container
env:
DOCKER_REGISTRY: ${{ fromJson(inputs.env_json).DOCKER_REGISTRY || '' }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME || github.actor }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
if [ -z "${DOCKER_REGISTRY}" ]; then echo "ERROR: DOCKER_REGISTRY not set in conf"; exit 1; fi
REGISTRY="${DOCKER_REGISTRY}"
REGISTRY_HOST="${REGISTRY%%/*}"
DOCKERFILE="${{ inputs.dockerfile_path }}"
IMAGE_NAME="${{ inputs.image_name }}"
TAG="${{ inputs.tag }}"
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
CONTEXT_DIR=$(dirname "${DOCKERFILE}")
docker build \
--label "git.commit=${{ github.sha }}" \
--label "git.commitBy=${{ github.actor }}" \
--label "build.date=${NOW}" \
-f "${DOCKERFILE}" \
-t "${IMAGE_NAME}:${TAG}" \
"${CONTEXT_DIR}"
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
echo "Pushing ${FULL_IMAGE} ..."
docker tag "${IMAGE_NAME}:${TAG}" "$FULL_IMAGE"
echo "$DOCKER_PASSWORD" | docker login "$REGISTRY_HOST" -u "$DOCKER_USERNAME" --password-stdin
docker push "$FULL_IMAGE"
docker logout "$REGISTRY_HOST"
+2 -2
View File
@@ -8,7 +8,7 @@ on:
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
REPORTS_PUBLISH_TOKEN:
required: true
outputs:
env_json:
@@ -19,7 +19,7 @@ on:
env:
CI_CONF_FILE: ${{ inputs.config_path }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }}
jobs:
parse-config:
+42 -16
View File
@@ -8,6 +8,9 @@ on:
version:
required: true
type: string
build_args:
required: false
type: string
secrets:
GITEA_TOKEN:
required: true
@@ -23,6 +26,7 @@ env:
DOCKER_IMAGE_NAME: ${{ fromJson(inputs.env_json).DOCKER_IMAGE_NAME || '' }}
DOCKER_UI_URL: ${{ fromJson(inputs.env_json).DOCKER_UI_URL || '' }}
DOCKERFILE: ${{ fromJson(inputs.env_json).DOCKERFILE || 'Dockerfile' }}
GIT_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
VERSION: ${{ inputs.version }}
concurrency:
@@ -31,7 +35,7 @@ concurrency:
jobs:
build-push:
runs-on: ubuntu-latest
runs-on: docker
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
@@ -43,25 +47,46 @@ jobs:
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME || github.actor }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
BUILD_ARGS_INPUT: ${{ inputs.build_args }}
run: |
if [ -z "${DOCKER_REGISTRY}" ]; then echo "ERROR: DOCKER_REGISTRY not set in env.conf"; exit 1; fi
if [ -z "${DOCKER_IMAGE_NAME}" ]; then echo "ERROR: DOCKER_IMAGE_NAME not set in env.conf"; exit 1; fi
REGISTRY="${DOCKER_REGISTRY}"
IMAGE="${DOCKER_IMAGE_NAME}"
REGISTRY_HOST="${REGISTRY%%/*}"
BUILD_ARGS=""
if [ -n "$BUILD_ARGS_INPUT" ]; then
while IFS= read -r arg; do
[ -n "$arg" ] && BUILD_ARGS="$BUILD_ARGS --build-arg $arg"
done <<EOF
$BUILD_ARGS_INPUT
EOF
fi
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
docker build \
CONTEXT_DIR=$(dirname "${DOCKERFILE}")
docker build $BUILD_ARGS \
--label "git.commit=${{ github.sha }}" \
--label "git.commitBy=${{ github.actor }}" \
--label "build.date=${NOW}" \
-f "${DOCKERFILE}" \
-t "${DOCKER_IMAGE_NAME}:${VERSION}" .
REGISTRY="${DOCKER_REGISTRY:?DOCKER_REGISTRY not set in env.conf}"
IMAGE="${DOCKER_IMAGE_NAME:?DOCKER_IMAGE_NAME not set in env.conf}"
REGISTRY_HOST="${REGISTRY%%/*}"
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:latest" \
"${CONTEXT_DIR}"
FULL_IMAGE="${REGISTRY}/${IMAGE}:${VERSION}"
echo "Pushing ${FULL_IMAGE} ..."
docker tag "${DOCKER_IMAGE_NAME}:${VERSION}" "$FULL_IMAGE"
docker tag "${IMAGE}:${VERSION}" "$FULL_IMAGE"
echo "$DOCKER_PASSWORD" | docker login "$REGISTRY_HOST" -u "$DOCKER_USERNAME" --password-stdin
docker push "$FULL_IMAGE"
FULL_LATEST="${REGISTRY}/${IMAGE}:latest"
echo "Pushing ${FULL_LATEST} ..."
docker tag "${IMAGE}:latest" "$FULL_LATEST"
docker push "$FULL_LATEST"
docker logout "$REGISTRY_HOST"
- name: Report status SUCCESS
@@ -69,16 +94,17 @@ jobs:
run: |
CONTAINER_URL=""
if [ -n "${DOCKER_UI_URL:-}" ] && [ -n "${VERSION:-}" ]; then
CONTAINER_URL="${DOCKER_UI_URL}/${VERSION}"
CONTAINER_URL="${DOCKER_UI_URL}/${DOCKER_IMAGE_NAME}/${VERSION}"
fi
DIR=$(dirname "${DOCKERFILE}")
if [ "$DIR" != "." ]; then
bash .ci/scripts/report-status.sh success "${DIR}: Docker push ${VERSION}" "${DIR}-ci-docker-build-push" "" "$CONTAINER_URL"
else
bash .ci/scripts/report-status.sh success "Docker push ${VERSION}" ci-docker-build-push "" "$CONTAINER_URL"
fi
bash .ci/scripts/report-status.sh success "Docker build & push ${VERSION} OK" ci-docker-build-push "" "$CONTAINER_URL"
- name: Report status FAILURE
if: failure()
run: bash .ci/scripts/report-status.sh failure "Docker build & push ${VERSION} FAILED" ci-docker-build-push
tag-commit:
runs-on: ubuntu-latest
runs-on: docker
needs: [build-push]
steps:
- uses: actions/checkout@v4
@@ -94,7 +120,7 @@ jobs:
"$SERVER_URL/api/v1/repos/${{ github.repository }}/tags" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${VERSION}\", \"message\": \"Build #$RUN_NUMBER\", \"target\": \"$SHA\"}")
-d "{\"tag_name\": \"${GIT_TAG_PREFIX}${VERSION}\", \"message\": \"Build #$RUN_NUMBER\", \"target\": \"$SHA\"}")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "409" ]; then
exit 0
+16 -29
View File
@@ -6,23 +6,26 @@ on:
required: true
type: string
bats-image:
required: true
required: false
type: string
default: gitea.app.keskikuja.site/niko/ci-bats:git
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
REPORTS_PUBLISH_TOKEN:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }}
jobs:
bats:
runs-on: ubuntu-latest
container:
image: ${{ inputs.bats-image }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
@@ -31,34 +34,18 @@ jobs:
path: .ci
- name: Run bats tests
id: bats-tests
shell: bash
run: |
docker volume create bats-workspace
tar c . | docker run --rm -i -v bats-workspace:/data alpine tar x -C /data
mkdir -p "reports/${GITHUB_SHA:0:8}/bats"
set +e
docker run --rm \
-v bats-workspace:/data \
--entrypoint bash ${{ inputs.bats-image }} \
-c 'apk add -q lsof python3 jq curl ruby && cd /data && gem install bashcov -v 3.3.0 2>&1 | tail -1 && bashcov -- bats tests/' \
> "reports/${GITHUB_SHA:0:8}/bats/results.txt" 2>&1
BATS_EXIT=$?
bash .ci/.gitea/scripts/bats-coverage.sh bats-workspace "reports/${GITHUB_SHA:0:8}/bats"
docker volume rm bats-workspace > /dev/null 2>&1
bash .ci/.gitea/scripts/bats-report.sh "reports/${GITHUB_SHA:0:8}/bats"
echo "BATS_EXIT=${BATS_EXIT}" >> "${GITHUB_ENV}"
exit ${BATS_EXIT}
mkdir -p reports/bats
bashcov -- bats tests/ > reports/bats/results.txt 2>&1
- name: Publish bats reports
- name: Post-process coverage
if: always()
run: bash .ci/scripts/publish-git-pages.sh bats
run: bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
- name: Report status
- name: Post-process test report
if: always()
run: |
if [ "${BATS_EXIT}" = "0" ]; then
bash .ci/scripts/report-status.sh success "Link to Bats reports" unit-tests bats
else
bash .ci/scripts/report-status.sh failure "Link to Bats reports" unit-tests bats
fi
run: bash .ci/.gitea/scripts/bats-report.sh reports/bats
- name: Report
if: always()
run: bash .ci/scripts/ci-report.sh "Bats test report" unit-tests bats ${{ job.status }}
@@ -0,0 +1,41 @@
name: CI Container Build Bats
on:
workflow_dispatch:
inputs:
config_path:
required: true
type: string
default: '.gitea/workflows/example-gitea-env.conf'
description: 'Polku .gitea-env.conf-tiedostoon'
dockerfile_path:
required: true
type: string
default: 'Dockerfile.ci-bats'
description: 'Polku Dockerfileen'
image_name:
required: true
type: string
default: 'ci-bats'
description: 'Kontin nimi ilman registry-polkua'
tag:
required: true
type: string
default: 'latest'
description: 'Image-tägi'
jobs:
load-config:
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
dockerfile_path: ${{ inputs.dockerfile_path }}
image_name: ${{ inputs.image_name }}
tag: ${{ inputs.tag }}
@@ -0,0 +1,41 @@
name: CI Container Build Cucumber
on:
workflow_dispatch:
inputs:
config_path:
required: true
type: string
default: '.gitea/workflows/example-gitea-env.conf'
description: 'Polku .gitea-env.conf-tiedostoon'
dockerfile_path:
required: true
type: string
default: 'Dockerfile.ci-cucumber'
description: 'Polku Dockerfileen'
image_name:
required: true
type: string
default: 'ci-cucumber'
description: 'Kontin nimi ilman registry-polkua'
tag:
required: true
type: string
default: 'latest'
description: 'Image-tägi'
jobs:
load-config:
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
dockerfile_path: ${{ inputs.dockerfile_path }}
image_name: ${{ inputs.image_name }}
tag: ${{ inputs.tag }}
+9 -32
View File
@@ -6,19 +6,20 @@ on:
required: true
type: string
cucumber-node-image:
required: true
required: false
type: string
default: gitea.app.keskikuja.site/niko/ci-cucumber:with-python
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
REPORTS_PUBLISH_TOKEN:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }}
jobs:
cucumber:
@@ -33,38 +34,14 @@ jobs:
path: .ci
- name: Run cucumber tests
id: cucumber-tests
shell: bash
run: |
apt-get update -qq && apt-get install -y -qq --no-install-recommends lsof jq
npm install @cucumber/cucumber > /dev/null 2>&1
mkdir -p "reports/${GITHUB_SHA:0:8}/cucumber"
set +e
mkdir -p reports/cucumber
npx cucumber-js \
--format json:"reports/${GITHUB_SHA:0:8}/cucumber/report.json" \
--format html:"reports/${GITHUB_SHA:0:8}/cucumber/index.html" 2>&1
CUCUMBER_EXIT=$?
echo "CUCUMBER_EXIT=${CUCUMBER_EXIT}" >> "${GITHUB_ENV}"
exit ${CUCUMBER_EXIT}
--format json:reports/cucumber/results.json \
--format html:reports/cucumber/test-report.html 2>&1
- name: Publish cucumber reports
if: always()
run: bash .ci/scripts/publish-git-pages.sh cucumber
- name: Report status
- name: Report
if: always()
shell: bash
run: |
if [ "${CUCUMBER_EXIT}" = "0" ]; then
if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
bash .ci/scripts/report-status.sh success "Link to Cucumber reports" acc-tests cucumber
else
bash .ci/scripts/report-status.sh success "Link to Cucumber reports" acc-tests
fi
else
if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
bash .ci/scripts/report-status.sh failure "Link to Cucumber reports" acc-tests cucumber
else
bash .ci/scripts/report-status.sh failure "Link to Cucumber reports" acc-tests
fi
fi
run: bash .ci/scripts/ci-report.sh "Cucumber test report" acc-tests cucumber ${{ job.status }}
+1 -3
View File
@@ -20,7 +20,6 @@ jobs:
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
bats-image: bats/bats:latest
cucumber:
name: Cucumber tests
@@ -29,13 +28,12 @@ jobs:
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
cucumber-node-image: node:22
report-summary:
name: Report Summary
needs: [load-config, bats, cucumber]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/example-report-summary.yml@main
uses: niko/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: bats cucumber
+2 -1
View File
@@ -2,5 +2,6 @@ GITEA_API_URL=https://gitea.app.keskikuja.site
GIT_PAGES_URL=https://ci-reports.helm-dev.keskikuja.site
DOCKER_REGISTRY=gitea.app.keskikuja.site/niko
DOCKER_IMAGE_NAME=gitea-ci-library-test-image
DOCKER_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container/gitea-ci-library-test-image
DOCKER_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container
#DOCKERFILE=Dockerfile.platform
+29 -7
View File
@@ -7,14 +7,14 @@ on:
jobs:
load-config:
name: Load example-gitea-env.conf to pipeline env
name: Config load
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: .gitea/workflows/example-gitea-env.conf
check-version:
name: Check existing artifact
name: Latest versio
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/check-version.yml@main
secrets: inherit
@@ -29,7 +29,6 @@ jobs:
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
bats-image: bats/bats:latest
cucumber:
name: Cucumber tests
@@ -39,9 +38,8 @@ jobs:
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
cucumber-node-image: node:22
build-push:
docker-build-push:
name: Build & Push Docker
needs: [load-config, check-version, bats, cucumber]
if: needs.check-version.outputs.artifact_exists != 'true'
@@ -50,12 +48,36 @@ jobs:
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
build_args: |
VERSION=${{ needs.check-version.outputs.version }}
FOO=bar
docker-gitops:
name: GitOps
needs: [docker-build-push, load-config, check-version]
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
GITOPS_FILE: dev/values.yaml
GITOPS_YQ_TPL: '.service.tag = "{{VERSION}}"'
GITOPS_REPO: niko/gitea-ci-gitops-tests
report-summary:
name: Report Summary
needs: [load-config, bats, cucumber]
needs: [load-config, check-version, docker-build-push, docker-gitops]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/example-report-summary.yml@main
uses: niko/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: bats cucumber
gitops: |
${{ needs.docker-gitops.outputs.summary }}
tag-maintenance:
name: Move provider version tag
needs: [docker-gitops]
if: success()
uses: niko/gitea-ci-library/.gitea/workflows/tag-maintenance.yml@main
secrets: inherit
+11
View File
@@ -0,0 +1,11 @@
name: Hello World (Manual)
on:
workflow_dispatch:
push:
# branches: [ main ] # Tai [ feature/ci-container ]
branches: [feature/ci-container]
jobs:
greet:
runs-on: ubuntu-latest
steps:
- run: echo "Hello"
@@ -1,34 +0,0 @@
name: Report Summary
on:
workflow_call:
inputs:
env_json:
required: true
type: string
suites:
required: true
type: string
description: Space-separated suite names published to git-pages
env:
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
jobs:
summary:
runs-on: ubuntu-latest
steps:
- name: Generate report links
shell: bash
run: |
SHA8="${GITHUB_SHA:0:8}"
BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}"
{
echo "## Test Reports"
echo ""
echo "| Suite | Report |"
echo "|-------|--------|"
for suite in ${{ inputs.suites }}; do
echo "| ${suite} | [View report](${BASE}/${suite}/) |"
done
} >> "${GITHUB_STEP_SUMMARY}"
@@ -0,0 +1,59 @@
name: CI Gitea Reports Main
on:
push:
branches:
- main
paths:
- gitea-reports/**
- ".gitea/workflows/gitea-reports.*"
workflow_dispatch:
jobs:
load-config:
name: Config load
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: .gitea/workflows/gitea-reports.gitea-env.conf
check-version:
name: Latest version
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/check-version.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
helm-push:
name: Build & Push Helm chart
needs: [load-config, check-version]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: niko/gitea-ci-library/.gitea/workflows/helm-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
chart_path: gitea-reports
chart-gitops:
name: GitOps
needs: [helm-push, load-config, check-version]
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
GITOPS_FILE: dev/Chart.yaml
GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "gitea-reports") | .version) = "{{VERSION}}"'
GITOPS_REPO: niko/gitea-ci-gitops-tests
report-summary:
name: Report Summary
needs: [load-config, check-version, helm-push, chart-gitops]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: ""
gitops: |
${{ needs.chart-gitops.outputs.summary }}
@@ -0,0 +1,5 @@
GITEA_API_URL=https://gitea.app.keskikuja.site
HELM_REGISTRY=gitea.app.keskikuja.site/niko
HELM_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container
GIT_TAG_PREFIX=gitea-reports/
VERSION_FILE=gitea-reports/Chart.yaml
+202
View File
@@ -0,0 +1,202 @@
name: GitOps Dispatch
on:
workflow_call:
inputs:
env_json:
required: true
type: string
version:
required: true
type: string
GITOPS_FILE:
required: true
type: string
GITOPS_YQ_TPL:
required: true
type: string
GITOPS_REPO:
required: true
type: string
GITOPS_EXTRA_CMD:
required: false
type: string
secrets:
GITOPS_DISPATCH_TOKEN:
required: true
outputs:
summary:
description: "Pipe-format: component|version|status|commit_sha|repo"
value: ${{ jobs.dispatch.outputs.summary }}
env:
GITOPS_VERSION: ${{ inputs.version }}
GITOPS_FILE: ${{ inputs.GITOPS_FILE }}
GITOPS_YQ_TPL: ${{ inputs.GITOPS_YQ_TPL }}
GITOPS_REPO: ${{ inputs.GITOPS_REPO }}
GITOPS_SOURCE_REPO: ${{ github.repository }}
GITOPS_SOURCE_COMMIT: ${{ github.sha }}
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GITOPS_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
GITOPS_EXTRA_CMD: ${{ inputs.GITOPS_EXTRA_CMD || '' }}
GITOPS_WORKFLOW: gitops-service.yaml
GITOPS_DISPATCH_TIMEOUT: 30
jobs:
dispatch:
runs-on: ubuntu-latest
outputs:
summary: ${{ steps.summary.outputs.GITOPS_SUMMARY }}
steps:
- name: Generate dispatch_id
id: gen
run: |
ID=$(date +%s | md5sum | head -c 8)
echo "dispatch_id=$ID" >> "$GITHUB_OUTPUT"
- name: Resolve commit author
id: author
run: |
echo "name=$(git log -1 --format='%an')" >> "$GITHUB_OUTPUT"
echo "email=$(git log -1 --format='%ae')" >> "$GITHUB_OUTPUT"
- name: Dispatch to GitOps repo
env:
GITEA_TOKEN: ${{ secrets.GITOPS_DISPATCH_TOKEN }}
run: |
INPUTS=$(jq -nc \
--arg dispatch_id "${{ steps.gen.outputs.dispatch_id }}" \
--arg author_name "${{ steps.author.outputs.name }}" \
--arg author_email "${{ steps.author.outputs.email }}" \
--arg file "$GITOPS_FILE" \
--arg yq_tpl "$GITOPS_YQ_TPL" \
--arg version "$GITOPS_VERSION" \
--arg source_repo "$GITOPS_SOURCE_REPO" \
--arg source_commit "$GITOPS_SOURCE_COMMIT" \
--arg git_tag_prefix "${GITOPS_TAG_PREFIX:-}" \
--arg extra_cmd "${GITOPS_EXTRA_CMD:-}" \
'{dispatch_id: $dispatch_id, author_name: $author_name, author_email: $author_email, file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_tag_prefix, extra_cmd: $extra_cmd}')
curl -s -X POST \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/actions/workflows/${GITOPS_WORKFLOW}/dispatches" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -nc --arg ref "main" --argjson inputs "$INPUTS" '{ref: "main", inputs: $inputs}')"
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Poll for completion
id: poll
env:
GITEA_TOKEN: ${{ secrets.GITOPS_DISPATCH_TOKEN }}
run: |
ID="${{ steps.gen.outputs.dispatch_id }}"
TIMEOUT_MINUTES="${GITOPS_DISPATCH_TIMEOUT:-30}"
POLL_INTERVAL=10
START_TIME=$(date +%s)
TIMEOUT_SECONDS=$((TIMEOUT_MINUTES * 60))
echo "Polling for run with dispatch_id=$ID"
while [ -z "$RUN_ID" ]; do
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
if [ "$ELAPSED" -ge "$TIMEOUT_SECONDS" ]; then
echo "ERROR: Timeout waiting for run to appear" >&2
exit 124
fi
RUNS_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/actions/runs?event=workflow_dispatch&limit=10" \
-H "Authorization: token $GITEA_TOKEN")
RUN_ID=$(echo "$RUNS_RESP" | jq -r --arg id "$ID" \
'[.workflow_runs[] | select(.display_title | contains($id))] | .[0].id // empty')
[ -z "$RUN_ID" ] && sleep "$POLL_INTERVAL"
done
echo "Run found: id=$RUN_ID"
while true; do
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
if [ "$ELAPSED" -ge "$TIMEOUT_SECONDS" ]; then
echo "ERROR: Timeout waiting for completion" >&2
exit 124
fi
RUN_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/actions/runs/${RUN_ID}" \
-H "Authorization: token $GITEA_TOKEN")
STATUS=$(echo "$RUN_RESP" | jq -r '.status // "running"')
CONCLUSION=$(echo "$RUN_RESP" | jq -r '.conclusion // ""')
echo " status=$STATUS conclusion=$CONCLUSION"
if [ "$STATUS" = "completed" ]; then
if [ "$CONCLUSION" = "success" ]; then
echo "GitOps workflow completed successfully"
# 1. List recent commits from GitOps repo
COMMITS=$(curl -s --connect-timeout 5 --max-time 10 \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/commits?sha=main&limit=10" \
-H "Authorization: token $GITEA_TOKEN")
# 2. Find commit by message: "gitops: update version to X.Y.Z"
SEARCH_MSG="gitops: update version to ${GITOPS_VERSION}"
GITOPS_COMMIT=$(echo "$COMMITS" | jq -r \
--arg msg "$SEARCH_MSG" \
'[.[] | select(.commit.message | contains($msg))] | .[0].sha // empty')
# 3. If not found → fail
if [ -z "$GITOPS_COMMIT" ]; then
echo "ERROR: no matching GitOps commit found for version ${GITOPS_VERSION}" >&2
exit 1
fi
echo "GITOPS_COMMIT=$GITOPS_COMMIT" >> "$GITHUB_OUTPUT"
echo "$GITOPS_COMMIT" > /tmp/gitops-commit
exit 0
else
echo "ERROR: GitOps workflow failed with conclusion=$CONCLUSION" >&2
exit 1
fi
fi
sleep "$POLL_INTERVAL"
done
- name: GitOps summary
id: summary
if: always()
run: |
STATUS="failure"
GITOPS_SHA=""
if [ -f /tmp/gitops-commit ]; then
STATUS="success"
GITOPS_SHA=$(cat /tmp/gitops-commit)
fi
COMPONENT="${GITOPS_TAG_PREFIX:-${GITOPS_FILE}}"
echo "GITOPS_SUMMARY=${COMPONENT}|${GITOPS_VERSION}|${STATUS}|${GITOPS_SHA}|${GITOPS_REPO}" >> "$GITHUB_OUTPUT"
- name: Set commit status
if: success()
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
GITOPS_SHA=$(cat /tmp/gitops-commit)
PREFIX="${GITOPS_TAG_PREFIX%/}"
if [ -n "$PREFIX" ]; then
CONTEXT="gitops/$(basename "${GITOPS_SOURCE_REPO}")/${PREFIX}"
DESCRIPTION="GitOps: ${PREFIX} ${GITOPS_VERSION}"
else
CONTEXT="gitops/$(basename "${GITOPS_SOURCE_REPO}")"
DESCRIPTION="GitOps: ${GITOPS_VERSION}"
fi
ROOT_REPO="${GITOPS_SOURCE_REPO}" ROOT_COMMIT="${GITOPS_SOURCE_COMMIT}" \
bash .ci/scripts/report-status.sh success \
"$DESCRIPTION" "$CONTEXT" "" \
"${GITEA_API_URL}/${GITOPS_REPO}/commit/${GITOPS_SHA}"
+118
View File
@@ -0,0 +1,118 @@
name: Helm Build & Push
on:
workflow_call:
inputs:
env_json:
required: true
type: string
version:
required: true
type: string
chart_path:
required: true
type: string
extra_dependency_paths:
required: false
type: string
secrets:
GITEA_TOKEN:
required: true
HELM_USER:
required: false
HELM_PASSWORD:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
HELM_REGISTRY: ${{ fromJson(inputs.env_json).HELM_REGISTRY || '' }}
HELM_UI_URL: ${{ fromJson(inputs.env_json).HELM_UI_URL || '' }}
GIT_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
CHART_FILE: ${{ fromJson(inputs.env_json).VERSION_FILE || 'Chart.yaml' }}
VERSION: ${{ inputs.version }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-push:
runs-on: ubuntu-latest
container:
image: alpine/helm:3.19.0
steps:
- name: Install Node.js for actions/checkout
# COMPROMISE: Requires internet access.
# Does NOT work in air-gapped environments.
# Replace with a custom image (e.g., extending alpine/helm + nodejs) if needed.
run: apk add --no-cache nodejs
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Resolve extra subchart dependencies
if: inputs.extra_dependency_paths != ''
run: |
for path in $(echo "${{ inputs.extra_dependency_paths }}" | tr ',' '\n'); do
helm dependency update "${path}"
done
- name: Package Helm chart
run: |
CHART_DIR=$(dirname "${CHART_FILE}")
helm dependency update "${CHART_DIR}"
helm package "${CHART_DIR}" \
--version "${VERSION}" \
--app-version "${VERSION}" \
--destination /tmp/helm-packages
- name: Push to OCI registry
env:
HELM_USER: ${{ secrets.HELM_USER || github.actor }}
HELM_PASSWORD: ${{ secrets.HELM_PASSWORD }}
run: |
REGISTRY="${HELM_REGISTRY:?HELM_REGISTRY not set in env.conf}"
echo "$HELM_PASSWORD" | helm registry login "${REGISTRY}" \
-u "$HELM_USER" \
--password-stdin
helm push /tmp/helm-packages/*.tgz "oci://${REGISTRY}"
helm registry logout "${REGISTRY}"
- name: Report status with UI link
if: success() && env.HELM_UI_URL != ''
run: |
CHART_NAME=$(grep '^name:' "${CHART_FILE}" | awk '{print $2}')
UI_URL="${HELM_UI_URL}/${CHART_NAME}/${VERSION}"
if [ "${CHART_PATH}" != "." ] && [ -n "${CHART_PATH}" ]; then
bash .ci/scripts/report-status.sh success "${CHART_PATH}: Helm push ${VERSION}" "${CHART_PATH}-ci-helm-build-push" "" "$UI_URL"
else
bash .ci/scripts/report-status.sh success "Helm push ${VERSION}" ci-helm-build-push "" "$UI_URL"
fi
tag-commit:
runs-on: ubuntu-latest
needs: [build-push]
steps:
- uses: actions/checkout@v4
- name: Create git tag
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
SERVER_URL: ${{ gitea.server_url }}
RUN_NUMBER: ${{ github.run_number }}
SHA: ${{ github.sha }}
run: |
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
"$SERVER_URL/api/v1/repos/${{ github.repository }}/tags" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${GIT_TAG_PREFIX}${VERSION}\", \"message\": \"Build #$RUN_NUMBER\", \"target\": \"$SHA\"}")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "409" ]; then
exit 0
else
exit 1
fi
+57
View File
@@ -0,0 +1,57 @@
name: Report Summary
on:
workflow_call:
inputs:
env_json:
required: true
type: string
suites:
required: true
type: string
description: Space-separated suite names published to git-pages
gitops:
required: false
type: string
description: 'Pipe-separated rows: component|version|status|commit_sha|repo'
env:
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
jobs:
summary:
runs-on: ubuntu-latest
steps:
- name: Generate report links
shell: bash
run: |
SHA8="${GITHUB_SHA:0:8}"
BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}"
{
echo "## Test Reports"
echo ""
echo "| Suite | Report |"
echo "|-------|--------|"
for suite in ${{ inputs.suites }}; do
echo "| ${suite} | [View report](${BASE}/${suite}/) |"
done
} >> "${GITHUB_STEP_SUMMARY}"
if [ -n "${{ inputs.gitops }}" ]; then
GITEA_URL="${{ fromJson(inputs.env_json).GITEA_API_URL }}"
{
echo ""
echo "## GitOps updates"
echo ""
echo "| Component | Version | Status | GitOps commit |"
echo "|-----------|---------|--------|--------------|"
echo '${{ inputs.gitops }}' | while IFS='|' read -r comp ver status sha repo; do
[ -z "$comp" ] && continue
if [ -n "$sha" ]; then
echo "| $comp | $ver | $status | [link]($GITEA_URL/$repo/commit/$sha) |"
else
echo "| $comp | $ver | $status | — |"
fi
done
} >> "${GITHUB_STEP_SUMMARY}"
fi
+38
View File
@@ -0,0 +1,38 @@
name: Tag Maintenance
on:
workflow_call:
secrets:
GITEA_TOKEN:
required: true
jobs:
move-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Read current provider version
id: version
run: echo "TAG=$(cat CURRENT_PROVIDER_VERSION | tr -d '[:space:]')" >> "$GITHUB_OUTPUT"
- name: Move tag to commit
run: |
TAG="${{ steps.version.outputs.TAG }}"
SHA="${{ github.sha }}"
curl -sf -X DELETE \
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
"${{ gitea.server_url }}/api/v1/repos/${{ github.repository }}/tags/${TAG}" || true
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
-H "Content-Type: application/json" \
"${{ gitea.server_url }}/api/v1/repos/${{ github.repository }}/tags" \
-d "{\"tag_name\": \"${TAG}\", \"message\": \"Release ${TAG}\", \"target\": \"${SHA}\"}")
if [ "$HTTP_CODE" = "201" ]; then
echo "${TAG} tag moved to ${SHA}"
else
echo "ERROR: failed to move ${TAG} tag (HTTP $HTTP_CODE)" >&2
exit 1
fi
+1
View File
@@ -7,3 +7,4 @@ tmp/
coverage/
.DS_Store
reports/
.vscode/
+1
View File
@@ -0,0 +1 @@
v1
+3
View File
@@ -1 +1,4 @@
FROM alpine:latest
ARG VERSION=unknown
ARG FOO=unknown
RUN mkdir -p /app && echo "$VERSION" > /app/version.txt && echo "$FOO" > /app/foo.txt
+3
View File
@@ -0,0 +1,3 @@
FROM bats/bats:1.11.0
RUN apk add --no-cache lsof python3 jq curl ruby nodejs git && \
gem install bashcov -v 3.3.0
+7
View File
@@ -0,0 +1,7 @@
FROM node:22
RUN apt-get update -qq && \
apt-get install -y -qq --no-install-recommends lsof jq python3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
npm install -g @cucumber/cucumber
ENV NODE_PATH=/usr/local/lib/node_modules
+87 -17
View File
@@ -2,6 +2,15 @@
Reusable workflow -kirjasto Gitea Actionsille. Lisätietoja: [docs/](docs/)
**Consumer-käyttöönotto:** [skills/consumer-pipelines/SKILL.md](skills/consumer-pipelines/SKILL.md) — pipeline-standardit ja säännöt consumer-projekteille
**GitOps-päivitys:** [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md) — GitOps-repon job-template, dispatch ja token-ohjeet
**Single repo & monorepo:** Kirjasto toimii molemmissa. Monorepo-tuki
polkusuodatuksella, komponenttikohtaisilla versioilla ja git-tägien
etuliitteillä — jokainen komponentti julkaistaan itsenäisesti omassa
tahdissaan. Katso [skills/consumer-pipelines/SKILL.md](skills/consumer-pipelines/SKILL.md).
## Provider-binding — miten consumer löytää providerin
Consumer kutsuu provideria `uses:`-viittauksella. Ei discoveryä — polku kovakoodataan consumerin
@@ -116,8 +125,8 @@ Hae token Giteasta:
```bash
GITEA_URL="https://<gitea-server-url>"
GITEA_ACTIONS_TOKEN="<registration-token>"
GITEA_ACTIONS_NAMESPACE="gitea-actions"
GITEA_ACTIONS_TOKEN="<registration-token>"
```
### 3. Tee secret vain init install yhteydessä
@@ -132,7 +141,7 @@ kubectl create secret generic act-runner-token \
### 4. Helm install / upgrade
Menee samalla komennolla.
Menee samalla komennolla.
> Muista asettaa variables ennen ajoa.
@@ -142,57 +151,102 @@ ja sitä kautta Gitea ei tarvitse päivityksessä mitään temppuja.
Päivityksen jälkeen muista tappaa pod (käynnistyy automaattisesti uudelleen), että lataa varmasti kaikki uudesta. Sillä ConfigMap tms eivät lataudu
mikäli pod jatkaa ajamista.
Klusterissa on kaksi StatefulSetiä, joilla on eri labelit:
- **`act-runner`** — yleisrunneri (label `ubuntu-latest`). DinD-sidecar on olemassa mutta idle — `require_docker: false` estää runneria käyttämästä sitä.
- **`act-runner-docker`** — Docker-buildien runneri (label `docker`). DinD on aktiivinen, `require_docker: true`.
Vain `docker-build-push.yml` ja `ci-container-build-push.yml` käyttävät `docker`-labelia.
Kaikki muut workflowt (testit, lintit, helm-publish, gitops-dispatch) ajetaan `ubuntu-latest`-runnerilla.
Steppien suoritus ei mene Docker Daemonin läpi, joten konekielisiä kontteja luodaan suoraan K8s-runtimella.
```bash
helm repo add gitea https://dl.gitea.com/charts
helm repo update
# 1. Yleisrunneri (DinD idle)
helm upgrade --install act-runner gitea/actions \
--set enabled=true \
--set giteaRootURL="$GITEA_URL" \
--set existingSecret=act-runner-token \
--set existingSecretKey=token \
--set statefulset.replicas=2 \
--set statefulset.runner.tag=1.0.8 \
--set statefulset.dind.tag=29.5.2-dind \
--set statefulset.dind.resources.requests.memory=250Mi \
--set statefulset.dind.resources.limits.memory=750Mi \
--set-string 'statefulset.runner.config=log:
level: info
cache:
enabled: false
enabled: true
container:
require_docker: true
docker_timeout: 300s' \
require_docker: false' \
--namespace "$GITEA_ACTIONS_NAMESPACE" \
--create-namespace
```
path escapes from parent -bugi korjattiin Docker 29.5.2:ssa. Tämän teko aikana default on 29.5.1 — juuri tämän alle jäävä versio.
# 2. Docker-runner (DinD)
helm upgrade --install act-runner-docker gitea/actions \
--set enabled=true \
--set giteaRootURL="$GITEA_URL" \
--set existingSecret=act-runner-token \
--set existingSecretKey=token \
--set statefulset.replicas=1 \
--set statefulset.runner.tag=1.0.8 \
--set statefulset.dind.tag=29.5.2-dind \
--set statefulset.dind.resources.requests.memory=250Mi \
--set statefulset.dind.resources.limits.memory=750Mi \
--set-string 'statefulset.runner.config=log:
level: info
cache:
enabled: true
container:
require_docker: true
docker_timeout: 300s
runner:
labels:
- "docker:docker://catthehacker/ubuntu:act-latest"' \
--namespace "$GITEA_ACTIONS_NAMESPACE"
```
`path escapes from parent` -bugi korjattiin Docker 29.5.2:ssa. Tämän teko aikana default on 29.5.1 — juuri tämän alle jäävä versio.
Oletus-lokitaso on `debug` — suositeltu `info`. Näkee jobien aloitukset ja valmistumiset ilman konttikerrosten purkua (Downloading/Extracting-spämmiä). `debug` on tarpeen vain vianselvityksessä.
`cache.enabled: true` nappaa image-cachen käyttöön — ilman sitä jokainen ajo lataa konttikuvat uudestaan.
#### Docker (DinD)
Helm chart deployaa DinD:n init-sidecarina (`docker:dind` samassa podissa).
`require_docker: true` kytkee jobit siihen — erillistä DinD-asennusta ei tarvita.
> **Huomio:** Gitea 1.26.x ei tue vielä label-pohjaista runner-valintaa.
> `runs-on: ubuntu-latest`-jobi saattaa päätyä `docker`-labeliselle runnerille.
> Bugi on tunnettu Gitean FAQ:ssa — korjaus tulossa myöhemmässä versiossa.
> Katso: [docs.gitea.com/usage/actions/faq](https://docs.gitea.com/usage/actions/faq)
**DinD-tag pinottu:** `29.5.2-dind` (ei chart-oletusta). Docker 29.5.1 aiheuttaa act-runnerissa
`path escapes from parent` -virheen job-kontin käynnistyksessä.
Helm chart deployaa DinD:n init-sidecarina (`docker:dind` samassa podissa).
Molemmissa StatefulSetissä on DinD-sidecar, mutta:
- **`act-runner`**: `require_docker: false` → runner ei käytä DinD:tä lainkaan, steppien suoritus menee suoraan K8s-runtimella
- **`act-runner-docker`**: `require_docker: true` → runner luo steppikontit DinD:n kautta (tarvitaan `docker build` -komentoja varten)
**DinD-tag pinottu:** `29.5.2-dind` molemmissa (ei chart-oletusta `29.5.1-dind`). Docker 29.5.1 aiheuttaa act-runnerissa
`path escapes from parent` -virheen job-kontin käynnistyksessä sekä `mkdirat var/run: file exists` -virheen tiedostojen kopioinnissa.
Maven/npm-ajot käyttävät vain workflow'n `container:`-imagea; DinD tarvitaan vasta Docker-buildissä.
### 3. Varmista
### 5. Varmista
```bash
kubectl get pods -n gitea-actions
# → act-runner-runner-0 Running
# → act-runner-docker-runner-0 Running
kubectl exec -n gitea-actions act-runner-runner-0 -c dind -- docker version
kubectl exec -n gitea-actions act-runner-docker-runner-0 -c dind -- docker version
# → Server Version: 29.5.2 (tai uudempi)
```
Gitean puolella runner ilmestyy Active-tilaan pienellä viiveellä:
Gitean puolella runnerit ilmestyvät Active-tilaan pienellä viiveellä:
```
Site Admin → Actions → Runners (tai Org → Settings → Actions → Runners)
# → act-runner-runner-0 Active ubuntu-latest
# → act-runner-docker-runner-0 Active docker
```
Tämän jälkeen `.gitea/workflows/ci.yml` triggeröityy automaattisesti pushista.
@@ -210,9 +264,11 @@ Consumer-repossa on oltava seuraavat asetukset:
| Secret | Kuvaus |
|--------|--------|
| `GIT_PAGES_PUBLISH_TOKEN` | Git-pages-palvelimen BasicAuth-token. Nimi on lukittu — tämä tarkka nimi vaaditaan. |
| `REPORTS_PUBLISH_TOKEN` | Git-pages-palvelimen BasicAuth-token. Nimi on lukittu — tämä tarkka nimi vaaditaan. |
`GITEA_TOKEN` on Gitean sisäinen secret (`secrets.GITEA_TOKEN`), joka on automauttisesti saatavilla — sitä ei tarvitse erikseen luoda.
`GITEA_TOKEN` on Gitean automaattisesti jokaiselle workflow-runille generoima token (`secrets.GITEA_TOKEN`). Se on scopeutettu **siihen repoon**, jossa workflow ajaa — ei toimi toiseen repoon dispatchaukseen eikä toisen repon commit-statusin asettamiseen. Ei tarvitse erikseen luoda.
Jos workflow tarvitsee oikeuksia **toiseen** repoon (esim. dispatch GitOps-repoon), tarvitaan manuaalinen token. Katso [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md).
### Config-tiedosto (`.gitea/workflows/gitea-env.conf`)
@@ -244,10 +300,21 @@ GIT_PAGES_URL=https://ci-reports.example.com
Jokaisen jobin alussa `ci-validate.sh` tarkistaa:
- `.gitea/workflows/gitea-env.conf` on olemassa ja sen arvot ovat validit
- `GITEA_TOKEN` ja `GIT_PAGES_PUBLISH_TOKEN` on asetettu
- `GITEA_TOKEN` ja `REPORTS_PUBLISH_TOKEN` on asetettu
Jos validointi epäonnistuu, job keskeytyy exit-koodilla 1 ja Gitean commit-status näyttää epäonnistumisen linkkinä lokiin.
### GitOps-päivitys
Artifact buildin jälkeen voidaan dispatchata GitOps-repoon, joka päivittää
konfiguraatiotiedoston (esim. Chart.yaml version) ja pushaa muutoksen.
Kaksi skriptiä:
- `scripts/dispatch-workflow.sh` — lähettää workflow_dispatch-pyynnön ja pollaa valmistumista
- `scripts/gitops-update.sh` — kloonaa, päivittää yq:llä, committaa ja pushaa
Tarkka asennus: [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md)
### Muuta
| Muuttuja | Kuvaus |
@@ -256,4 +323,7 @@ Jos validointi epäonnistuu, job keskeytyy exit-koodilla 1 ja Gitean commit-stat
| `existingSecret` | Kubernetes secretin nimi, jossa token |
| `existingSecretKey` | Avain secretin sisällä |
| `statefulset.dind.tag` | DinD-image tag (`29.5.2-dind` minimi) |
| `statefulset.dind.resources.requests.memory` | DinD muistirequest (suositus `250Mi`) |
| `statefulset.dind.resources.limits.memory` | DinD muistilimitti (suositus `750Mi`) |
| `statefulset.runner.labels` | Mukautetut labelit |
+1 -1
View File
@@ -26,7 +26,7 @@ Consumer:
- Omistaa pipeline-logiikan (mitä testejä ajetaan, missä järjestyksessä)
- Luo raportit omiin polkuihinsa (`reports/{sha8}/{step}/`)
- Luo `.meta`-tiedostot per step
- Määrittelee Gitea-secretit (`GIT_PAGES_PUBLISH_TOKEN`, `GITEA_TOKEN`)
- Määrittelee Gitea-secretit (`REPORTS_PUBLISH_TOKEN`, `GITEA_TOKEN`)
### Provider → Consumer (mitä provider tarjoaa)
@@ -0,0 +1,53 @@
# 9. Breaking changes kielletty
## Päätös
Providerin `v1`-tagin osoittamaa rajapintaa ei koskaan rikota.
Consumerin `uses:`-kutsut säilyvät yhteensopivina — uusi versiotagi
(`v2`, `v3`) luodaan VAIN jos taaksepäin yhteensopimaton muutos on
pakottava. Käytännössä: `v1` on pysyvä, ja sitä ylläpidetään
eteenpäin.
## Rajapinnan määritelmä
Providerin rajapinta = `config-provider.yml` ja `check-version.yml`
workflow_call-inputit ja -outputit:
- Inputtien nimet, tyypit ja required-arvot eivät muutu
- Outputtien nimet eivät katoa
- Secret-nimet eivät muutu
- Workflow-tiedoston nimi ja polku eivät muutu
Sisäinen toteutus (scriptit, checkout-logiikka, build-vaiheet) voi
muuttua vapaasti — consumer ei ole niistä riippuvainen.
## Versiotagin siirto
Tagi `v1` siirretään automaattisesti uusimpaan onnistuneeseen
main-commitin CI-ajoon (`tag-maintenance.yml`). Tagin nimi luetaan
tiedostosta `CURRENT_PROVIDER_VERSION`.
Jos breaking change joskus tulee pakottavaksi:
1. Päivitä `CURRENT_PROVIDER_VERSION``v2`
2. Luo uusi `v2`-tagi manuaalisesti (osoittaa uuteen rajapintaan)
3. `tag-maintenance.yml` alkaa ylläpitää `v2`:ta eteenpäin
4. `v1`-tagia **ei poisteta** — se jää osoittamaan viimeistä
v1-yhteensopivaa committia. `v1`:tä käyttävät consumerit
jatkavat toimintaansa ilman muutoksia.
5. Uudet consumerit ottavat käyttöön `@v2`:n.
Aktiivisesti ylläpidetään vain yhtä tagia (`CURRENT_PROVIDER_VERSION`).
Vanhat tagit säilyvät paikoillaan taaksepäin yhteensopivuutta varten.
## Perustelu
Yhden aktiivisen tagin ylläpito on yksinkertaisempaa kuin usean
rinnakkaisen version aktiivinen hallinta. Homelab-ympäristössä
consumerit ovat saman ylläpitäjän hallinnassa — eri tiimien
rinnakkaisia aktiiviversioita ei tarvita.
Breaking changen sattuessa vanha tagi säilyy — vanhat consumerit
eivät hajoa. Uusi tagi otetaan käyttöön uusissa consumereissa.
Breaking changen kielto pakottaa suunnittelemaan rajapinnat
huolellisesti etukäteen.
@@ -0,0 +1,43 @@
# 10. Pipeline-reititin — ei komentoja
## Päätös
CI-pipelinetiedostot (`ci-main.yml`, `ci-feature.yml`) ovat puhtaita
**reitittimiä**. Ne eivät saa sisältää `run:`-komentoja, inline-skriptejä
tai varsinaista build-/test-logiikkaa. Niiden ainoa sallittu sisältö on:
- `uses:` — viittaus reusable workflow'hun
- `needs:` — riippuvuus toisen jobin valmistumiseen
- `if:` — ehdollinen suoritus
- `secrets: inherit` — salaisuuksien välitys
## Mikä ei kuulu reitittimeen
- `run:`-komennot
- Inline-skriptit (shell, Python, tms.)
- `actions/checkout` (paitsi providerin sisäinen infrastruktuuri)
- Build-työkalut, testikomennot, Docker-komennot
- Raporttien generointi
## Missä logiikka on
| Kerros | Tiedosto | Sisältö |
|---|---|---|
| Reititin | `ci-main.yml`, `ci-feature.yml` | Vain `uses:`-kutsut |
| Workflow_call | `ci-unit-tests.yml`, `ci-acc-tests.yml`, … | Varsinainen testi-/build-logiikka |
| Provider | `config-provider.yml`, `check-version.yml`, … | Jaettu infrastruktuuri |
| Skriptit | `scripts/` | Apufunktiot (status, publish, validointi) |
## Perustelu
Reitittimen ja toteutuksen erottelu:
- Reititin kertoo **mitä** ajetaan ja **missä järjestyksessä** — luettavissa
yhdellä silmäyksellä ilman teknistä taustaa
- Toteutus (workflow_call) kertoo **miten** — tekninen henkilö voi keskittyä
yhteen tiedostoon kerrallaan
- Providerin reusable workflow't eivät koskaan sisällä inline-logiikkaa —
skriptit ovat omissa tiedostoissaan
Tämä on sama periaate kuin web-sovelluksissa: reititin ei sisällä
business-logiikkaa, vain HTTP-verbit ja polut.
+30 -6
View File
@@ -1,6 +1,6 @@
# AI Context: Gitea Actions CI -kirjasto
**Updated**: 2026-06-15 (siivottu, provider/consumer-erottelu valmis)
**Updated**: 2026-06-19 (provider/consumer dual role, Project Skills -aktivointi)
## Project Overview
Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-,
@@ -11,8 +11,8 @@ käyttävät kirjastoa `uses:`-direktiivillä.
## Monorepo: kaksi erillistä kokonaisuutta
### 1. Juuri (`gitea-ci-library`)
Provider-kirjasto: reusable workflowt, scriptit, ADRt, dokumentaatio.
Consumer kutsuu provider-workflowta `uses:`-direktiivillä.
Provider- ja consumer-kirjasto: reusable workflowt, scriptit, ADRt, dokumentaatio,
ja consumer-esimerkit (dogfood). Consumer kutsuu provider-workflowta `uses:`-direktiivillä.
### 2. `git-pages/` — oma kokonaisuus
Helm-chartti Codeberg git-pagesille. Täysin itsenäinen — oma dokumentaatio,
@@ -26,21 +26,27 @@ kuuluu `git-pages/docs/`-alle, ei juuren `docs/`-kansioon.
| `.gitea/workflows/config-provider.yml` | Provider: lataa + validoi config-tiedoston, tuottaa `env_json` |
| `.gitea/workflows/check-version.yml` | Provider: tarkistaa onko commitille jo artifact, laskee version |
| `.gitea/workflows/docker-build-push.yml` | Provider: buildaa + puskea Docker-imagen, tagittaa commitin |
| `.gitea/workflows/ci-container-build-push.yml` | Provider: buildaa + puskea CI-työkalukontin |
| `.gitea/workflows/example-*` | **Consumer-esimerkki**: tämän repon oma CI (dogfood) |
| `scripts/` | Provider-skriptit: `report-status.sh`, `publish-git-pages.sh`, `ci-validate.sh` |
| `.gitea/scripts/` | **Consumer-skriptit**: `bats-coverage.sh`, `bats-report.sh` |
| `docs/` | Arkkitehtuuri, ADRt (00040008) |
| `skills/consumer-pipelines/` | Consumer-pipeline-standardit (ks. Project Skills). Koskee vain consumer-puolta |
| `skills/ci-container-build/` | CI-kontin build-workflow'n template (ks. Project Skills) |
| `docs/adr/` | Architecture Decision Records |
| `git-pages/` | Raporttien hostaus (Helm-chartti) |
| `tests/` | Bats-testit skripteille |
### Provider workflowt (3 kpl)
### Provider workflowt (6 kpl)
| Workflow | Input | Output | Kuvaus |
|---|---|---|---|
| `config-provider.yml` | `config_path` | `env_json`, `config_path` | Validoi ja jäsentää `.conf` → JSON. Sama kutsu hoitaa validoinnin. |
| `check-version.yml` | `env_json` | `artifact_exists`, `version` | Tarkistaa git-tagit ja `package.json`:n, laskee seuraavan version. Vain main-haarassa. |
| `docker-build-push.yml` | `env_json`, `version` | — | Buildaa Docker-imagen, puskea rekisteriin, tagittaa commitin. |
| `ci-container-build-push.yml` | `env_json`, `dockerfile_path`, `image_name`, `tag` | — | Buildaa CI-työkalukontin, puskea rekisteriin. Ei versiointia eikä git-tägäystä. |
| `report-summary.yml` | `env_json`, `suites` | — | Generoi `GITHUB_STEP_SUMMARY`-taulukon raporttilinkeillä (Gitea 1.27+) |
| `helm-build-push.yml` | `env_json`, `version` | — | Pakkaa + puskea Helm chartin OCI-registryyn, tagittaa commitin. **Tekninen velka:** asentaa node.js:n runtime-vaiheessa (`apk add --no-cache nodejs` ennen checkouttia) koska `alpine/helm`-kontissa ei ole nodea. Rikkoo Offline Container -periaatetta. Ratkaistaan myöhemmin: proper multi-tool CI-kontti (helm + nodejs + git) docker hubiin. Ei consumerin ongelma. |
### Example-tiedostot (consumer-referenssi)
@@ -50,12 +56,30 @@ kuuluu `git-pages/docs/`-alle, ei juuren `docs/`-kansioon.
| `example-main.yml` | push [main] | load-config → check-version → bats + cucumber → report-summary → docker-build-push |
| `example-bats-tests.yml` | workflow_call | Unit-testit Batsilla, raportit git-pagesiin, status linkillä |
| `example-cucumber-tests.yml` | workflow_call | Hyväksymätestit Cucumberilla, raportit git-pagesiin, status linkillä |
| `example-report-summary.yml` | workflow_call | `GITHUB_STEP_SUMMARY`-taulukko raporttilinkeillä (Gitea 1.27+) |
| `example-gitea-env.conf` | — | KEY=VALUE config tälle repolle |
## Provider & Consumer Dual Role
Tämä repo on **yhtä aikaa sekä provider että consumer**. Eri puolilla on eri säännöt:
- **Provider-puoli**: `.gitea/workflows/*.yml` (pl. `example-*`), `scripts/` — reusable workflowt joita muut projektit kutsuvat. Saa käyttää `docker run` -komentoja (esim. `docker-build-push.yml`). Consumer-pipeline-standardit (`skills/consumer-pipelines/`) eivät koske provideria.
- **Consumer-puoli**: `.gitea/workflows/example-*`, `.gitea/scripts/` — tämän repon oma CI (dogfood), toimii consumer-esimerkkinä. Käyttää `@main`-refiä provider-viittauksissa (sama repo). Noudattaa `skills/consumer-pipelines/`-sääntöjä.
- **Ulkoiset consumerit** käyttävät `@v1`-tagia provider-viittauksissa.
## Project Skills (skills/)
Tämä projekti sisältää omia `.ai/skills/`-järjestelmästä riippumattomia skillejä `skills/`-kansiossa. Jokainen alihakemisto sisältää `SKILL.md`:n jossa on `activation-gate`-kenttä.
**Sääntö:** Uuden tehtävän alussa skannaa `skills/*/SKILL.md` ja arvioi jokaisen `activation-gate` annettua tehtävää vasten. Jos gate matchaa, lataa skill aktiiviseksi ohjeeksi ennen toimenpiteitä.
| Skill | Gate | Kuvaus |
|---|---|---|
| `skills/consumer-pipelines/` | Consumer-pipeline-muutokset | Consumer-pipeline-standardit: reitittimen puhtaus, exit-koodi, konttipolitiikka, raportointi, nimeäminen. Koskee vain consumer-puolta. |
| `skills/ci-container-build/` | CI-kontin build | CI-kontin build-workflown template ja Dockerfile-ohjeet |
## Key Technical Decisions
- **Provider & Consumer -malli**: `example-*`-tiedostot ovat consumer-esimerkkejä, provider-workflowt reusableja. ADR 0005.
- **Provider & Consumer -malli**: Tämä repo on sekä provider että consumer. Provider-workflowt reusableja muille, `example-*`-tiedostot tämän repon oma consumer-CI (dogfood). ADR 0005.
- **Vain Gitea, vain reusable workflowt**: ei custom actioneita, ei multi-platform
- **Commit-status API vain raporttilinkeille**: Tool-jobit luottavat natiiviin. Test-jobit käyttävät API:a koska se on ainoa tapa upottaa raporttilinkki. ADR 0004, 0007.
- **Exit-koodi on ainoa onnistumisen mittari**: Ei pipeä, ei tiedostoheuristiikkaa. ADR 0008.
+1 -1
View File
@@ -33,7 +33,7 @@ Tarkemmin: ADR 0005.
| `example-main.yml` | Consumer | Main-haaran CI: load-config → check-version → bats + cucumber → summary → docker |
| `example-bats-tests.yml` | Consumer | Unit-testit Batsilla |
| `example-cucumber-tests.yml` | Consumer | Hyväksymätestit Cucumberilla |
| `example-report-summary.yml` | Consumer | `GITHUB_STEP_SUMMARY`-taulukko (Gitea 1.27+) |
| `report-summary.yml` | Provider | `GITHUB_STEP_SUMMARY`-taulukko raporttilinkeillä (Gitea 1.27+) |
| `publish-git-pages.sh` | Provider-skripti | PATCH tar git-pagesiin |
| `report-status.sh` | Provider-skripti | POSTaa commit-status (vain custom-linkkiin) |
| `ci-validate.sh` | Provider-skripti | Validoi `.conf`-tiedoston ja tarkistaa secretit |
+1 -1
View File
@@ -96,7 +96,7 @@ Avainkomponentit:
- **workflow `env:`** — ainoa tapa jakaa arvot kaikkiin jobeihin.
`fromJson(inputs.env_json).KEY` purkaa yksittäiset arvot ilman toistoa.
- **Per-job `env:`** — sisältää vain secretit (`GITEA_TOKEN`,
`GIT_PAGES_PUBLISH_TOKEN`), ei config-arvoja.
`REPORTS_PUBLISH_TOKEN`), ei config-arvoja.
## 5. Pipeline Provides All Dependencies
+10 -4
View File
@@ -60,10 +60,16 @@ Salaisuudet eivät ole `.conf`-tiedostossa. Ne määritellään Gitean
organization/repository secrets -mekanismissa ja välitetään workflowlle
`secrets: inherit` -direktiivillä.
**`secrets.GITEA_TOKEN` on Gitean automaattisesti generoima token,
scopeutuu siihen repoon jossa workflow ajaa.** Se ei oikeuta
dispatchaamaan toiseen repoon eikä kirjoittamaan toisen repon
commit-statusta. Cross-repo-operaatioihin tarvitaan manuaalinen
org-tason token.
| Secret | Pakollinen | Käyttäjä |
|---|---|---|
| `GITEA_TOKEN` | Kyllä | `report-status.sh`, `check-version.yml`, `docker-build-push.yml` |
| `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | `publish-git-pages.sh`, `config-provider.yml` (validointi) |
| `GITEA_TOKEN` | Kyllä | `report-status.sh`, `check-version.yml`, `docker-build-push.yml`, `gitops-update.sh` (GitOps-repossa) |
| `REPORTS_PUBLISH_TOKEN` | Kyllä | `publish-git-pages.sh`, `config-provider.yml` (validointi) |
| `DOCKER_USERNAME` | Ei | `docker-build-push.yml` (oletus: `github.actor`, ei pakollinen kaikissa registryissä) |
| `DOCKER_PASSWORD` | Kyllä | `docker-build-push.yml` |
@@ -82,12 +88,12 @@ JSON-muotoisen `env_json`:n.
- `.conf`-tiedosto on olemassa
- Jokaisella `KEY=VALUE`-rivillä on arvo (ei tyhjää)
- URL-tyyppiset avaimet alkavat `http://` tai `https://`
- Pakolliset secretit (`GITEA_TOKEN`, `GIT_PAGES_PUBLISH_TOKEN`) on asetettu
- Pakolliset secretit (`GITEA_TOKEN`, `REPORTS_PUBLISH_TOKEN`) on asetettu
Kutsu:
```yaml
load-config:
uses: org/gitea-ci-library/.gitea/workflows/config-provider.yml@main
uses: org/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: .gitea/workflows/gitea-env.conf
-390
View File
@@ -1,390 +0,0 @@
# Consumer Guide — Kirjaston käyttöönotto
> Anna tämä dokumentti AI:lle kun haluat ottaa `gitea-ci-library`:n käyttöön
> uudessa projektissa tai muokata olemassa olevia pipelineja.
---
## Rakenneperiaate
**Pipeline-tiedostot (`ci-feature.yml`, `ci-main.yml`) eivät saa sisältää
varsinaista logiikkaa.** Ne ovat puhtaita reitittimiä: pelkkiä `uses:`-kutsuja
`if`- ja `needs`-ehdoilla. Kaikki testien ajaminen, buildaus ja raportointi
kuuluu omiin `workflow_call`-tiedostoihinsa.
```
ci-unit-tests.yml ← testien ajaminen (varsinainen logiikka)
ci-acc-tests.yml ← hyväksymätestit (varsinainen logiikka)
ci-feature.yml ← reititin: load-config → test-workflow't → summary
ci-main.yml ← reititin: load-config → check-version → testit → build → summary
```
Provider tarjoaa 3 reusable workflow'ta ja joukon skriptejä.
Consumer omistaa orkestroinnin: mitä palikoita käytetään, missä järjestyksessä,
millä branch-ehdoilla. Consumer ei kopioi providerin koodia — se viittaa
`uses:`-direktiivillä.
---
## Vaihe 1: Konfiguraatiotiedosto
Luo `.gitea/workflows/gitea-env.conf`:
```ini
GITEA_API_URL=https://gitea.example.com
GIT_PAGES_URL=https://reports.example.com
```
Jos buildaat Docker-kontteja, lisää:
```ini
DOCKER_REGISTRY=gitea.example.com/myorg
DOCKER_IMAGE_NAME=my-service
DOCKER_UI_URL=https://gitea.example.com/myorg/-/packages/container/my-service
#DOCKERFILE=Dockerfile.platform # valinnainen, oletus Dockerfile
```
Salaisuudet määritellään Gitean Settings → Secrets -näkymässä:
| Secret | Pakollinen |
|---|---|
| `GITEA_TOKEN` | Aina |
| `GIT_PAGES_PUBLISH_TOKEN` | Aina |
| `DOCKER_USERNAME` | Vain jos buildaat kontteja (ei pakollinen kaikissa registryissä) |
| `DOCKER_PASSWORD` | Vain jos buildaat kontteja |
---
## Vaihe 2: Test-workflow't (varsinainen logiikka)
Jokainen testityyppi omaan `workflow_call`-tiedostoonsa. Tässä esimerkki
Maven-yksikkötesteistä. Luo `.gitea/workflows/ci-unit-tests.yml`:
```yaml
name: Unit Tests
on:
workflow_call:
inputs:
env_json:
required: true
type: string
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
jobs:
test:
runs-on: ubuntu-latest
container: maven:3.9-eclipse-temurin-21
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: org/gitea-ci-library
path: .ci
- name: Run tests
shell: bash
run: |
mvn test
EXIT=$?
echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
exit ${EXIT}
- name: Publish reports
if: always()
run: bash .ci/scripts/publish-git-pages.sh junit
- name: Report status
if: always()
shell: bash
run: |
if [ "${EXIT}" = "0" ]; then
bash .ci/scripts/report-status.sh success "Link to JUnit reports" unit-tests junit
else
bash .ci/scripts/report-status.sh failure "Link to JUnit reports" unit-tests junit
fi
```
Hyväksymätesteille vastaava tiedosto `ci-acc-tests.yml` (Cucumber, Playwright
tms.), jossa oma `container:`, oma testikomento ja oma `suite`-nimi.
**Tärkeää:** `mvn test` korvataan omalla testikomennolla. `container:` ja
`publish-git-pages.sh`-suite ovat projektikohtaisia. Muu runko pysyy samana.
---
## Vaihe 3: Feature-haaran CI (puhdas reititin)
Luo `.gitea/workflows/ci-feature.yml`. **Ei sisällä yhtään `run:`-steppiä**
— pelkkiä `uses:`-kutsuja:
```yaml
name: CI Feature
on:
push:
branches-ignore:
- main
workflow_dispatch:
jobs:
load-config:
uses: org/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: .gitea/workflows/gitea-env.conf
unit-tests:
needs: [load-config]
uses: ./.gitea/workflows/ci-unit-tests.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
acc-tests:
needs: [load-config]
uses: ./.gitea/workflows/ci-acc-tests.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
report-summary:
needs: [load-config, unit-tests, acc-tests]
if: always()
uses: org/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: junit cucumber
```
---
## Vaihe 4: Main-haaran CI (puhdas reititin)
Luo `.gitea/workflows/ci-main.yml`. **Ei sisällä yhtään `run:`-steppiä**:
```yaml
name: CI Main
on:
push:
branches:
- main
workflow_dispatch:
jobs:
load-config:
uses: org/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: .gitea/workflows/gitea-env.conf
check-version:
needs: [load-config]
uses: org/gitea-ci-library/.gitea/workflows/check-version.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
unit-tests:
needs: [load-config, check-version]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: ./.gitea/workflows/ci-unit-tests.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
acc-tests:
needs: [load-config, check-version]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: ./.gitea/workflows/ci-acc-tests.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
build-push:
needs: [load-config, check-version, unit-tests, acc-tests]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: org/gitea-ci-library/.gitea/workflows/docker-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
report-summary:
needs: [load-config, unit-tests, acc-tests]
if: always()
uses: org/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: junit cucumber
```
Mihin kiinnittää huomiota:
- `check-version` on **idempotentti** — jos commitilla on jo tagi, kaikki
sen jälkeiset jobit skipataan (`if: artifact_exists != 'true'`)
- `needs`-ketju takaa järjestyksen ja virheiden propagointin
- Artifakti voi olla **mitä tahansa**`docker-build-push.yml` on yksi
esimerkki. Voit korvata sen Maven-deploylla, npm-publishilla, tai millä
tahansa omalla build-workflow'lla. Rajapinta on `version`-input.
---
## Versionhallinta
`check-version.yml` lukee version automaattisesti prioriteettijärjestyksessä:
| # | Lähde | Formaatti | Esimerkki |
|---|---|---|---|
| 1 | `VERSION`-tiedosto | Plain text | `0.2` |
| 2 | `package.json` | `.version` | `"version": "0.2.0"` |
| 3 | `pom.xml` | `<version>` | `<version>0.2.0</version>` |
`major.minor` otetaan tästä. Patch (kolmas numero) lasketaan automaattisesti
git-tageista. Esim. jos `VERSION` on `0.2` ja tagit ovat `0.2.0`, `0.2.1`,
niin seuraava on `0.2.2`.
---
## Testien lisääminen — oma työkalu
Kopioi `ci-unit-tests.yml`:n rakenne uudelle testityypille ja muuta:
- `container:` — oma testikonttisi
- Testikomento — oma testityökalusi (`npm test`, `pytest`, `go test`, ...)
- `publish-git-pages.sh <suite>` — oma suite-nimi
- `report-status.sh ... <context> <suite>` — oma uniikki konteksti
Lisää uusi jobi reititintiedostoihin (`ci-feature.yml`, `ci-main.yml`)
samalla `uses:`-kaavalla.
Testijobit ajetaan rinnakkain — ne kaikki `needs: [load-config]` ilman
keskinäisiä riippuvuuksia.
### Tärkeimmät säännöt
1. **Exit-koodi aina ylös:**
```bash
run-tests
EXIT=$?
echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
exit ${EXIT}
```
2. **Ei pipeä testikomennon perään.** `command | tee file` syö exit-koodin.
Käytä `command > file 2>&1` jos haluat logit talteen.
3. **Status vain jos on raportti.** Testijobit käyttävät commit-status API:a
raporttilinkin takia. Tool-jobit luottavat Gitean natiiviin job-statukseen.
4. **`if: always()`** publish- ja status-stepeissä — raportti julkaistaan
ja status asetetaan vaikka testit feilaisivat.
### Raporttien generointi
`publish-git-pages.sh <suite>` odottaa hakemiston `reports/${SHA8}/<suite>/`
olevan olemassa. Sen sisältö sellaisenaan julkaistaan git-pagesiin.
`report-status.sh` linkittää statuksen suoraan tähän hakemistoon — selain
avaa sieltä `index.html`:n.
Test-workflow'n vastuulla on tuottaa raportit oikeaan polkuun. Kaksi
tyypillistä patternia:
**Pattern 1: Yksi raporttitiedosto (Cucumber)**
Testityökalu tuottaa suoraan HTML-raportin. Yksinkertaisin tapaus:
```bash
mkdir -p "reports/${GITHUB_SHA:0:8}/cucumber"
npx cucumber-js \
--format html:"reports/${GITHUB_SHA:0:8}/cucumber/index.html"
```
**Pattern 2: Monta raporttitiedostoa (Bats + coverage)**
Eri työkalut tuottavat eri tiedostoja. Generoi `index.html` joka linkittää
ne yhteen:
```
reports/${SHA8}/bats/
├── index.html ← generoitu: linkit alla oleviin
├── results.txt ← bats-testien stdout
├── coverage/ ← bashcov-coverage HTML
│ └── index.html
└── ...
```
```bash
mkdir -p "reports/${GITHUB_SHA:0:8}/bats"
# Aja testit → results.txt
bats tests/ > "reports/${GITHUB_SHA:0:8}/bats/results.txt" 2>&1
# Generoi coverage → coverage-hakemisto
bashcov -- bats tests/
# Generoi index.html joka linkittää kaikkiin raportteihin
cat > "reports/${GITHUB_SHA:0:8}/bats/index.html" <<'EOF'
<!DOCTYPE html>
<html><body>
<h1>Bats Test Reports</h1>
<ul>
<li><a href="results.txt">Test results (raw)</a></li>
<li><a href="coverage/index.html">Code coverage</a></li>
</ul>
</body></html>
EOF
```
Yhteistä molemmille: `publish-git-pages.sh <suite>`-kutsun jälkeen raportit
ovat julkisesti selailtavissa. `report-status.sh`-kutsu `suite`-parametrilla
linkittää commit-statuksen suoraan `index.html`:ään.
Jos testit feilasivat, raportti generoidaan silti — se kertoo MITKÄ testit
feilasivat. Siksi publish- ja status-stepit käyttävät `if: always()`.
---
## Branch protection (PR-gate)
Gitean Settings → Branches → Add Rule:
- **Branch:** `main`
- **Enable Require Status Checks:** päälle
- **Status checks:** valitse `unit-tests`, `acc-tests`
---
## Raporttien koonti (Gitea 1.27+)
Kun Gitea päivittyy versioon 1.27, `GITHUB_STEP_SUMMARY`-tuki mahdollistaa
raporttilinkkien koontinäkymän suoraan Gitea UI:ssa. `report-summary`-jobi
on mukana molemmissa reititinesimerkeissä yllä — forward-compatibeli, ei
hajota vanhemmilla versioilla.
---
## Provider-rajapinnat — referenssi
### Workflowt
| Workflow | Käyttötarkoitus |
|---|---|
| `config-provider.yml` | Lataa + validoi `.conf`, tuottaa `env_json` |
| `check-version.yml` | Tarkistaa onko commit buildattu, laskee version |
| `docker-build-push.yml` | Buildaa + puskea Docker-imagen, tagittaa |
| `report-summary.yml` | `GITHUB_STEP_SUMMARY`-taulukko raporttilinkeillä |
### Skriptit (kutsutaan `.ci/scripts/`-polun kautta)
| Skripti | Käyttötarkoitus |
|---|---|
| `report-status.sh` | POSTaa commit-statuksen linkillä |
| `publish-git-pages.sh` | Julkaisee raporttihakemiston git-pagesiin |
| `ci-validate.sh` | Validoi `.conf`-tiedoston (kutsutaan `config-provider.yml`:stä) |
+28 -211
View File
@@ -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) |
+551
View File
@@ -0,0 +1,551 @@
# git-pages → Nginx + PV -arkkitehtuuri
> Päivitetty: 2026-06-28
## Ongelma
git-pages storage v2 pakottaa kaikki tiedostot yhteen `.index`-protobufiin per site.
Raja on 1MB kovakoodattu. Yksi monorepo täytti tämän, eikä uusia raportteja voi
julkaista ennen kuin ongelma on ratkaistu.
## Ratkaisu
Poista git-pages kokonaan. Korvaa Nginx:llä (static serving) + kevyellä upload-sidecarilla (tar.gz HTTP PUT → extract). Tiedostot suoraan PV:llä ilman `.index`-välikerrosta.
## Lukitut päätökset
1. **git-pages poistuu** — sovellus ei ole mukana luku- eikä kirjoitusketjussa
2. **Nginx palvelee suoraan PV:ltä**`root /data`, `autoindex on`, `try_files $uri $uri/ $uri/index.html =404`
3. **Upload-sidecar** — busybox httpd + CGI, ottaa vastaan `PUT /path` + tar.gz body ja purkaa PV:lle
4. **URL = FS-polku** — ei Host-header-kikkaa, ei slugia, ei rewritea
5. **Traefik** — PATCH/PUT → upload-sidecar:8081 (BasicAuth), GET/HEAD → nginx:8080 (ForwardAuth myöh.)
6. **CI-julkaisu**`curl -X PUT --data-binary @report.tar.gz https://{host}/{owner}/{repo}/{branch_raw}/{sha8}/{suite}/`
7. **Branch raakana URL:ssa**`feature/x` URL:ssa = `feature/x/` FS:ssä. Ei slugitusta
8. **Linkki on aina 1-suuntainen** — gitea → raportti. Raportista ei linkkiä giteaan
9. **GITEA_API_URL poistuu** publish-skriptin pakollisista env-varista (retention käyttää omaansa)
## Ympäristömuuttujat
### GITHUB_REF_NAME
Gitea Actions runnerin automaattisesti asettama muuttuja, joka on branchin/tagin nimi ilman `refs/heads/`-etuliitettä.
| Tilanne | Arvo |
|---------|------|
| branch `main` | `main` |
| branch `feature/branch` | `feature/branch` |
| tag `v1.0.0` | `v1.0.0` |
Käytännössä `git rev-parse --abbrev-ref HEAD`. Branchin `/` on sallittu URL-polussa, joten `GITHUB_REF_NAME` voidaan käyttää sellaisenaan rakenteessa `${GITHUB_REF_NAME}/${SHA8}/${SUITE}`. Rinnakkaisissa buildeissa jokainen runner ajaa oman branchinsa kontekstissa → URL pysyy uniikkina.
```
CI → curl -X PUT → Traefik (BasicAuth middleware)
→ upload-sidecar:8081 (busybox httpd + CGI)
→ tar -xzf - -C /data/{owner}/{repo}/{branch}/{sha8}/{suite}/
→ PV /app/data/
Browser → GET → Traefik
→ nginx:8080 (root /app/data)
→ /data/{owner}/{repo}/{branch}/{sha8}/{suite}/index.html
```
## Tiedostomuutokset
### Poistuu (4 tiedostoa)
| Tiedosto | Miksi |
|----------|-------|
| `templates/init-job.yaml` | Ei enää git-pages API:a alustettavaksi |
| `templates/configmap.yaml` (nykyinen) | Korvataan nginx-conf + CGI |
| `templates/deployment.yaml` (nykyinen) | Korvataan uudella |
| `git-pages-image` (values.yaml) | Ei enää git-pages-konttia |
### Muuttuu (8 tiedostoa)
| # | Tiedosto | Muutos |
|---|----------|--------|
| 1 | `Chart.yaml` | description, poista appVersion |
| 2 | `values.yaml` | Poista git-pages-keyt, lisää nginx/sidecar |
| 3 | `dev-values.yaml` | Matchaa uusi values-rakenne |
| 4 | `templates/configmap.yaml` | `default.conf` (nginx) + `upload.cgi` |
| 5 | `templates/deployment.yaml` | 2 konttia: nginx:alpine (8080) + busybox httpd (8081) |
| 6 | `templates/ingressroute.yaml` | Backend: PATCH/PUT → upload:8081, GET/HEAD → nginx:8080 |
| 7 | `templates/service.yaml` | 2 porttia: http-read(8080), http-write(8081) |
| 8 | `templates/NOTES.txt` | Uusi URL + esimerkit |
### Pysyy ennallaan (8 tiedostoa)
| Tiedosto | Huomio |
|----------|--------|
| `templates/certificate.yaml` | TLS hostille |
| `templates/middleware.yaml` | BasicAuth + HTTPS redirect |
| `templates/publish-auth-secret.yaml` | Sama BasicAuth-secret |
| `templates/pvc.yaml` | Sama PVC |
| `templates/retention-configmap.yaml` | Päivitä retention-scriptit |
| `templates/retention-cronjob.yaml` | Pieni muutos (ei git-pages API) |
| `templates/retention-rbac.yaml` | Ennallaan |
| `_helpers.tpl` | Vain label-helpers |
### Chartin ulkopuolella
| Tiedosto | Muutos |
|----------|--------|
| `scripts/publish-git-pages.sh` | Uusi: `curl -X PUT` tar.gz upload-sidecariin |
| `scripts/report-status.sh` | URL: `reports/``${GITHUB_REF_NAME}/` |
| `scripts/ci-report.sh` | URL (rivi 101): `reports/``${GITHUB_REF_NAME}/` |
| `git-pages/docs/architecture.md` | Uusi arkkitehtuuri |
| `docs/design-rationale.md` | Päivitys |
| `files/retention-*.sh` | Lue PV:tä suoraan, ei git-pages API:a |
## Upload CGI
Busybox httpd välittää CGI-skriptille tiedot ympäristömuuttujissa (`REQUEST_METHOD`, `PATH_INFO`, `CONTENT_LENGTH`). Skripti on tiedosto `/cgi/upload.cgi``.cgi`-pääte laukaisee CGI-execution.
```bash
#!/bin/sh
# REQUEST_METHOD, PATH_INFO, etc. asettaa busybox httpd automaattisesti
if [ "$REQUEST_METHOD" = "PUT" ]; then
dest="/app/data${PATH_INFO%/}"
mkdir -p "$dest"
cat - | tar -xzf - -C "$dest"
echo "Status: 201 Created"
echo ""
fi
```
**PUT `https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`** → CGI purkaa:
- `PATH_INFO` = `/{owner}/{repo}/{branch}/{sha8}/{suite}/`
- `dest` = `/app/data/{owner}/{repo}/{branch}/{sha8}/{suite}`
- Tarin tiedostot puretaan tähän
**Huomio:** Skripti käyttää tar.gz-pakkausta. Jos tar on raaka (ilman gzip:ia), vaihda `-xzf``-xf`.
### CGI-kutsu
Busybox httpd käynnistetään:
```bash
busybox httpd -f -p 8081 -h /cgi
```
`-h /cgi` on docroot. Skriptitiedoston pääte `.cgi` (esim. `/cgi/upload.cgi`) käynnistää CGI-execution automaattisesti.
## Nginx-konfiguraatio
```nginx
client_max_body_size 100M; # nousevat raportit; oletus 1M on liian pieni
server {
listen 8080;
root /app/data;
autoindex on;
client_max_body_size 100M;
location / {
try_files $uri $uri/ $uri/index.html =404;
}
}
```
Alpine-nginx lukee configit `/etc/nginx/http.d/default.conf`. ConfigMap mountataan tähän polkuun.
## Deployment (kontit)
```yaml
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 8080
name: http-read
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/http.d
- name: data
mountPath: /app/data
- name: upload
image: alpine:latest
command:
- busybox
- httpd
- -f
- -p
- "8081"
- -h
- /cgi
ports:
- containerPort: 8081
name: http-write
volumeMounts:
- name: cgi-scripts
mountPath: /cgi
readOnly: true
- name: data
mountPath: /app/data
```
## values.yaml-rakenne
```yaml
# git-pages → Nginx + upload-sidecar
nameOverride: ""
fullnameOverride: ""
nginx:
image: nginx
tag: alpine
port: 8080
bodySize: 100M
upload:
image: alpine
tag: latest
port: 8081
service:
type: ClusterIP
persistence:
enabled: true
accessMode: ReadWriteOnce
storageClass: ""
size: 5Gi
ingress:
enabled: true
host: ci-reports.helm-dev.keskikuja.site
entryPoints:
websecure: websecure
web: web
certificate:
enabled: true
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
publishAuth:
create: false
htpasswdUsers: ""
retention:
enabled: false
mode: cronjob
schedule: "0 3 * * *"
image:
repository: debian
tag: bookworm-slim
giteaApiUrl: ""
rules:
branches:
default:
minAgeDays: 7
keepMin: 5
```
## CI-julkaisu (publish-skripti)
### Muutokset nykyiseen
| Kohta | Nykyinen | Uusi |
|-------|----------|------|
| `GITEA_API_URL` | pakollinen env-var | **poistettu** (retention käyttää omaansa) |
| Tar-prefiksi | `{owner}/{repo}/reports/{sha8}/{suite}` | ei prefiksiä — `{suite}` |
| Paketti | `tar -cf` | `tar -czf` (gzip) |
| Content-Type | `application/x-tar` | `application/tar+gz` |
| Headerit | `Atomic: no`, `Create-Parents: yes` | poistettu |
| Kohde | `PATCH {GIT_PAGES_URL}/` | `PUT {GIT_PAGES_URL}/{owner}/{repo}/{branch}/{sha8}/{suite}/` |
| Branch | vain `.meta` | URL:ssa + `.meta` |
### Uusi skripti (ydin)
```bash
#!/usr/bin/env sh
set -eu
SUITE_PATH="${1:-}"
[ -n "$SUITE_PATH" ] || { echo "ERROR: suite_path required" >&2; exit 1; }
[ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; }
[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${GITHUB_REPOSITORY:-}" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; }
[ -n "${GITHUB_SHA:-}" ] || { echo "ERROR: GITHUB_SHA is not set" >&2; exit 1; }
[ -n "${GITHUB_REF_NAME:-}" ] || { echo "ERROR: GITHUB_REF_NAME is not set" >&2; exit 1; }
OWNER="${GITHUB_REPOSITORY%%/*}"
REPO="${GITHUB_REPOSITORY##*/}"
SHA8=$(echo "$GITHUB_SHA" | cut -c1-8)
BRANCH="${GITHUB_REF_NAME}"
SUITE="${SUITE_PATH%/}"
PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}"
[ -d "$SUITE" ] || { echo "ERROR: not a directory: $SUITE" >&2; exit 1; }
WORK=$(mktemp -d)
TAR=$(mktemp)
trap 'rm -rf "$WORK" "$TAR"' EXIT
# Kopioi raporttitiedostot
mkdir -p "$WORK/$SUITE"
cp -a "$SUITE/." "$WORK/$SUITE/"
# Generoi index.html (sama logiikka kuin nykyään)
cd "$WORK/$SUITE"
if [ ! -f "index.html" ]; then
# identtinen item-listaus kuin nykyisessä skriptissä
fi
# .meta tiedosto retentionia varten
cat > ".meta" <<EOF
{"branch":"${BRANCH}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF
cd "$WORK"
# Pakkaa: pelkät tiedostot ilman $SUITE-etuliitettä
# CGI purkaa destinationiin /data/{owner}/{repo}/{branch}/{sha8}/{suite}/
# ilman --strip-components, joten tarin menee suoraan oikeaan paikkaan
tar czf "$TAR" -C "$WORK/$SUITE" .
# PUT upload-sidecariin
PUBLISH_URL="${GIT_PAGES_URL}/${OWNER}/${REPO}/${BRANCH}/${SHA8}/${SUITE}/"
HTTP_CODE=$(curl -sS -X PUT "$PUBLISH_URL" \
-u "${PAGES_USER}:${REPORTS_PUBLISH_TOKEN}" \
-H "Content-Type: application/tar+gz" \
--data-binary @"$TAR" \
-o /tmp/git-pages-publish-response.txt \
-w "%{http_code}")
case "$HTTP_CODE" in
200|201|204) ;;
*)
echo "ERROR: publish HTTP ${HTTP_CODE}" >&2
cat /tmp/git-pages-publish-response.txt >&2
exit 1
;;
esac
echo "${PUBLISH_URL}"
```
## Gitea commit -linkki (report-status.sh)
Gitean commit-näkymään tuleva raporttilinkki muodostetaan `scripts/report-status.sh`:ssa.
### Muutos
| Kohta | Nykyinen (rivi 21) | Uusi |
|-------|-------------------|------|
| URL | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}` |
`reports/``{GITHUB_REF_NAME}/`. Branchin raaka nimi URL-polussa on sallittu. Muu skripti (Gitea API -kutsu, token, json-body) pysyy identtisenä.
Tarkista: `GITHUB_REF_NAME` on oltava asetettu — Gitea Actions asettaa sen automaattisesti. Jos sitä tarvitaan tarkistuksena, lisätään `report-status.sh`:n env-var-tarkistuksiin.
## Gitea Actions step summary (report-summary.yml)
`.gitea/workflows/report-summary.yml` luo GITHUB_STEP_SUMMARY -taulukon raporttilinkeillä.
### Muutos
| Kohta | Nykyinen (rivi 28) | Uusi |
|-------|-------------------|------|
| BASE | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}` |
Sama muuri: `reports/``${GITHUB_REF_NAME}/`. Loppuosa identtinen.
## CI-report (ci-report.sh)
`scripts/ci-report.sh` rakentaa URL:n suoraan single-entry-tapauksessa (rivi 101).
### Muutos
| Kohta | Nykyinen (rivi 101) | Uusi |
|-------|-------------------|------|
| URL | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${SINGLE_ENTRY}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}/${SINGLE_ENTRY}` |
`reports/``${GITHUB_REF_NAME}/`. Lisäksi `GITHUB_REF_NAME` pitää lisätä env-var-tarkistuksiin, jos sitä ei ole.
## Retention
Retention siirtyy lukemaan PV:tä suoraan (find, ls, stat) git-pages API:n sijaan. Nykyinen malli luki `.git-pages/manifest.json` HTTP:lla — uusi lukee FS:ää.
### Data
```
/app/data/{owner}/{repo}/{branch}/{sha8}/
.meta ← {"branch":"...","sha":"...","published_at":"..."}
index.html
style.css
...
```
### Logiikka commit-tasolla
Retention on commit-kohtainen. Yksi commit = yksi kansio `/{sha8}/`, jonka alla voi olla useita suiteja (`cucumber/`, `bats/`, jne.).
```
/app/data/{owner}/{repo}/{branch}/
abc12345/ ← yksi commit
cucumber/
index.html
.meta
bats/
index.html
.meta
def67890/ ← toinen commit
cucumber/
index.html
.meta
```
Toiminta aktiiviselle branchille (löytyy Giteasta):
1. Listaa commit-kansiot (`abc12345/`, `def67890/`, ...) uusin ensin (viimeksi muokattu)
2. Ohita `keepMin` kpl — nämä säilytetään aina
3. Jos raportti on alle `minAgeDays` päivää vanha → skip (liian tuore poistettavaksi)
4. Muut poistetaan (`rm -rf {sha8}/`), jolloin kaikki commitin suite-tulokset poistuvat kerralla
Poistuneelle branchille (404 Giteasta): poista koko `{branch}/`-kansio.
### Parametrit
```yaml
retention:
rules:
branches:
default:
keepMin: 5
minAgeDays: 7
main:
keepMin: 20
minAgeDays: 14
```
**Parametrin nimi:** `maxAgeDays``minAgeDays` (arvo sama, uusi nimi kertoo mitä se todella tekee: minimi-ikä ennen poistoa).
### Skripti (ydin)
```bash
DATA_ROOT="/app/data"
for owner_dir in "$DATA_ROOT"/*/; do
owner=$(basename "$owner_dir")
for repo_dir in "$owner_dir"*/; do
repo=$(basename "$repo_dir")
for branch_dir in "$repo_dir"*/; do
branch=$(basename "$branch_dir")
branch_urlenc=$(echo "$branch" | sed 's/\//%2F/g')
# Tarkista onko branch yhä olemassa Giteassa
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token $GITEA_TOKEN" \
"$GITEA_API_URL/api/v1/repos/$owner/$repo/branches/$branch_urlenc")
if [ "$HTTP_CODE" = "404" ]; then
# Branch poistettu — tuhoa koko branch
rm -rf "$branch_dir"
continue
fi
# Aktiivinen branch — hae säännöt
rules=$(jq -r ".branches.\"$branch\" // .branches.default" "$RETENTION_CONFIG")
keep_min=$(echo "$rules" | jq -r '.keepMin // 5')
min_age=$(echo "$rules" | jq -r '.minAgeDays // 7')
# Listaa commit-kansiot uusin ensin
commits=$(ls -1t "$branch_dir")
count=0
for sha8 in $commits; do
[ -d "$branch_dir/$sha8" ] || continue
count=$((count + 1))
# Ohita keepMin kpl
[ "$count" -le "$keep_min" ] && continue
# Tarkista ikä
age_seconds=$(( $(date +%s) - $(stat -c %Y "$branch_dir/$sha8") ))
age_days=$(( age_seconds / 86400 ))
[ "$age_days" -lt "$min_age" ] && continue
# Poista koko commit
rm -rf "$branch_dir/$sha8"
done
done
done
done
```
## Deploy-järjestys (vaiheistus)
### Vaiheet
| # | Vaihe | Mitä | Riippuvuus |
|---|-------|------|-----------|
| 1 | **Poista vanha PVC** | `kubectl delete pvc git-pages-data -n gitea-reports` (tai helm uninstall + uusi asennus) | Vanha git-pages-data .index-muodossa — tuhotaan |
| 2 | **Deployaa uusi chart** | `helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml` | Uusi PVC luodaan tyhjänä. Vanhoja raportteja ei ole. |
| 3 | **Varmenna Nginx** | `curl localhost:8080/` → 404/autoindex | Nginx palvelee |
| 4 | **Varmenna upload** | `curl -X PUT -T test.tar.gz localhost:8081/test/` → 201 | CGI toimii |
| 5 | **Päivitä provider-skriptit** | `scripts/publish-git-pages.sh`, `scripts/report-status.sh`, `scripts/ci-report.sh` | Uusi URL-muoto |
| 6 | **Päivitä provider-workflow** | `.gitea/workflows/report-summary.yml` | BASE-muuttuja |
| 7 | **Varmenna CI-julkaisu** | `bash publish-git-pages.sh <test-suite>` → 201 + URL | Koko ketju |
### Consumer-workflowt EI päivitetä
`.gitea/workflows/example-*` ja `.ci/scripts/*` (consumerien kopioimat skriptit) eivät muutu.
### report-summary.yml
On provider-workflow (ei `example-`-prefiksiä). Saa päivittyä.
## Rollback-suunnitelma
### Jos chart epäonnistuu
```bash
helm rollback git-pages <revision>
git revert HEAD # tai git reset --hard ennen script-muutoksia
```
- Vanha PVC on poistettu → rollback palauttaa vanhan git-pages-deploymentin mutta ilman dataa
- Vanhaa `.index`-dataa ei palaudu — se tuhottiin vaiheessa 1
- Jos uutta dataa on ehtinyt kertyä, se on uudella PVC:llä → vanha deployment ei löydä sitä
### Suojaus
Pidä vanha PVC hengessä (nimeä uudelleen, älä poista) kunnes rollback-ikkuna on umpeutunut:
```bash
# Ennen vaihetta 1: uudelleennimeä vanha PVC
kubectl delete pvc git-pages-data -n gitea-reports --wait=false
kubectl patch pvc git-pages-data -n gitea-reports -p '{"metadata":{"finalizers":[]}}' --type merge # tarvittaessa
```
Jos rollback tarvitaan 24h sisällä, palauta vanha PVC backupista (esim. snapshot).
## Varmennus (check-stepit)
Jokaiselle komponentille check ennen seuraavaan vaiheeseen siirtymistä:
| Vaihe | Check |
|-------|-------|
| **helm template** | `helm template gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml` → deploymentissa 2 konttia (nginx + upload) |
| **Nginx** | `kubectl exec <pod> -- 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 <pod> -- ls /app/data/test/hello/` → tiedostot näkyvissä |
| **Retention** | `kubectl exec <pod> -- bash /scripts/retention-run.sh` → exit 0, ei virheitä |
| **Provider-skripti (end-to-end)** | 1. `kubectl port-forward pod/<pod> 8080:8080 8081:8081` 2. `bash scripts/publish-git-pages.sh <test-suite>` → output URL (`https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`) 3. `curl http://localhost:8080/{owner}/{repo}/{branch}/{sha8}/{suite}/` → 200, HTML näkyy 4. `curl -I http://localhost:8080/{owner}/{repo}/{branch}/{sha8}/{suite}/index.html` → 200 |
## Data migration
**Ei migraatiota.** Vanha PVC sisältää git-pages storage v2 -dataa (`.index` protobuf). Tämä data tuhotaan tarkoituksella:
1. CI on rikki 1MB rajoitteen takia — uusia raportteja ei voi julkaista ennen kuin git-pages on poistettu
2. Vanhat raportit ovat vanhentuneita (branchit on jo saatettu poistaa, commitit vanhoja)
3. Uudet buildit tuottavat raportit uuteen järjestelmään ilman migraatiota
**Toimenpide:**
- Poista vanha PVC ennen uuden chartin deployausta
- Uusi chart luo uuden tyhjän PVC:n
- CI ajaa uudet buildit → raportit kirjoitetaan Nginx+PV-malliin
+28 -7
View File
@@ -79,7 +79,7 @@ publish-git-pages.sh <suite>
|---|---|
| `GITEA_API_URL` | `env_json` → workflow `env:` |
| `GIT_PAGES_URL` | `env_json` → workflow `env:` |
| `GIT_PAGES_PUBLISH_TOKEN` | Gitea secret → `env:` |
| `REPORTS_PUBLISH_TOKEN` | Gitea secret → `env:` |
| `GITHUB_REPOSITORY` | Automaattinen |
| `GITHUB_SHA` | Automaattinen |
@@ -104,7 +104,7 @@ Lukee tiedoston polun `CI_CONF_FILE`-env-muuttujasta (oletus: `.gitea/workflows/
- Jokaisella `KEY=VALUE`-rivillä on arvo (ei tyhjää)
- URL-tyyppiset avaimet alkavat `http://` tai `https://`
- `GITEA_TOKEN` on asetettu
- `GIT_PAGES_PUBLISH_TOKEN` on asetettu
- `REPORTS_PUBLISH_TOKEN` on asetettu
---
@@ -113,17 +113,38 @@ Lukee tiedoston polun `CI_CONF_FILE`-env-muuttujasta (oletus: `.gitea/workflows/
Dispatchaa workflow'n toisessa repossa ja pollaa sen valmistumista synkronisesti.
Käytetään GitOps-deploymentissa ja klusteritestien ketjutuksessa (tuleva).
Generoi automaattisesti `dispatch_id`-tunnisteen, lisää sen dispatch-
inputteihin ja tunnistaa workflow-runin kohdereposta `display_title`-
kentän perusteella. Toimii luotettavasti vaikka samassa repossa olisi
useita samanaikaisia ajoja.
**Kohde-workflow'ssa on oltava `dispatch_id`-input ja `run-name`-kenttä
`display_title`-matchausta varten.** Katso `skills/gitops-update/SKILL.md`.
### Rajapinta
```bash
dispatch-workflow.sh <target_repo> <workflow_file> <ref> <inputs_json> [timeout_minutes]
dispatch-workflow.sh <target_repo> <workflow_file> <ref> <inputs_json> <gitea_api_url> <gitea_token> [timeout_minutes]
```
| Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------|
| `target_repo` | Kyllä | `owner/repo` |
| `workflow_file` | Kyllä | Workflow-tiedosto (esim. `ci-main.yml`) |
| `ref` | Kyllä | Branch |
| `inputs_json` | Kyllä | JSON-objekti dispatch-inputteina |
| `gitea_api_url` | Kyllä | Gitean API-URL |
| `gitea_token` | Kyllä | Gitea API -token (write kohderepoon) |
| `timeout_minutes` | Ei | Aikakatkaisu (oletus 360) |
### Toiminta
1. **Dispatch:** `POST /api/v1/repos/{target_repo}/actions/workflows/{workflow_file}/dispatches`
2. **Poll:** `GET /api/v1/repos/{target_repo}/actions/runs` → odota valmistumista
3. **Palauta:** `conclusion` (`success`/`failure`/`timeout`)
1. **Generoi `dispatch_id`** — 8-hex uniikki tunniste
2. **Injektoi** `dispatch_id` inputteihin
3. **Dispatch:** `POST /api/v1/repos/{target_repo}/actions/workflows/{workflow_file}/dispatches`
4. **Etsi run:** pollaa rinnakkaisia `workflow_dispatch`-runeja, matchaa `display_title` sisältää `dispatch_id`:n
5. **Poll:** `GET /api/v1/repos/{target_repo}/actions/runs/{run_id}` — odota valmistumista
6. **Palauta:** exit 0 (success), exit 1 (failure), exit 124 (timeout)
---
@@ -134,7 +155,7 @@ dispatch-workflow.sh <target_repo> <workflow_file> <ref> <inputs_json> [timeout_
| `GITEA_API_URL` | `env_json` | `report-status.sh`, `ci-validate.sh` |
| `GIT_PAGES_URL` | `env_json` | `publish-git-pages.sh`, `report-status.sh` |
| `GITEA_TOKEN` | Gitea secret | `report-status.sh`, `check-version.yml`, `docker-build-push.yml` |
| `GIT_PAGES_PUBLISH_TOKEN` | Gitea secret | `publish-git-pages.sh` |
| `REPORTS_PUBLISH_TOKEN` | Gitea secret | `publish-git-pages.sh` |
| `GITHUB_REPOSITORY` | Automaattinen | Kaikki skriptit |
| `GITHUB_SHA` | Automaattinen | Kaikki skriptit |
| `GITHUB_RUN_ID` | Automaattinen | `report-status.sh` |
+1 -1
View File
@@ -38,4 +38,4 @@ suoraan Gitea UI:ssa.
| **Multi-Git-platform** | Vain Gitea — yksi alusta kunnolla (periaate 10) |
| **Custom actionit** | Reusable workflow on kevyempi ja natiivimpi (periaate 2) |
| **Ulkoinen orkestraattori** | Gitean `needs` + `if` hoitaa ohjauksen |
| **Artifactory/Nexus** | Gitea Packages riittää MVP:ssä |
| **Artifactory/Nexus** | Build & push toimii Docker-standardilla. UI-tason linkitys (`report-summary`) vaatii Nexus/Artifactory-spesifin URL-rakenteen — ei vielä toteutettu, toteutetaan tarvittaessa |
+185 -11
View File
@@ -32,7 +32,7 @@ Kaikki workflowt:
| Secret | Pakollinen | Kuvaus |
|--------|------------|--------|
| `GITEA_TOKEN` | Kyllä | Validointia varten |
| `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | Validointia varten |
| `REPORTS_PUBLISH_TOKEN` | Kyllä | Validointia varten |
**Outputs:**
@@ -63,13 +63,100 @@ checkout → laske versio package.json + git-tageista → output
**Trigger:** `workflow_call`
**Inputs:** `env_json`, `version`
**Inputs:**
| Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
| `version` | Kyllä | Version string (check-version output) |
**`env_json`-avaimet:**
| Avain | Pakollinen | Kuvaus |
|-------|------------|--------|
| `DOCKER_REGISTRY` | Kyllä | Registry (esim. `gitea.app.keskikuja.site/niko`) |
| `DOCKER_IMAGE_NAME` | Kyllä | Kuvan nimi ilman registry-polkua |
| `DOCKER_UI_URL` | Ei | Registry UI -linkki raportointia varten |
| `DOCKERFILE` | Ei | Dockerfile-polku, oletus `Dockerfile` |
| `GITEA_API_URL` | Kyllä | Gitean API-URL |
| `GIT_TAG_PREFIX` | Ei | Tag-prefix (esim. `docker/`) |
**Secrets:** `GITEA_TOKEN`, `DOCKER_USERNAME`, `DOCKER_PASSWORD`
**Steppi-kaavio:**
```
build-push (build + push samassa jobissa, ei levyn kautta) → tag-commit
build-push (build + push, labelit: commit+date) → tag-commit (git-tagin luonti)
```
**Huomio:** Ei käytä `container:`-direktiiviä — ajaa suoraan runnerilla,
joten `actions/checkout` toimii ilman node-asennuksia.
---
### `helm-build-push.yml` — Helm chart build & push
**Trigger:** `workflow_call`
**Inputs:**
| Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
| `version` | Kyllä | Version string (check-version output) |
| `chart_path` | Kyllä | Polku Chart.yaml-hakemistoon |
| `extra_dependency_paths` | Ei | Pilkulla erotellut polut subcharttien dependeinceille, joille ajetaan `helm dependency update` ennen päächartin buildia |
**`env_json`-avaimet:**
| Avain | Pakollinen | Kuvaus |
|-------|------------|--------|
| `HELM_REGISTRY` | Kyllä | OCI-registry (esim. `gitea.app.keskikuja.site/niko`) |
| `HELM_UI_URL` | Ei | Registry UI -linkki raportointia varten |
| `GITEA_API_URL` | Kyllä | Gitean API-URL |
| `GIT_TAG_PREFIX` | Ei | Tag-prefix (esim. `helm/`) |
**Secrets:** `GITEA_TOKEN`, `HELM_USER`, `HELM_PASSWORD`
**Steppi-kaavio:**
```
build-push (helm package → helm push OCI) → tag-commit (git-tagin luonti)
```
**Steppien kuvaus `build-push`-jobissa:**
1. **Node.js-asennus**`apk add --no-cache nodejs` (vaaditaan `actions/checkout`-actionia varten)
2. **Checkout** — sovellusrepo ja gitea-ci-library `.ci/`-polkuun
3. **Resolve extra subchart dependencies**`helm dependency update` jokaiselle `extra_dependency_paths`-polulle (vain jos input on annettu)
4. **Package**`helm dependency update` + `helm package` versiolla `$VERSION`
5. **Push OCI**`helm push` registryyn autentikoinnilla
6. **Report status** — commit-status + UI-linkki
**Kompromissi:** Kontti `alpine/helm` ei sisällä node.js:ää, mutta
`actions/checkout@v4` on JavaScript-action ja vaatii sen. Siksi nodejs
asennetaan lennossa ennen checkouttia. Tämä vaatii internet-yhteyden
eikä toimi air gap -ympäristössä. Korvaa tarvittaessa custom-kontilla
(jossa helm + nodejs, ks. `skills/ci-container-build/SKILL.md`).
---
### `gitops-dispatch.yml` — GitOps-päivityksen dispatch
**Trigger:** `workflow_call`
**Inputit:**
| Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------|
| `env_json` | Kyllä | Konffi, josta luetaan `GITOPS_FILE`, `GITOPS_YQ_TPL`, `GITOPS_REPO`, `GIT_TAG_PREFIX` |
| `version` | Kyllä | Päivitettävä versio (check-version output) |
| `component` | Kyllä | `chart` tai `container` — tunniste summary-riville |
**Secretit:** `GITOPS_TOKEN`
**Outputit:** `summary` — pipe-formaatti: `{component}|{version}|{status}|{commit_sha}|{repo}`
**Steppi-kaavio:**
```
checkout → gitops-dispatch.sh → dispatch-workflow.sh → GITOPS_SUMMARY output
```
---
@@ -89,11 +176,23 @@ load-config → bats + cucumber → report-summary (always)
**Trigger:** `push` [branches: main]
```
load-config → check-version →
[artifact exists] → done
[no artifact] → bats + cucumber → report-summary (always) → docker-build-push
load-config ───────────────────────────────────────────────────────┐
load-config-helm ───────────────────────────────────────────┐ │
│ │
check-version ←─────────────────────────────────────────────┘ │
│ │
└→ bats + cucumber │
├─ docker-build-push → gitops-container ─┐ │
└─ helm-build-push → gitops-chart ──────┤ │
├→ report-summary ←┘
tag-maintenance ←────────────────────────┘
```
GitOps-jobit (`gitops-chart`, `gitops-container`) käyttävät
`gitops-dispatch.yml`-provider-workflowia. Kaksisuuntainen track:
dispatch-workflow.sh → GITOPS_COMMIT + GITOPS_SUMMARY.
Katso [skills/gitops-update/SKILL.md](../skills/gitops-update/SKILL.md).
### `example-bats-tests.yml` — Bats unit-testit
**Trigger:** `workflow_call`
@@ -108,11 +207,16 @@ raportit git-pagesiin, asettaa commit-statuksen linkillä raporttiin.
Ajaa Cucumber-testit Node-kontissa, julkaisee raportit git-pagesiin, asettaa
commit-statuksen linkillä raporttiin.
### `example-report-summary.yml` — Raporttien koontinäkymä
### `report-summary.yml` — Raporttien koontinäkymä
**Trigger:** `workflow_call` — ajetaan `if: always()` testien jälkeen
**Inputs:** `env_json`, `suites` (space-separated lista suite-nimistä)
**Inputs:** `env_json`, `suites` (space-separated lista suite-nimistä), `gitops` (optional JSON array)
**GitOps-tuki:** Jos `gitops` input on annettu (JSON array objekteilla
`component`, `version`, `status`, `commit`, `repo`), workflow lisää
GitOps-päivitystaulukon testiraporttien perään. Jokaiselle riville
muodostuu linkki GitOps-repon committiin.
Generoi Markdown-taulukon `GITHUB_STEP_SUMMARY`:yn kaikista julkaistuista
raporteista. Renderöityy HTML:ksi Gitea 1.27+ Summary-välilehdellä.
@@ -120,7 +224,77 @@ Forward-compatibeli — ei haittaa vanhemmilla Gitea-versioilla.
---
## Suunnitteilla
## Provider-skriptit
- `deploy.yml` — GitOps-deployment (dispatch-workflow.sh-pohjainen)
- `test.yml` — Klusteritason test flow
### `gitops-update.sh` — GitOps-version päivitys
**Riippuvuudet:** `yq`, `scripts/report-status.sh`, `git`
Päivittää GitOps-repon konfiguraatiotiedoston versionumeron `yq`:lla,
committaa muutoksen ja asettaa commit-statuksen molempiin repoihin
(kaksisuuntainen track):
| Status | Mihin repo | Context | Linkki |
|---|---|---|---|
| ✅ | **GitOps-repo** | `source/{repo}` | Code-repon committiin |
| ✅ | **Code-repo** (dispatchin jälkeen) | `gitops/{repo} {RUN_ID}` | GitOps-repon committiin |
**Input-ympäristömuuttujat (ajetaan GitOps-repon workflow'ssa):**
| Muuttuja | Pakollinen | Kuvaus |
|---|---|---|
| `INPUT_FILE` | Kyllä | Tiedosto GitOps-repossa (esim. `dev/Chart.yaml`) |
| `YQ_TPL` | Kyllä | `yq`-lauseke `{{VERSION}}`-placeholderilla |
| `VERSION` | Kyllä | Uusi versio (esim. `0.2.3`) |
| `SOURCE_REPO` | Kyllä | Lähdekoodirepo (esim. `org/app`) |
| `SOURCE_COMMIT` | Kyllä | Lähdekoodin commit-SHA |
| `GITOPS_REPO` | Kyllä | GitOps-repo slug |
| `GITEA_API_URL` | Kyllä | Gitean API-URL |
| `GITEA_TOKEN` | Kyllä | Gitea API-token (write GitOps-repoon) |
| `GITOPS_BRANCH` | Ei | GitOps-repon branch (oletus `main`) |
| `GIT_TAG_PREFIX` | Ei | Komponentin tag-prefix status-nimeämiseen |
**Commit-status (GitOps-repoon):**
| Kenttä | Formaatti | Esimerkki |
|--------|-----------|-----------|
| Context | `source/{repo}` | `source/gitea-ci-library` |
| Description | `Install to {env} {version}` | `Install to dev 0.2.0` |
| Target URL | Linkki code-repon committiin | `/org/repo/commit/sha` |
`{env}` parsitaan `INPUT_FILE`:stä (`dev/Chart.yaml``dev`).
**Steppikuvaus:**
1. Korvaa `YQ_TPL`:n `{{VERSION}}` versiolla
2. Muodostaa `CLONE_URL` tokenilla ja hostilla
3. Kloonaa GitOps-repon
4. Ajaa `yq eval -i` päivittääkseen tiedoston
5. Jos muutoksia: commit + push `[skip ci]`, muuten status `— no change`
6. Asettaa commit-statuksen GitOps-repoon (source-konteksti, linkki code-repoon)
**Scriptiä ei ajeta code reposta.** Se ajaa GitOps-repon workflow'ssa.
### Code-repon commit-status (dispatchin jälkeen)
GitOps-päivityksen valmistuttua `dispatch-workflow.sh` tulostaa
`GITOPS_COMMIT=<sha>` (GitOps-repon commitin SHA). Code repo asettaa
oman commit-statusinsa linkillä GitOps-committiin:
| Kenttä | Formaatti | Esimerkki |
|--------|-----------|-----------|
| Context | `gitops/{repo} {RUN_ID}` | `gitops/gitea-ci-library 473` |
| Description | `Install to {env} {version}` | `Install to dev 0.2.0` |
| Target URL | Linkki GitOps-repon committiin | `/niko/gitea-ci-gitops-tests/commit/def456` |
### Loppuraportti (GITHUB_STEP_SUMMARY)
`report-summary.yml` (optio `gitops`-inputti) lisää GitOps-rivit
GITHUB_STEP_SUMMARYyn:
| Component | Version | Status | GitOps commit |
|---|---|---|---|
| helm | 0.2.0 | success | [link](...) |
Kokonainen esimerkki molemmista puolista: [skills/gitops-update/SKILL.md](../skills/gitops-update/SKILL.md)
ja [.gitea/workflows/example-main.yml](../.gitea/workflows/example-main.yml).
---
-6
View File
@@ -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"
-142
View File
@@ -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.
-198
View File
@@ -1,198 +0,0 @@
# Design Rationale — git-pages
> Miksi git-pages on rakennettu näin. Arvot, periaatteet ja reunaehdot.
>
> Tämä dokumentti on **normatiivinen** `git-pages/`-alikansiolle. Se ei kuvaa juuren
> `gitea-ci-library`-kirjastoa eikä sen workfloweja.
>
> Liittyvät dokumentit: [architecture.md](architecture.md), [tech-stack.md](tech-stack.md), [secrets.md](secrets.md).
---
## Miksi tämä on olemassa
### Ongelma
CI-testiajoista syntyy HTML-raportteja. Esimerkiksi Cucumber-testiraportti toimii
elävänä dokumentaationa git commitin tilasta.
Gitea ei tarjoa web-selaimella selattavaa arkistoa näille HTML-raporteille.
git-pages ratkaisee tämän ongelman.
| Vaihtoehto | Miksi ei riitä |
|---|---|
| **Gitea Actions -artifactit** | Vain ZIP-lataus — HTML ei renderöidy selaimessa |
| **Gitea `pages`-branch** | Yksi branch per repo; rinnakkaiset buildit törmäävät saman branchin pushissa |
| **Gitea Releases** | Sotkee julkaisuhistorian satojen CI-buildien raporteilla |
### Ongelma URL:ssa (hylätty malli)
Alkuvaiheen malli sitoi hostin repoon: `https://{owner}.{host}/{repo}/...`
(subdomain per owner). Julkinen linkki piti sitten “kääntää” Gitea-tyyliseksi poluksi
Traefik-rewritellä (`/{owner}/{repo}/...` → eri `Host` + lyhyempi polku).
Tämä oli ongelmallinen:
- per-owner middleware / rewrite kube-resursseina
- julkaisu-URL ja lukemis-URL eri muodossa
- wildcard-TLS tai monimutkainen cert-hallinta
- vaikea selittää kehittäjälle mistä host tulee
### Ratkaisu — `selvä_url` + Gitea-yhteensopiva polku
URL rakennetaan kahdesta erillisestä osasta, ei yhdestä sekavasta kaavasta:
| Osa | Mistä | Esimerkki |
|-----|-------|-----------|
| **Selvä URL** | Organisaation kiinteä pages-host | `https://pages.example.com` |
| **Gitea-yhteensopiva polku** | Repo (`{owner}/{repo}`) + commit | `/acme-corp/backend-api/reports/abc12345/index.html` |
**Julkinen linkki** = selvä URL + polku (yksi merkkijono commit-statusiin, ei rewritea):
`https://pages.example.com/acme-corp/backend-api/reports/abc12345/index.html`
Polku vastaa Gitea Pages -käytäntöä (`/{owner}/{repo}/...`). Host on aina sama —
ei `{owner}.pages...`-subdomainia.
**Konkreettinen esimerkki (nykyinen ympäristö):**
| Elementti | Arvo |
|-----------|------|
| **Gitea-instanssi** | `gitea.app.keskikuja.site` |
| **Repo** | `niko/gitea-ci-library` |
| **Haara** | `plan/0003-alkaa-käyttämään-itseään-commit-raportti` |
| **Commit SHA** | `14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb` |
| **Raportin nimi** | `cucumber` (esim.) |
| **Gitea commit -URL** | `https://gitea.app.keskikuja.site/niko/gitea-ci-library/commit/14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb` |
| **Raportin julkinen URL** | `https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/commit/14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb/cucumber/index.html` |
Tämä varmistaa, että CI-statuslinkki on suoraan luettavissa ilman domain-rewriteä: raportin polku peilaa täsmälleen Gitean commit-polun rakennetta (`/{owner}/{repo}/commit/{sha}/{raportin-nimi}/`). Koska yksi ajo tuottaa useita raportteja, raportin nimi erottaa ne toisistaan.
Julkaisija (CI tai muu asiakas) lähettää tar-arkiston PATCH/PUT:lla. Lukija hakee
HTML:n GET:llä. Ei Gitea-git-integraatiota eikä `pages`-branchia.
**Codebergin security-malli ei sovellu tähän käyttöön** — forge-auth (Gitea PAT +
`write:repository`), DNS TXT -haaste ja muut git-pagesin sisäänrakennetut valtuutus-
mekanismit on ohitettu kokonaan (`PAGES_INSECURE=1`). Niiden sijaan Kubernetes-kerros
hoitaa rajauksen: Traefik BasicAuth julkaisuun, cert-manager TLS:ään, erillinen
publish-token ([secrets.md](secrets.md)). Sovellus palvelee sisältöä; klusteri päättää
kuka saa kirjoittaa.
---
## Suunnitteluperiaatteet
### 1. Selvä URL + Gitea-yhteensopiva polku
Julkinen osoite = kiinteä apex-host + polku `/{owner}/{repo}/reports/{sha8}/...`.
Apex-juuri `/` on tyhjä tarkoituksella — ei landing-sivua.
**Miksi:** Kehittäjä näkee Gitea-tyylisen polun; infra näkee yhden hostin. Ei Traefik-
rewritea, ei per-owner subdomaineja, ei erillistä “julkaisu-URL vs. lukemis-URL” -kaavaa.
Yksi TLS-sertifikaatti, yksi IngressRoute, yksi PVC.
### 2. Sovelluksen sisäinen security kytketty pois, Traefik hoitaa rajauksen
`git-pages`-sovelluksen koko sisäinen security-mekanismi on kytketty pois päältä (`PAGES_INSECURE=1`). Kirjoitusoikeuden validointi tapahtuu yksinomaan Kubernetes-reunalla Traefik BasicAuth -middlewaren avulla. Sovellus palvelee sisältöä sokeana; klusteri päättää, kuka saa kirjoittaa.
### 3. Julkaisu ja luku erotettu
Julkaisu (PATCH/PUT) vaatii Traefik BasicAuthin. Luku (GET/HEAD) on erillinen reitti — katso [Luku-auth](#luku-auth) alla.
**Miksi:** Koska sovellus ei validoi julkaisuoikeuksia, kirjoitusoikeus on eksplisiittisesti eriytetty Traefik Middlewaressä (`git-pages-publish-auth`).
### 4. Yksi publish-token, kaksi säilöä
Sama plaintext-token: klusterin Secretissä htpasswd-hashina, julkaisijan secret-holvissa
(esim. CI-alustan Actions-secret).
**Miksi:** Ei Gitea PAT:ia eikä `write:repository` -oikeutta. Token antaa vain
julkaisuoikeuden tähän palveluun. Yksi arvo, kaksi paikkaa — ks. [secrets.md](secrets.md).
### 5. Secretit erillisessä hallinnassa
`git-pages-publish-auth` luodaan ennen käyttöönottoa — ei osana sovelluksen konfiguraatiotiedostoja.
**Miksi:** Salaisuudet eivät kulje versionoiduissa arvoissa. Rotaatio ja SealedSecrets
pysyvät operaattorin hallussa. Ks. [secrets.md](secrets.md).
### 6. Minimaalinen parametrisointi
Instance-arvot (`host`, `issuer`, PVC) `{env}-values.yaml`:ssa. Resurssinimet,
secret-nimet ja Traefik-wire kovakoodattu templatessa.
**Miksi:** Parametrisoi vain se, mikä vaihtelee instanssien välillä (host, TLS-issuer,
levy). Vakioidut nimet ja wire pysyvät ennustettavina kaikissa asennuksissa.
---
## Puutteet
Tietoisesti avoimet asiat — eivät estä nykyistä julkaisu- ja lukumallia.
### Luku-auth
Julkaisu on suojattu (Traefik BasicAuth). **Luku ei ole:** GET/HEAD on julkinen — kuka
tuntee URL:n voi lukea raportin.
Tavoite: Traefik OIDC GET/HEAD-reitille (Gitea OAuth2 -provider). Session säilyy —
commit-statuslinkki toimii kirjautumisen jälkeen ilman uutta julkaisuoikeutta.
Ei toteutettu. Julkaisu- ja luku-reitit pysyvät erillisinä; OIDC lisätään vain lukupuolelle.
### Retention
Sidecar samassa podissa (HTTP localhost:3000), ajaa retention-cleanup.sh
24h välein:
| Sääntö | Konfiguroitavissa? | Kuvaus |
|--------|-------------------|--------|
| **Poistettu branch** | Ei — aina | Jos `.meta.branch` ei ole Giteassa enää, raportti poistetaan |
| **maxAgeDays** | Kyllä (`dev-values`) | Aktiivisen branchin raportit vanhemmat kuin N päivää |
| **keepMin** | Kyllä (`dev-values`) | Aktiivisella branchilla pidetään vähintään N uusinta |
Poistettujen branchien siivous ei tarvitse parametreja. Jäljelle jääneille
branchille säännöt tulevat `retention.rules` (`branches.default` +
`branches.{name}`).
Ei PVC-skaalausta — sidecar lukee manifestin HTTP:lla ja poistaa whiteout
PATCHilla. Ei K8s API -oikeuksia.
Secret: `git-pages-retention-gitea` — Gitea PAT branch-tarkistukseen.
Ks. [secrets.md](secrets.md).
---
## Rajat
- **Ei forge-integraatiota** — ei `pages`-branchia, ei Gitea API -hakua, ei forge-authia
- **Ei julkaisijalogiikkaa** — kuka julkaisee ja milloin on julkaisijan vastuulla
- **Ei sisäverkon ohitusjulkaisua** — julkaisu kulkee julkisen ingressin kautta (BasicAuth)
---
## Teknologiavalinnat
| Valinta | Miksi |
|---------|-------|
| **Codeberg git-pages** `0.9.1` | Natiivi apex index-site + tar-pohjainen PATCH/PUT -julkaisu |
| **Filesystem + PVC** | Yksinkertainen, yksi replica, ei erillistä objektivarastoa |
| **Traefik IngressRoute + Middleware** | Julkaisuauth erillään sovelluksesta; GET/HEAD eri säännöllä |
| **cert-manager** | TLS automaattisesti (`git-pages-tls`) |
| **Helm v3** | Toistettava asennus; instanssikohtaiset arvot erillisessä values-tiedostossa |
---
## Mitä tietoisesti hylättiin
| Hylätty | Syy |
|---------|-----|
| **deadnews/gitea-pages** | Vetää sisällön Gitea API:sta — ei sopinut CI-push-malliin |
| **Gitea `pages`-branch** | Race condition rinnakkaisissa buildeissa |
| **Per-owner subdomain** (`{owner}.pages...`) | Ongelmallinen URL; vaatii rewrite-middlewarea polun kääntämiseen |
| **Traefik path→host -rewrite** | Korvattu apex + Gitea-polulla — yksi selvä URL commit-linkissä |
| **Gitea forge-auth / PAT** | `write:repository` liian laaja oikeus vain raporttijulkaisuun |
| **DNS TXT -haaste julkaisuun** | Operatiivinen kompleksisuus ilman hyötyä BasicAuthiin verrattuna |
| **Helm-managed publish Secret** | Arvot values-tiedostoihin on kielletty; manuaalinen lähde totuudelle |
| **Image tag `v0.9.1`** | Oikea tagi on `0.9.1` (ei `v`-etuliitettä) |
-43
View File
@@ -1,43 +0,0 @@
# Implementation Notes
Teknisiä huomioita git-pages 0.9.1:n käyttäytymisestä, joita ei ole ilmeistä
dokumentaatiosta.
## Storage v2 (Protobuf manifest)
Git-pages 0.9.1 käyttää v2-arkkitehtuuria. Kaikki sisältö on pakattu
Protobuf-manifestiin (`site/{host}/.index`), ei flat-FS:nä. Tästä seuraa:
- Tiedostoja ei voi etsiä tai poistaa `find`/`rm`-komennoilla
- `.git-pages/manifest.json` listaa kaikki tiedostot (ProtoJSON)
- `.git-pages/archive.tar` antaa koko sisällön (saattaa truncata HTTP/2:ssa)
## Host-header
Git-pages valitsee sivuston Host-headerin perusteella. Ilman oikeaa Hostia
palauttaa 404.
- Ulkoiset requestit: Traefik välittää alkuperäisen Hostin automaattisesti
- Sidecar/localhost: `-H "Host: ci-reports.helm-dev.keskikuja.site"`
## PATCH ja directory-entryt
Jos PATCH-tar sisältää directory-entryn (tyyppi directory, tar typeflag '5'),
se **korvaa** koko hakemiston dokumentaation mukaan. Tar saa sisältää vain
file- ja symlink-entryjä, jotta PATCH toimii odotetusti.
## Whiteout — tiedostojen poisto
Ainoa tapa poistaa tiedostoja ilman koko sivuston PUT-korvausta:
- Tarissa character device entry (`CHRTYPE`, tar typeflag '4')
- `devmajor=0`, `devminor=0`
- PATCH:ataan sivuston juureen
## .init — ensimmäinen julkaisu
Ensimmäinen julkaisu vaatii PUTin, joka luo `.index`-tiedoston. Tämän jälkeen
PATCH riittää.
Helm-chartin post-install -job hoitaa tämän automaattisesti:
consumerien publish-scriptien ei tarvitse tuntea asennuksen tilaa.
-237
View File
@@ -1,237 +0,0 @@
# Secrets — git-pages
## Quick Start
### Vaihe 1: Secret-arkkitehtuuri
Järjestelmässä on kaksi loogista salaista arvoa. Publish-token jaetaan kahteen K8s-secretiin (Traefik-yhteensopivuus):
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` (htpasswd) | `git-pages-publish-auth` (users) | - |
| `report_publish_api_token` (plaintext) | `git-pages-publish-token` (token) | Actions Secret: `GIT_PAGES_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `git-pages-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
**Huomio:** Publish-token jaetaan kahteen secretiin, koska Traefik BasicAuth middleware vaatii single-key secretin sekä on muodossa, missä sitä ei saa takaisin. Jokainen repo mikä raportteja käyttää, tarvitsee selväkielisen arvon, joka on "ylimääräisessä" secretissä.
### Vaihe 2: Luo Gitea PAT (retention)
**Avaa Gitea browserissa:**
1. Kirjaudu Gitea-käyttäjällä, jolla on luku kaikkiin raporttirepoihin
2. **Settings****Applications****Generate New Token**
3. Token name: `CI-REPORTS_READ_FOR_RETENTION`
4. Scopes: valitse vain **`read:repository`**
5. **Generate Token****kopioi token heti** (näytetään vain kerran)
6. Tallenna token talteen (`GITEA_RETENTION_TOKEN`)
### Vaihe 3: Generoi publish-token
**Palaa terminaalille:**
```bash
GITEA_RETENTION_TOKEN="<from Gitea>"
GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)"
echo "Publish-token generoitu. Tallennetaan K8s-secretiin Vaiheessa 4."
echo "$GIT_PAGES_PUBLISH_TOKEN"
```
### Vaihe 4: Luo K8s secrets
```bash
NS=git-pages
# 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin)
kubectl create secret generic git-pages-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$GIT_PAGES_PUBLISH_TOKEN")" \
-n "$NS"
# 2. Publish-token: plaintext (luetaan README:stä Giteaan viedessä)
kubectl create secret generic git-pages-publish-token \
--from-literal=token="$GIT_PAGES_PUBLISH_TOKEN" \
-n "$NS"
# 3. Retention (käyttää Vaiheessa 2 luotua PAT:ia)
kubectl create secret generic git-pages-retention-gitea \
--from-literal=token="$GITEA_RETENTION_TOKEN" \
-n "$NS"
kubectl get secrets -n "$NS"
```
---
### Seuraava: Helm-asennus
Palaa takaisin [README.md](../README.md#käyttöönotto) ja jatka kohdasta "Instanssin values-tiedosto".
---
## Secret Arkkitehtuuri
### Loogiset salaisuudet
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` | `git-pages-publish-auth` (htpasswd) | Actions Secret: `GIT_PAGES_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `git-pages-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
### Secret Reference Architecture
```mermaid
graph TD
subgraph "Publish Flow"
P1["Actions Secret<br/>GIT_PAGES_PUBLISH_TOKEN"]
P2["K8s Secret<br/>git-pages-publish-auth"]
P1 -->|token| TRAEFIK
P2 -->|htpasswd| TRAEFIK
TRAEFIK["Traefik BasicAuth"]
end
subgraph "Retention Flow"
R1["K8s Secret<br/>git-pages-retention-gitea"]
R2["Gitea PAT<br/>CI-REPORTS_READ_FOR_RETENTION"]
R1 -->|token| SC["Sidecar"]
SC -->|API auth| GITEA["Gitea API"]
SC -->|read branches| GITEA
end
```
---
## Data Flow
### Flow 1: Julkaisu (Publish)
```mermaid
sequenceDiagram
participant Actions as Gitea Actions
participant Traefik as Traefik
participant K8sAuth as K8s Secret<br/>git-pages-publish-auth
participant K8sToken as K8s Secret<br/>git-pages-publish-token
participant GP as git-pages
Note over Actions: 1. Lue plaintext-token
Actions->>K8sToken: lue token-avain
K8sToken-->>Actions: plaintext token
Note over Actions: 2. Lähettää raportin
Actions->>Traefik: PUT / + BasicAuth<br/>publish:TOKEN + repo-url
Traefik->>K8sAuth: lue users (htpasswd)
K8sAuth-->>Traefik: publish:$apr1$...
alt Token match
Traefik->>GP: välitä
GP-->>Traefik: 200 OK
Traefik-->>Actions: 200 OK
else Token ei match
Traefik-->>Actions: 401 Unauthorized
end
```
**Kaksi secretiä (Traefik-yhteensopivuus):**
- `git-pages-publish-auth` = `users` (htpasswd, Traefik käyttää)
- `git-pages-publish-token` = `token` (plaintext, luetaan Giteaan viedessä)
---
### Flow 2: Luku (Read)
```mermaid
sequenceDiagram
participant Browser as Selain
participant Traefik as Traefik
participant GP as git-pages
Browser->>Traefik: GET /OWNER/REPO/commit/SHA/raportti/index.html
Traefik->>GP: välitä (ei authia)
GP-->>Traefik: HTML
Traefik-->>Browser: HTML
```
GET/HEAD-reitillä ei ole Middlewarea. Luku on julkinen, jos URL tunnetaan.
---
### Flow 3: Retention (Siivous)
```mermaid
sequenceDiagram
participant Sidecar as Retention Sidecar
participant K8sSecret as K8s Secret<br/>git-pages-retention-gitea
participant GiteaAPI as Gitea API
participant GP as git-pages (localhost:3000)
Note over Sidecar: 1. Lue PAT
Sidecar->>K8sSecret: lue token
K8sSecret-->>Sidecar: Gitea PAT
Note over Sidecar: 2. Lue manifest
Sidecar->>GP: GET .git-pages/manifest.json
GP-->>Sidecar: sisällysluettelo
Note over Sidecar: 3. Kysy branch
Sidecar->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH
GiteaAPI-->>Sidecar: 200 / 404
Note over Sidecar: 4. Luo whiteout-tar + PATCH
Sidecar->>GP: PATCH / (whiteout)
GP-->>Sidecar: 200 OK
```
**Huomio:** Retention-PAT:in omistajalla on oltava lukuoikeus KAIKKIIN repoihin,
joista raportteja on PVC:llä.
---
## Troubleshooting
- **"secret not found"** — luiko secretit ennen Helm-asennusta?
- **"401 Unauthorized"** — onko Gitea Actions secret oikea?
- **"found 2 elements for secret"** — Traefik vaatii single-key secretin. Varmista että `git-pages-publish-auth` sisältää vain `users`-avaimen.
- **"token hukkuu"** — generoi uusi token (Vaihe 3) ja päivitä molemmat publish-secretit:
```bash
# 1. Generoi uusi
GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)"
# 2. Päivitä K8s secrets (molemmat)
NS=git-pages
kubectl delete secret git-pages-publish-auth -n "$NS"
kubectl delete secret git-pages-publish-token -n "$NS"
kubectl create secret generic git-pages-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$GIT_PAGES_PUBLISH_TOKEN")" \
-n "$NS"
kubectl create secret generic git-pages-publish-token \
--from-literal=token="$GIT_PAGES_PUBLISH_TOKEN" \
-n "$NS"
# 3. Päivitä Gitea Actions secret jokaisessa repoissa (luke README:stä)
```
## Automatisointi: useamman repon salaisuuden lisääminen
Jos repoja on monta, voit käyttää Gitea API:ta (vaatii admin-tokenin):
```bash
ADMIN_TOKEN="<gitea-admin-token>"
NS=git-pages
# Lue plaintext-token erillisestä secretistä
TOKEN=$(kubectl get secret git-pages-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
for repo in "owner/repo1" "owner/repo2" "owner/repo3"; do
curl -X POST "https://gitea.example.com/api/v1/repos/$repo/actions/secrets" \
-H "Authorization: token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"GIT_PAGES_PUBLISH_TOKEN\",\"data\":\"$TOKEN\"}"
done
```
Tai `tea` CLI:lla (Gitea:n virallinen CLI):
```bash
tea actions secrets add --repo owner/repo1 GIT_PAGES_PUBLISH_TOKEN "$TOKEN"
tea actions secrets add --repo owner/repo2 GIT_PAGES_PUBLISH_TOKEN "$TOKEN"
```
-221
View File
@@ -1,221 +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 BRANCH_CACHE
branch_exists() {
local owner="$1" repo="$2" branch="$3" key="${owner}/${repo}/${branch}"
local status attempt
[ -z "$GITEA_API_URL" ] && return 0
[ -z "$GITEA_TOKEN" ] && return 0
if [ "${BRANCH_CACHE[$key]:-}" = "1" ]; then
return 0
fi
# Retry up to 2 times on API errors (hardcoded)
for attempt in 1 2 3; do
status=$(curl -sS -o /dev/null -w "%{http_code}" \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_API_URL}/api/v1/repos/${owner}/${repo}/branches/${branch}" 2>/dev/null || echo "000")
if [ "$status" = "200" ]; then
BRANCH_CACHE[$key]=1
return 0
fi
if [ "$status" = "404" ]; then
return 1
fi
# API error - retry if not last attempt
if [ "$attempt" -lt 3 ]; then
sleep 10
continue
fi
done
# All retries failed - keep report (fail-safe)
echo " WARN: Gitea API error for ${owner}/${repo}/${branch} (status ${status}) after 3 attempts - KEEPING report"
BRANCH_CACHE[$key]=1
return 0
}
default_max_age=$(jq -r '.branches.default.maxAgeDays // 90' "$CONFIG")
default_keep_min=$(jq -r '.branches.default.keepMin // 5' "$CONFIG")
rule_max_age() {
local branch="$1" v
v=$(jq -r --arg b "$branch" '.branches[$b].maxAgeDays // empty' "$CONFIG")
[ -n "$v" ] && echo "$v" || echo "$default_max_age"
}
rule_keep_min() {
local branch="$1" v
v=$(jq -r --arg b "$branch" '.branches[$b].keepMin // empty' "$CONFIG")
[ -n "$v" ] && echo "$v" || echo "$default_keep_min"
}
age_days() {
local published="$1" epoch_pub now
epoch_pub=$(date -u -d "$published" +%s 2>/dev/null || echo 0)
[ "$epoch_pub" -eq 0 ] && echo 99999 && return
now=$(date -u +%s)
echo $(( (now - epoch_pub) / 86400 ))
}
parse_path() {
local rel="$1"
OWNER="${rel%%/*}"
rest="${rel#*/}"
REPO="${rest%%/*}"
}
echo "Fetching manifest from ${PAGES_URL}/.git-pages/manifest.json"
MANIFEST=$(curl_with_host "${PAGES_URL}/.git-pages/manifest.json")
echo "Manifest loaded"
META_PATHS=$(echo "$MANIFEST" | jq -r '.contents | to_entries[] | select(.key | test("/reports/")) | select(.key | endswith("/.meta")) | .key' 2>/dev/null || true)
if [ -z "$META_PATHS" ]; then
echo "No .meta files found under /reports/ — nothing to clean"
exit 0
fi
echo ""
echo "=== Phase 1: collect reports ==="
declare -A SEEN_REPORTS
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}")
echo " ${OWNER}/${REPO} branch=${branch} age=${days}d"
done <<< "$META_PATHS"
[ "${#REPORTS[@]}" -eq 0 ] && { echo "No actionable reports"; exit 0; }
echo ""
echo "=== Phase 2: check branches in Gitea ==="
declare -a TO_DELETE
declare -a KEEP
for entry in "${REPORTS[@]}"; do
IFS='|' read -r dir owner repo branch days <<< "$entry"
if [ -n "$GITEA_API_URL" ] && [ -n "$GITEA_TOKEN" ]; then
if branch_exists "$owner" "$repo" "$branch"; then
echo " BRANCH EXISTS: ${owner}/${repo}/${branch}"
KEEP+=("${dir}|${owner}|${repo}|${branch}|${days}")
else
echo " BRANCH DELETED: ${owner}/${repo}/${branch} -> DELETE"
TO_DELETE+=("$dir")
fi
else
KEEP+=("${dir}|${owner}|${repo}|${branch}|${days}")
fi
done
echo ""
echo "=== Phase 3: apply retention rules to remaining reports ==="
declare -A BRANCH_COUNTS
if [ "${#KEEP[@]}" -gt 0 ]; then
IFS=$'\n'
for entry in $(printf '%s\n' "${KEEP[@]}" | sort -t'|' -k4,4 -k5,5rn); do
IFS='|' read -r dir owner repo branch days <<< "$entry"
max_age=$(rule_max_age "$branch")
keep_min=$(rule_keep_min "$branch")
if [ "$days" -gt "$max_age" ]; then
echo " DELETE: ${dir} (age ${days}d > maxAge ${max_age}d, branch ${branch})"
TO_DELETE+=("$dir")
continue
fi
key="${branch}"
count="${BRANCH_COUNTS[$key]:-0}"
count=$((count + 1))
BRANCH_COUNTS["$key"]=$count
if [ "$count" -gt "$keep_min" ]; then
echo " DELETE: ${dir} (kept ${keep_min}/${count}, exceeds keepMin, branch ${branch})"
TO_DELETE+=("$dir")
fi
done
unset IFS
fi
if [ "${#TO_DELETE[@]}" -eq 0 ]; then
echo "Nothing to delete"
exit 0
fi
echo ""
echo "=== Phase 4: whiteout deletion ==="
echo "Creating whiteout tar for ${#TO_DELETE[@]} report(s)..."
WHITEOUT_TAR=$(mktemp)
trap 'rm -f "$WHITEOUT_TAR"' EXIT
python3 -c "
import tarfile, sys
tar = tarfile.open(name='${WHITEOUT_TAR}', mode='w')
dirs = set()
for d in sys.argv[1:]:
dirs.add(d.strip())
tarinfo = tarfile.TarInfo()
tarinfo.type = tarfile.CHRTYPE
tarinfo.devmajor = 0
tarinfo.devminor = 0
for d in sorted(dirs, key=len, reverse=True):
info = tarinfo
info.name = d
tar.addfile(info)
tar.close()
" "${TO_DELETE[@]}"
echo "Patching ${PAGES_URL}/ with whiteout tar..."
HTTP_CODE=$(curl_with_host -X PATCH "${PAGES_URL}/" \
-H "Content-Type: application/x-tar" \
-H "Atomic: no" \
--data-binary @"${WHITEOUT_TAR}" \
-w "%{http_code}" \
-o /dev/null)
echo "HTTP $HTTP_CODE"
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then
echo "Retention cleanup finished."
else
echo "ERROR: retention HTTP ${HTTP_CODE}" >&2
exit 1
fi
-19
View File
@@ -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."
-16
View File
@@ -1,16 +0,0 @@
git-pages installed.
See docs/secrets.md for secret prerequisites (K8s + Gitea Actions).
Install:
helm upgrade --install git-pages ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml
Host: https://{{ .Values.ingress.host }}
Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/reports/{sha8}/index.html
Publish (CI):
PATCH https://{{ .Values.ingress.host }}/
Authorization: Basic publish:<GIT_PAGES_PUBLISH_TOKEN>
Upgrade: helm upgrade {{ .Release.Name }} ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml
Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }}
-20
View File
@@ -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"
-120
View File
@@ -1,120 +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 python3 >/dev/null
echo "Retention sidecar: ready"
while true; do
/scripts/retention-cleanup.sh
echo "Retention sidecar: next run in 24h"
sleep 86400
done
env:
- name: PAGES_URL
value: http://localhost:3000
- name: PAGES_HOST
value: {{ .Values.ingress.host | quote }}
- name: RETENTION_CONFIG
value: /etc/retention/retention.json
- name: GITEA_API_URL
value: {{ .Values.retention.giteaApiUrl | quote }}
- name: GITEA_TOKEN
valueFrom:
secretKeyRef:
name: git-pages-retention-gitea
key: token
volumeMounts:
- name: retention-scripts
mountPath: /scripts
- name: retention-config
mountPath: /etc/retention
{{- end }}
volumes:
- name: config
configMap:
name: {{ include "git-pages.fullname" . }}-config
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "git-pages.fullname" . }}-data
{{- end }}
{{- if and .Values.persistence.enabled .Values.retention.enabled (eq .Values.retention.mode "sidecar") }}
- name: retention-scripts
configMap:
name: git-pages-retention
defaultMode: 0755
- name: retention-config
configMap:
name: git-pages-retention
items:
- key: retention.json
path: retention.json
{{- end }}
-48
View File
@@ -1,48 +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, post-upgrade
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
spec:
backoffLimit: 5
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "git-pages.name" . }}-init
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
restartPolicy: Never
containers:
- name: init
image: "{{ .Values.initJob.image.repository }}:{{ .Values.initJob.image.tag }}"
imagePullPolicy: {{ .Values.initJob.image.pullPolicy }}
command:
- bash
- -c
- |
set -euo pipefail
apt-get update -qq && apt-get install -y -qq curl tar >/dev/null
echo "Init: waiting for git-pages..."
until curl -sf \
-H "Host: {{ .Values.ingress.host }}" \
-o /dev/null "http://git-pages:3000/.git-pages/health"
do sleep 2; done
echo "Init: creating placeholder site..."
WORK=$(mktemp -d)
mkdir -p "$WORK/__init__"
echo "initialized" > "$WORK/__init__/index.html"
tar cf /tmp/init.tar -C "$WORK" __init__
curl -sf -X PUT "http://git-pages:3000/" \
-H "Host: {{ .Values.ingress.host }}" \
-H "Content-Type: application/x-tar" \
--data-binary @/tmp/init.tar -o /dev/null
echo "Init: done"
env:
- name: PAGES_INSECURE
value: "1"
{{- end }}
-22
View File
@@ -1,22 +0,0 @@
{{- if .Values.ingress.enabled }}
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "git-pages.fullname" . }}-publish-auth
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
basicAuth:
secret: git-pages-publish-auth
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "git-pages.fullname" . }}-https-redirect
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
redirectScheme:
scheme: https
permanent: true
{{- end }}
-15
View File
@@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "git-pages.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
{{- include "git-pages.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
+5
View File
@@ -0,0 +1,5 @@
apiVersion: v2
name: gitea-reports
description: Nginx + upload-sidecar for CI HTML reports (static serving, Traefik BasicAuth publish)
type: application
version: 0.2.0
+56 -28
View File
@@ -1,4 +1,4 @@
# git-pages
# gitea-reports
Jaettu **Gitea CI -raporttien** tallennus- ja lukupaikka: HTML-raportit (esim. Cucumber)
commit-kohtaisiin polkuihin, selaimella avattavina linkkeinä Gitean commitin CI-job
@@ -21,39 +21,39 @@ Luo secretit ennen Helm-asennusta: [docs/secrets.md](docs/secrets.md)
```bash
# Muokkaa oma values-tiedosto (tai käytä dev-values.yaml)
cp git-pages/dev-values.yaml my-values.yaml
cp gitea-reports/dev-values.yaml my-values.yaml
# Muuta: ingress.host, certificate.issuerRef.name, jne.
```
### 3. Helm-asennus
```bash
NS=git-pages
VALUES=my-values.yaml
NS=gitea-reports
VALUES=gitea-reports/dev-values.yaml
helm upgrade --install git-pages ./git-pages \
helm upgrade --install gitea-reports ./gitea-reports \
-n "$NS" --create-namespace \
-f "$VALUES"
helm template gitea-reports ./gitea-reports -n "$NS" -f "$VALUES"
```
Helm ajaa asennuksen jälkeen init-jobin, joka PUTtaa paikanpitäjäsivun
git-pagesiin. Tämä luo tarvittavan `.index`-tiedoston — sen jälkeen
Gitea Actions -scriptit voivat käyttää suoraan PATCHia ilman
PUT-fallbackia.
Nginx ja upload-sidecar käynnistyvät suoraan ilman init-vaihetta.
PV on tyhjä ennen ensimmäistä CI-julkaisua.
---
## Vie publish-token Gitea Actions-secretiin (per repo)
⚠️ **Tehtävä jokaiselle repoille**, joka julkaisee raportteja git-pagesiin.
⚠️ **Tehtävä jokaiselle repoille**, joka julkaisee raportteja gitea-reportsiin.
```bash
NS=git-pages
NS=gitea-reports
REPO_OWNER="niko"
REPO_NAME="gitea-ci-library"
# 1. Lue plaintext-token erillisestä secretistä
TOKEN=$(kubectl get secret git-pages-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
TOKEN=$(kubectl get secret gitea-reports-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
# 2. Kopioi leikepöydälle
echo -n "$TOKEN" | pbcopy # macOS
@@ -64,12 +64,12 @@ open "https://gitea.app.keskikuja.site/${REPO_OWNER}/${REPO_NAME}/settings/actio
# Linux: xdg-open "https://gitea.app.keskikuja.site/${REPO_OWNER}/${REPO_NAME}/settings/actions/secrets"
```
**Gitea UI:ssa:** New Secret → Name: `GIT_PAGES_PUBLISH_TOKEN` → Value: **liitä leikepöydältä** → Save
**Gitea UI:ssa:** New Secret → Name: `REPORTS_PUBLISH_TOKEN` → Value: **liitä leikepöydältä** → Save
> 💡 **Monelle repoille:** Toista vaiheet 34, tai katso [automatisointi](docs/secrets.md#automatisointi-useamman-repon-salaisuuden-lis%C3%A4%C3%A4miseen).
---
---
## Käyttöönotto
### 1. Secretit
@@ -113,16 +113,16 @@ 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"
```
---
## CI-julkaisu
@@ -131,21 +131,49 @@ Julkaisu DNS-osoitteeseen BasicAuthilla:
```bash
# Esimerkki: julkaise raportti
curl -X PATCH https://ci-reports.helm-dev.keskikuja.site/owner/repo/commit/sha8/ \
-H "Authorization: Basic $(echo -n "publish:$GIT_PAGES_PUBLISH_TOKEN" | base64)" \
-H "Content-Type: application/x-tar" \
--data-binary @raportti.tar
curl -X PUT \
-u publish:$REPORTS_PUBLISH_TOKEN \
-H "Content-Type: application/tar+gz" \
--data-binary @report.tar.gz \
https://ci-reports.helm-dev.keskikuja.site/{owner}/{repo}/{branch}/{sha8}/{suite}/
```
**Vaaditut asetukset:**
| Missä | Arvo |
|-------|------|
| Gitea Actions secret | `GIT_PAGES_PUBLISH_TOKEN` (sama kuin K8s `git-pages-publish-token` `token`-avain) |
| Scriptissä | `curl` käyttää BasicAuth-headeria yllä |
| Gitea Actions secret | `REPORTS_PUBLISH_TOKEN` (sama kuin K8s `gitea-reports-publish-token` `token`-avain) |
| Scriptissä | `bash scripts/publish-gitea-reports.sh <suite>` |
**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).
---
## Testaus
Retention-logiikalle on unit-testit, jotka testaa funktiot ja Phase 3 -säännöt
erikseen ilman ulkoisia riippuvuuksia.
```bash
cd gitea-reports
bats tests/retention.bats
```
Testit käyttävät `<root>/files/retention-lib.sh` -jaettua kirjastoa, jota myös
`retention-cleanup.sh` sourceaa.
**Vaatimukset:** `bats`, `jq`, `date` (GNU date tai BSD date ISO 8601 -tuella).
---
## Retention
Ylläpitoscripti, joka poistaa vanhat raportit PV:ltä suoraan retentionsääntöjen mukaan.
Ajetaan CronJobina Kubernetesissa.
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.
@@ -1,5 +1,5 @@
# Dev instance — overrides values.yaml constants.
# helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml
# helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml
ingress:
host: ci-reports.helm-dev.keskikuja.site
@@ -19,11 +19,11 @@ retention:
rules:
branches:
default:
maxAgeDays: 90
minAgeDays: 3
keepMin: 5
main:
maxAgeDays: 365
keepMin: 20
minAgeDays: 3
keepMin: 10
master:
maxAgeDays: 365
keepMin: 20
minAgeDays: 3
keepMin: 10
+82
View File
@@ -0,0 +1,82 @@
# Architecture — Nginx + upload-sidecar
> gitea-reports on korvattu. Uusi arkkitehtuuri: Nginx palvelee staattisia tiedostoja PV:ltä, upload-sidecar (busybox httpd + CGI) ottaa vastaan tar.gz-PUTit.
## Yleiskuvaus
Yksi apex-host, monta Gitea-repoa. URL = FS-polku: `/{owner}/{repo}/{branch}/{sha8}/{suite}/`.
Ei Host-header-kikkaa, ei slugia, ei rewritea.
## Komponentit
| Komponentti | Rooli |
|-------------|-------|
| **nginx** | Palvelee staattisia tiedostoja /app/data -rootista (:8080) |
| **upload** (Python HTTP server) | Vastaanottaa PUT /path + tar.gz body, purkaa PV:lle (:8081) |
| **PVC** | Raporttisisältö suoraan FS-muodossa |
| **Service** | ClusterIP :8080 (read), :8081 (write) |
| **Traefik IngressRoute** | PATCH/PUT → upload:8081 (BasicAuth), GET/HEAD → nginx:8080 |
| **Traefik Middleware** | `gitea-reports-publish-auth` (BasicAuth), HTTPS-redirect |
| **cert-manager Certificate** | TLS |
## URL ja sisältö
```
https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/main/f4baa286/cucumber/index.html
└── selvä URL ──┘ └─────────── Gitea-yhteensopiva polku ──────────────────────────┘
```
Levyllä:
```
/app/data/{owner}/{repo}/{branch}/{sha8}/
.meta
index.html
...
```
## Järjestelmäkaavio
```mermaid
flowchart TB
subgraph ext["Ulkoiset"]
PUB["Julkaisija\n(CI)"]
BR["Selain"]
GITEA["Gitea API\n(branch-lista)"]
end
subgraph edge["Reuna"]
TRAEFIK["Traefik\nIngressRoute + Middleware"]
CM["cert-manager\nTLS"]
end
subgraph cluster["Pod"]
NGINX["nginx\n(kontti)"]
UPLOAD["upload sidecar\n(Python HTTP server)"]
PVC["PVC /app/data"]
end
PUB -->|"PUT + BasicAuth\ntar.gz"| TRAEFIK
BR -->|"GET/HEAD"| TRAEFIK
TRAEFIK -->|"PUT/PATCH"| UPLOAD
TRAEFIK -->|"GET/HEAD"| NGINX
CM --> TRAEFIK
UPLOAD -->|"extract to PV"| PVC
NGINX --> PVC
```
## Julkaisu
1. Julkaisija paketoi tiedostot tar.gz:ksi
2. `PUT https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`
3. Traefik tarkistaa BasicAuth → välittää upload-sidecarille
4. CGI purkaa tar.gz:n suoraan PV:lle: `/app/data/{owner}/{repo}/{branch}/{sha8}/{suite}/`
## Luku
1. Selain avaa URL:n (GET/HEAD)
2. Traefik välittää nginxille
3. Nginx palauttaa suoraan PV:ltä
## Retention
CronJob lukee PV:tä suoraan (find, ls, stat), tarkistaa branchin olemassaolon Giteasta, poistaa vanhat commit-kansiot.
+90
View File
@@ -0,0 +1,90 @@
# Design Rationale — gitea-reports
Miksi gitea-reports on rakennettu näin. Periaatteet, jotka pitävät arkkitehtuurin koossa.
Teknologiavalinnat ovat näiden periaatteiden seurauksia — eivät erillisiä preferenssejä.
Tämä dokumentti on normatiivinen. Arkkitehtuurin tulee noudattaa sen periaatteita.
Ehdotus, joka on ristiriidassa dokumentin kanssa, on konflikti — ei hiljainen ohitus.
## The problem this solves
CI-testiajoista syntyy HTML-raportteja. Gitea ei tarjoa web-selaimella selattavaa arkistoa
näille raporteille.
Vaihtoehdot eivät sovi:
- **Gitea Actions artifactit**: vain ZIP-lataus — HTML ei renderöidy selaimessa
- **Gitea pages-branch**: yksi branch per repo; rinnakkaiset buildit törmäävät
- **Gitea Releases**: sotkee julkaisuhistorian sadoilla CI-buildien raporteilla
## How this is solved
Yksi klusteri, yksi apex-host, monta Gitea-repoa. URL on suoraan FS-polku:
`/{owner}/{repo}/{branch}/{sha8}/{suite}/`. Ei rewritea, ei subdomain-per-owner,
ei slugia.
Palvelu on read/write -jaettu: luku ja julkaisu eri sovelluksilla, eri porteilla,
eri Traefik-säännöillä.
## Why selvä URL + Gitea-yhteensopiva polku
Repo tunnistetaan polusta `/{owner}/{repo}/...`, ei hostista. Kaikki URL-taso
(branch, sha8, suite) on suoraan FS-polku. Tämä mahdollistaa yhden TLS-sertifikaatin,
yhden IngressRouten ja URL:n, joka on suoraan kopioitavissa commit-statusiin ilman
rewritea.
**Hylätty:** subdomain-per-owner (`{owner}.pages...`). Vaati per-owner Traefik-
rewritea ja wildcard-TLS:ää. Julkaisu-URL ja luku-URL olivat eri muodossa — kehittäjälle
vaikea ymmärtää ja debugata.
## Why luku ja kirjoitus eri sovelluksilla
Yksi binary, joka palvelee sekä lukua että kirjoitusta samassa prosessissa, on
yksinkertainen mutta yhdistää kaksi eri asiaa. Tässä arkkitehtuurissa nginx palvelee
staattisia tiedostoja; Python-skripti purkaa tar.gz-julkaisun PV:lle. Ne eivät tiedä
toisistaan.
Luku ei tarvitse julkaisulogiikkaa. Julkaisu ei tarvitse optimoitua staattista
palvelua. Erottamalla ne kumpikin voidaan valita parhaaseen tarkoitukseensa.
## Why security klusterin reunalla — ei sovelluksessa
Sovellus on "tyhmä": se palvelee mitä PV:llä on ja kirjoittaa mitä sille annetaan.
Valtuutus tapahtuu yksinomaan Traefik BasicAuth -middlewaressa.
Tämä mahdollistaa:
- Julkaisu- ja lukuoikeuksien erillisen hallinnan eri Traefik-säännöillä
- Sovelluksen vaihtamisen ilman turvallisuusauditointia
- Token-rotaation ilman sovelluksen uudelleenkäynnistystä
**Hylätty:** sovelluksen sisäinen forge-auth (Gitea PAT, DNS TXT -haaste).
Sitoo sovelluksen tiettyyn autentikointimekanismiin ja vaatii laajat oikeudet
(`write:repository`) pelkkää raporttijulkaisua varten.
## Why retention erillisenä CronJobina
Retention ei tarvitse olla aina käynnissä — se ajetaan kerran vuorokaudessa.
CronJob lukee PV:tä suoraan (find, ls, stat), ei HTTP API:n kautta. Tämä pitää
retention poissa podin kriittiseltä polulta (luku/julkaisu).
**Hylätty:** sidecar samassa podissa. Sidecar kuluttaa resursseja aina, ja
retention-logiikka sekoittuu julkaisu- ja lukukoodiin.
## Why yksi token, kaksi säilöä
Sama plaintext-arvo: htpasswd-hashina K8s Secretissä (Traefik lukee), plaintextina
Gitea Actions Secretissä (julkaisija lukee). Yksi rotaatio päivittää molemmat.
Token antaa vain julkaisuoikeuden tähän palveluun — ei Gitea PAT:ia eikä
`write:repository`-oikeutta.
## Why PVC suoraan FS-muodossa
Ei objektivarastoa, ei HTTP API:a retentionille. Sisältö on suoraan luettavissa
kubectl exec + ls/find. Yksinkertaisin mahdollinen tallennusmuoto. Yksi replica,
ei synkronointia.
## References
- [architecture.md](architecture.md) — komponentit ja vuokaavio
- [tech-stack.md](tech-stack.md) — teknologiavalinnat listana
- [secrets.md](secrets.md) — secret-arkkitehtuuri ja käyttöönotto
+267
View File
@@ -0,0 +1,267 @@
# Secrets — gitea-reports
## Quick Start
### Vaihe 1: Secret-arkkitehtuuri
Järjestelmässä on kaksi loogista salaista arvoa. Publish-token jaetaan kahteen K8s-secretiin (Traefik-yhteensopivuus):
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` (htpasswd) | `gitea-reports-publish-auth` (users) | - |
| `report_publish_api_token` (plaintext) | `gitea-reports-publish-token` (token) | Actions Secret: `REPORTS_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `gitea-reports-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
**Huomio:** Publish-token jaetaan kahteen secretiin, koska Traefik BasicAuth middleware vaatii single-key secretin sekä on muodossa, missä sitä ei saa takaisin. Jokainen repo mikä raportteja käyttää, tarvitsee selväkielisen arvon, joka on "ylimääräisessä" secretissä.
### Vaihe 2: Luo Gitea PAT (retention)
**Avaa Gitea browserissa:**
1. Kirjaudu Gitea-käyttäjällä, jolla on luku kaikkiin raporttirepoihin
2. **Settings****Applications****Generate New Token**
3. Token name: `CI-REPORTS_READ_FOR_RETENTION`
4. Scopes: valitse vain **`read:repository`**
5. **Generate Token****kopioi token heti** (näytetään vain kerran)
6. Tallenna token talteen (`GITEA_RETENTION_TOKEN`)
### Vaihe 3: Generoi publish-token
**Palaa terminaalille:**
```bash
GITEA_RETENTION_TOKEN="<from Gitea>"
REPORTS_PUBLISH_TOKEN="$(openssl rand -base64 24)"
echo "Publish-token generoitu. Tallennetaan K8s-secretiin Vaiheessa 4."
echo "$REPORTS_PUBLISH_TOKEN"
```
### Vaihe 4: Luo K8s secrets
```bash
NS=gitea-reports
# 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin)
kubectl create secret generic gitea-reports-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$REPORTS_PUBLISH_TOKEN")" \
-n "$NS"
# 2. Publish-token: plaintext (luetaan README:stä Giteaan viedessä)
kubectl create secret generic gitea-reports-publish-token \
--from-literal=token="$REPORTS_PUBLISH_TOKEN" \
-n "$NS"
# 3. Retention (käyttää Vaiheessa 2 luotua PAT:ia)
kubectl create secret generic gitea-reports-retention-gitea \
--from-literal=token="$GITEA_RETENTION_TOKEN" \
-n "$NS"
kubectl get secrets -n "$NS"
```
---
### Vaihe 5: Vie publish-token Gitea Actions-secretiin
Token näkyy terminaalissa Vaiheen 3 `echo`sta. Jos se on jo kadonnut näkyvistä, hae se K8s-secretistä:
```bash
NS=gitea-reports
kubectl get secret gitea-reports-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d; echo
```
Vie kopioitu secret Gitea Actions Secrets:
Kannattaa viedä tämä token ORG tason secretiksi Gitea Actions. Muuten joudut tekemään tämän jokaiselle rapositorylle erikseen.
Luo `REPORTS_PUBLISH_TOKEN` secret, ja terminaalista kopioitu arvo `values` kenttään.
Description voi olla esim:
> Julkaisuun liittyvä salaisuus, jonka gitea-pages auth tarkistaa, että saako julkaista raporttia.
**Gitea UI:ssa:** New Secret → Name: `REPORTS_PUBLISH_TOKEN` → Value: **liitä token** → Save
Toista jokaiselle repolle, joka julkaisee raportteja.
---
### Seuraava: Helm-asennus
Palaa takaisin [README.md](../README.md#käyttöönotto) ja jatka kohdasta "Instanssin values-tiedosto".
---
## Secret Arkkitehtuuri
### Loogiset salaisuudet
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` | `gitea-reports-publish-auth` (htpasswd) | Actions Secret: `REPORTS_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `gitea-reports-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
### Secret Reference Architecture
```mermaid
graph TD
subgraph "Publish Flow"
P1["Actions Secret<br/>REPORTS_PUBLISH_TOKEN"]
P2["K8s Secret<br/>gitea-reports-publish-auth"]
P1 -->|token| TRAEFIK
P2 -->|htpasswd| TRAEFIK
TRAEFIK["Traefik BasicAuth"]
end
subgraph "Retention Flow"
R1["K8s Secret<br/>gitea-reports-retention-gitea"]
R2["Gitea PAT<br/>CI-REPORTS_READ_FOR_RETENTION"]
R1 -->|token| SC["Sidecar"]
SC -->|API auth| GITEA["Gitea API"]
SC -->|read branches| GITEA
end
```
---
## Data Flow
### Flow 1: Julkaisu (Publish)
```mermaid
sequenceDiagram
participant Actions as Gitea Actions
participant Traefik as Traefik
participant K8sAuth as K8s Secret<br/>gitea-reports-publish-auth
participant K8sToken as K8s Secret<br/>gitea-reports-publish-token
participant Upload as upload-sidecar
participant PV as PVC /app/data
Note over Actions: 1. Lue plaintext-token
Actions->>K8sToken: lue token-avain
K8sToken-->>Actions: plaintext token
Note over Actions: 2. Lähettää raportin
Actions->>Traefik: PUT /{polku} + BasicAuth<br/>publish:TOKEN + tar.gz
Traefik->>K8sAuth: lue users (htpasswd)
K8sAuth-->>Traefik: publish:$apr1$...
alt Token match
Traefik->>Upload: PUT /{path}
Upload->>PV: extract tar.gz
PV-->>Upload: 201 Created
Upload-->>Traefik: 201 Created
Traefik-->>Actions: 201 Created
else Token ei match
Traefik-->>Actions: 401 Unauthorized
end
```
**Kaksi secretiä (Traefik-yhteensopivuus):**
- `gitea-reports-publish-auth` = `users` (htpasswd, Traefik käyttää)
- `gitea-reports-publish-token` = `token` (plaintext, luetaan Giteaan viedessä)
---
### Flow 2: Luku (Read)
```mermaid
sequenceDiagram
participant Browser as Selain
participant Traefik as Traefik
participant Nginx as nginx
participant PV as PVC /app/data
Browser->>Traefik: GET /OWNER/REPO/branch/SHA/suite/index.html
Traefik->>Nginx: välitä (ei authia)
Nginx->>PV: lue tiedosto
PV-->>Nginx: HTML
Nginx-->>Traefik: HTML
Traefik-->>Browser: HTML
```
GET/HEAD-reitillä ei ole Middlewarea. Luku on julkinen, jos URL tunnetaan.
---
### Flow 3: Retention (Siivous)
```mermaid
sequenceDiagram
participant CronJob as Retention CronJob
participant K8sSecret as K8s Secret<br/>gitea-reports-retention-gitea
participant GiteaAPI as Gitea API
participant PV as PVC /app/data
Note over CronJob: 1. Lue PAT
CronJob->>K8sSecret: lue token
K8sSecret-->>CronJob: Gitea PAT
Note over CronJob: 2. Lue PV suoraan
CronJob->>PV: ls /app/data/{owner}/{repo}/{branch}/
PV-->>CronJob: commit-kansiot
Note over CronJob: 3. Kysy branch
CronJob->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH
GiteaAPI-->>CronJob: 200 / 404
Note over CronJob: 4. Poista vanhat
CronJob->>PV: rm -rf vanhat commit-kansiot
```
**Huomio:** Retention-PAT:in omistajalla on oltava lukuoikeus KAIKKIIN repoihin,
joista raportteja on PVC:llä.
---
## Troubleshooting
- **"secret not found"** — luiko secretit ennen Helm-asennusta?
- **"401 Unauthorized"** — onko Gitea Actions secret oikea?
- **"found 2 elements for secret"** — Traefik vaatii single-key secretin. Varmista että `gitea-reports-publish-auth` sisältää vain `users`-avaimen.
- **"token hukkuu"** — generoi uusi token (Vaihe 3) ja päivitä molemmat publish-secretit:
```bash
# 1. Generoi uusi
REPORTS_PUBLISH_TOKEN="$(openssl rand -base64 24)"
# 2. Päivitä K8s secrets (molemmat)
NS=gitea-reports
kubectl delete secret gitea-reports-publish-auth -n "$NS"
kubectl delete secret gitea-reports-publish-token -n "$NS"
kubectl create secret generic gitea-reports-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$REPORTS_PUBLISH_TOKEN")" \
-n "$NS"
kubectl create secret generic gitea-reports-publish-token \
--from-literal=token="$REPORTS_PUBLISH_TOKEN" \
-n "$NS"
# 3. Päivitä Gitea Actions secret jokaisessa repoissa (luke README:stä)
```
## Automatisointi: useamman repon salaisuuden lisääminen
Jos repoja on monta, voit käyttää Gitea API:ta (vaatii admin-tokenin):
```bash
ADMIN_TOKEN="<gitea-admin-token>"
NS=gitea-reports
# Lue plaintext-token erillisestä secretistä
TOKEN=$(kubectl get secret gitea-reports-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
for repo in "owner/repo1" "owner/repo2" "owner/repo3"; do
curl -X POST "https://gitea.example.com/api/v1/repos/$repo/actions/secrets" \
-H "Authorization: token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"REPORTS_PUBLISH_TOKEN\",\"data\":\"$TOKEN\"}"
done
```
Tai `tea` CLI:lla (Gitea:n virallinen CLI):
```bash
tea actions secrets add --repo owner/repo1 REPORTS_PUBLISH_TOKEN "$TOKEN"
tea actions secrets add --repo owner/repo2 REPORTS_PUBLISH_TOKEN "$TOKEN"
```
@@ -1,7 +1,7 @@
# Tech Stack — git-pages
# Tech Stack — gitea-reports
> Mitä teknologioita `git-pages/` Helm chart käyttää ja edellyttää. Tämä dokumentti koskee vain
> `git-pages/`-alikansiota monorepossa — ei juuren `gitea-ci-library`-kirjastoa.
> Mitä teknologioita `gitea-reports/` Helm chart käyttää ja edellyttää. Tämä dokumentti koskee vain
> `gitea-reports/`-alikansiota monorepossa — ei juuren `gitea-ci-library`-kirjastoa.
---
@@ -9,13 +9,9 @@
| Teknologia | Versio | Käyttö |
|---|---|---|
| **git-pages** (Codeberg) | `0.9.1` | Staattinen sisältö, apex index-site (`/.index`), HTTP PATCH/PUT -julkaisu |
| **Filesystem storage** | — | Sisältö PVC:llä (`/app/data`) |
| **TOML** | — | Sovellusconfig ConfigMapissa (`config.toml`) |
Image: `codeberg.org/git-pages/git-pages:0.9.1` (ei `v`-etuliitettä tagissa).
Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei forge-authia.
| **nginx** | alpine | Staattinen tiedostopalvelin (GET/HEAD) |
| **Python** | 3-alpine | PUT-vastaanotto + tar.gz-purku PV:lle |
| **Filesystem storage** | — | Sisältö PVC:llä |
---
@@ -26,7 +22,7 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f
| **Kubernetes** | 1.24+ | Deployment, Service, PVC, Secret |
| **Helm** | v3 | Chart asennus ja päivitys |
| **Traefik** | CRD `traefik.io/v1alpha1` | IngressRoute, Middleware (`basicAuth`, HTTPS-redirect) |
| **cert-manager** | — | TLS-sertifikaatti (`git-pages-tls`) |
| **cert-manager** | — | TLS-sertifikaatti (`gitea-reports-tls`) |
---
@@ -43,8 +39,8 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f
| Resurssi | Lähde | Dokumentti |
|---|---|---|
| `git-pages-publish-auth` | Manuaalinen Secret | [secrets.md](secrets.md) |
| `git-pages-tls` | cert-manager Certificate | Automaattinen asennuksessa |
| `gitea-reports-publish-auth` | Manuaalinen Secret | [secrets.md](secrets.md) |
| `gitea-reports-tls` | cert-manager Certificate | Automaattinen asennuksessa |
| `ClusterIssuer` | Klusteri (esim. `letsencrypt-prod`) | `dev-values.yaml` |
---
@@ -66,7 +62,7 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f
| `values.yaml` | Chart-vakiot | Image, resurssit, Traefik entrypointit |
| `{env}-values.yaml` | Instanssi | Host, issuer, PVC koko/storageClass |
Esimerkki: `helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml`
Esimerkki: `helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml`
---
@@ -74,8 +70,8 @@ Esimerkki: `helm upgrade --install git-pages ./git-pages -n git-pages -f dev-val
| Teknologia | Syy |
|---|---|
| **Gitea `pages`-branch** | Ei Gitea git-pages -integraatiota |
| **Gitea `pages`-branch** | Ei Gitea gitea-reports -integraatiota |
| **deadnews/gitea-pages** | Ei Gitea API -hakua |
| **MinIO / S3** | Erillinen raporttivarasto — ei tämän chartin scope |
| **Gitea forge-auth / PAT** | Julkaisu BasicAuth-tokenilla (`git-pages-publish-auth`) |
| **Gitea forge-auth / PAT** | Julkaisu BasicAuth-tokenilla (`gitea-reports-publish-auth`) |
| **Helm-managed publish Secret** | `publishAuth.create: false` — secret manuaalisesti |
+66
View File
@@ -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}"
+32
View File
@@ -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" ]
}
+6
View File
@@ -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."
+19
View File
@@ -0,0 +1,19 @@
Nginx + upload-sidecar installed.
See docs/secrets.md for secret prerequisites (K8s + Gitea Actions).
Install:
helm upgrade --install gitea-reports ./gitea-reports -n {{ .Release.Namespace }} --create-namespace -f dev-values.yaml
Host: https://{{ .Values.ingress.host }}
Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/{branch}/{sha8}/{suite}/
Publish (CI):
curl -X PUT \
-u publish:<token> \
-H "Content-Type: application/tar+gz" \
--data-binary @report.tar.gz \
https://{{ .Values.ingress.host }}/{owner}/{repo}/{branch}/{sha8}/{suite}/
Upgrade: helm upgrade {{ .Release.Name }} ./gitea-reports -n {{ .Release.Namespace }} -f dev-values.yaml
Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }}
@@ -1,8 +1,8 @@
{{- define "git-pages.name" -}}
{{- define "gitea-reports.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "git-pages.fullname" -}}
{{- define "gitea-reports.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
@@ -15,25 +15,25 @@
{{- end }}
{{- end }}
{{- define "git-pages.chart" -}}
{{- define "gitea-reports.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "git-pages.labels" -}}
helm.sh/chart: {{ include "git-pages.chart" . }}
{{ include "git-pages.selectorLabels" . }}
{{- define "gitea-reports.labels" -}}
helm.sh/chart: {{ include "gitea-reports.chart" . }}
{{ include "gitea-reports.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "git-pages.selectorLabels" -}}
app.kubernetes.io/name: {{ include "git-pages.name" . }}
{{- define "gitea-reports.selectorLabels" -}}
app.kubernetes.io/name: {{ include "gitea-reports.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "git-pages.componentLabels" -}}
{{ include "git-pages.labels" . }}
{{- define "gitea-reports.componentLabels" -}}
{{ include "gitea-reports.labels" . }}
app.kubernetes.io/component: pages-server
{{- end }}
@@ -2,11 +2,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ include "git-pages.fullname" . }}-tls
name: {{ include "gitea-reports.fullname" . }}-tls
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
secretName: git-pages-tls
secretName: {{ include "gitea-reports.fullname" . }}-tls
dnsNames:
- {{ .Values.ingress.host | quote }}
issuerRef:
+43
View File
@@ -0,0 +1,43 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "gitea-reports.fullname" . }}-config
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
data:
default.conf: |
client_max_body_size {{ .Values.nginx.bodySize }};
server {
listen {{ .Values.nginx.port }};
root /app/data;
autoindex on;
client_max_body_size {{ .Values.nginx.bodySize }};
location / {
try_files $uri $uri/ $uri/index.html =404;
}
}
upload-server.py: |
#!/usr/bin/env python3
import http.server, os, sys, tarfile, io, urllib.parse
PORT = int(os.environ.get("UPLOAD_PORT", "8081"))
DATA_ROOT = "/app/data"
class Handler(http.server.BaseHTTPRequestHandler):
def do_PUT(self):
path = urllib.parse.unquote(self.path)
dest = DATA_ROOT + path.rstrip("/")
os.makedirs(dest, exist_ok=True)
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
with tarfile.open(fileobj=io.BytesIO(body), mode="r:gz") as tar:
tar.extractall(dest)
self.send_response(201)
self.end_headers()
def log_message(self, fmt, *args):
sys.stderr.write("%s - %s\n" % (self.client_address[0], fmt % args))
http.server.HTTPServer(("", PORT), Handler).serve_forever()
+75
View File
@@ -0,0 +1,75 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "gitea-reports.fullname" . }}
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "gitea-reports.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "gitea-reports.componentLabels" . | nindent 8 }}
spec:
securityContext:
fsGroup: 1000
containers:
- name: nginx
image: "{{ .Values.nginx.image }}:{{ .Values.nginx.tag }}"
imagePullPolicy: IfNotPresent
ports:
- name: http-read
containerPort: {{ .Values.nginx.port }}
protocol: TCP
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
resources:
{{- toYaml .Values.nginx.resources | nindent 12 }}
- name: upload
image: "{{ .Values.upload.image }}:{{ .Values.upload.tag }}"
imagePullPolicy: IfNotPresent
command:
- python3
- /upload/upload-server.py
ports:
- name: http-write
containerPort: {{ .Values.upload.port }}
protocol: TCP
volumeMounts:
- name: upload-script
mountPath: /upload
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
resources:
{{- toYaml .Values.upload.resources | nindent 12 }}
volumes:
- name: nginx-conf
configMap:
name: {{ include "gitea-reports.fullname" . }}-config
items:
- key: default.conf
path: default.conf
- name: upload-script
configMap:
name: {{ include "gitea-reports.fullname" . }}-config
defaultMode: 0755
items:
- key: upload-server.py
path: upload-server.py
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "gitea-reports.fullname" . }}-data
{{- end }}
@@ -2,9 +2,9 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: {{ include "git-pages.fullname" . }}
name: {{ include "gitea-reports.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
entryPoints:
- {{ .Values.ingress.entryPoints.websecure }}
@@ -14,24 +14,24 @@ spec:
(Method(`PATCH`) || Method(`PUT`))
kind: Rule
middlewares:
- name: {{ include "git-pages.fullname" . }}-publish-auth
- name: {{ include "gitea-reports.fullname" . }}-publish-auth
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
- name: {{ include "gitea-reports.fullname" . }}
port: {{ .Values.upload.port }}
- match: Host(`{{ .Values.ingress.host }}`) && (Method(`GET`) || Method(`HEAD`))
kind: Rule
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
- name: {{ include "gitea-reports.fullname" . }}
port: {{ .Values.nginx.port }}
tls:
secretName: git-pages-tls
secretName: {{ include "gitea-reports.fullname" . }}-tls
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: {{ include "git-pages.fullname" . }}-http
name: {{ include "gitea-reports.fullname" . }}-http
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
entryPoints:
- {{ .Values.ingress.entryPoints.web }}
@@ -41,8 +41,8 @@ spec:
!PathPrefix(`/.well-known/acme-challenge/`)
kind: Rule
middlewares:
- name: {{ include "git-pages.fullname" . }}-https-redirect
- name: {{ include "gitea-reports.fullname" . }}-https-redirect
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
- name: {{ include "gitea-reports.fullname" . }}
port: {{ .Values.nginx.port }}
{{- end }}
+22
View File
@@ -0,0 +1,22 @@
{{- if .Values.ingress.enabled }}
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "gitea-reports.fullname" . }}-publish-auth
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
basicAuth:
secret: {{ include "gitea-reports.fullname" . }}-publish-auth
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "gitea-reports.fullname" . }}-https-redirect
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
redirectScheme:
scheme: https
permanent: true
{{- end }}
@@ -2,9 +2,9 @@
apiVersion: v1
kind: Secret
metadata:
name: git-pages-publish-auth
name: {{ include "gitea-reports.fullname" . }}-publish-auth
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
type: Opaque
stringData:
users: |
@@ -2,9 +2,9 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "git-pages.fullname" . }}-data
name: {{ include "gitea-reports.fullname" . }}-data
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.accessMode }}
@@ -2,12 +2,14 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
data:
retention.json: |
{{- .Values.retention.rules | toJson | nindent 4 }}
retention-lib.sh: |
{{- .Files.Get "files/retention-lib.sh" | nindent 4 }}
retention-cleanup.sh: |
{{- .Files.Get "files/retention-cleanup.sh" | nindent 4 }}
retention-run.sh: |
@@ -2,9 +2,9 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
schedule: {{ .Values.retention.schedule | quote }}
concurrencyPolicy: Forbid
@@ -16,10 +16,10 @@ spec:
template:
metadata:
labels:
app.kubernetes.io/name: git-pages-retention
app.kubernetes.io/name: {{ include "gitea-reports.fullname" . }}-retention
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: git-pages-retention
serviceAccountName: {{ include "gitea-reports.fullname" . }}-retention
restartPolicy: OnFailure
containers:
- name: retention
@@ -34,15 +34,9 @@ spec:
set -euo pipefail
apt-get update -qq
apt-get install -y --no-install-recommends curl jq >/dev/null
chmod +x /scripts/retention-run.sh /scripts/retention-cleanup.sh
chmod +x /scripts/retention-run.sh
/scripts/retention-run.sh
env:
- name: NAMESPACE
value: {{ .Release.Namespace | quote }}
- name: DEPLOYMENT
value: {{ include "git-pages.fullname" . | quote }}
- name: INSTANCE
value: {{ .Release.Name | quote }}
- name: DATA_ROOT
value: /app/data
- name: RETENTION_CONFIG
@@ -52,7 +46,7 @@ spec:
- name: GITEA_TOKEN
valueFrom:
secretKeyRef:
name: git-pages-retention-gitea
name: {{ include "gitea-reports.fullname" . }}-retention-gitea
key: token
volumeMounts:
- name: data
@@ -64,14 +58,14 @@ spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ include "git-pages.fullname" . }}-data
claimName: {{ include "gitea-reports.fullname" . }}-data
- name: scripts
configMap:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
defaultMode: 0755
- name: config
configMap:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
items:
- key: retention.json
path: retention.json
@@ -2,16 +2,16 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale"]
@@ -23,15 +23,15 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
subjects:
- kind: ServiceAccount
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
namespace: {{ .Release.Namespace }}
{{- end }}
+19
View File
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "gitea-reports.fullname" . }}
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
{{- include "gitea-reports.selectorLabels" . | nindent 4 }}
ports:
- name: http-read
port: {{ .Values.nginx.port }}
targetPort: http-read
protocol: TCP
- name: http-write
port: {{ .Values.upload.port }}
targetPort: http-write
protocol: TCP
+150
View File
@@ -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"
}
@@ -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
+113
View File
@@ -0,0 +1,113 @@
# Helm Registry Setup (OCI)
Pipeline paketoi Helm chartin OCI-artefaktiksi ja pushee sen OCI-rekisteriin.
---
## 1. Konfiguroi `gitea-env.conf`
```
# HELM_REGISTRY on muotoa: registry.example.com/org
#
# host+org: registry.example.com/org
#
# Pipeline rakentaa OCI-refin: oci://${HELM_REGISTRY}/<chart-name>:${VERSION}
# (chart-name tulee Chart.yaml:n name-kentästä)
HELM_REGISTRY=gitea.app.keskikuja.site/niko # PAKOLLINEN — tyhjä ei käy
HELM_UI_URL= # valinnainen — tarkista Giteasta kontin oma UI-osoite, workflow liittää perään /chart-name/VERSION
GIT_TAG_PREFIX=git-pages/ # valinnainen — monorepo-tägäys
VERSION_FILE=git-pages/Chart.yaml # valinnainen — jos Chart.yaml ei rootissa
```
| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `HELM_REGISTRY` | **kyllä** | Registry host + owner (esim. `gitea.app.site/niko`). **Tyhjä pysäyttää workflow'n.** |
| `HELM_UI_URL` | ei | Base-URL OCI-paketin UI-sivulle (ilman chart-nimeä ja versiota). Osoite riippuu onko paketti linkitetty repoon vai ei — tarkista Giteasta. Workflow liittää perään `/chart-name/VERSION`. Jos tyhjä, commit-statusia ei erikseen aseteta. |
| `GIT_TAG_PREFIX` | ei | Etuliite git-tägille. Pakollinen monorepossa, jotta tagit eivät sekoitu muihin komponentteihin. |
| `VERSION_FILE` | ei | Polku version lähteeseen (Chart.yaml, package.json, VERSION). Oletus: juuren `Chart.yaml`. |
**OCI-ref = `oci://${HELM_REGISTRY}/<chart-name>:${VERSION}`**
Esim. `oci://gitea.app.keskikuja.site/niko/git-pages:1.2.3`
Chartin nimi (`<chart-name>`) määräytyy `Chart.yaml`-tiedoston `name`-kentästä.
---
## 2. Luo PAT (Personal Access Token) Giteassa
**Gitea → oma profiili (oikea yläkulma) → Settings → Applications → Manage Access Tokens → Generate New Token**
Valitse scope:
| Scope | Pääsy |
|---|---|
| `package` | **Read and Write** |
> Tämä token toimii salasanana `helm registry login` -komennossa. Muut scopet (kuten `repository`) eivät riitä — konttirekisteri vaatii nimenomaan `package`-scopen.
Tokenin arvo näytetään **vain kerran** luomisen yhteydessä. Kopioi se talteen.
---
## 3. Tallenna PAT repositoryn Secretsiin
Nämä ovat kaksi eri paikkaa:
- **Access Tokenit** (User Settings) = missä luot tokenin
- **Repository Secrets** (Repository Settings) = minne talletat sen workflow'n käyttöön
**Repository → Settings → Actions → Secrets → Add new secret**
| Secret | Arvo |
|---|---|
| `HELM_PASSWORD` | Edellisessä vaiheessa luotu PAT |
`HELM_USER`-secretiä **ei tarvita**. Workflow käyttää automaattisesti `${{ github.actor }}` (workflowin käynnistäjä).
Jos registry vaatii eri käyttäjätunnuksen kuin `github.actor` (esim. Harbor, Artifactory), lisää myös:
| Secret | Arvo |
|---|---|
| `HELM_USER` | Registryn käyttäjätunnus |
---
## 4. Tarkistuslista ennen ajoa
- [ ] `HELM_REGISTRY` asetettu `gitea-env.conf`issa
- [ ] (tarvittaessa) `HELM_UI_URL` asetettu — ilman tätä commit-statusia ei erikseen aseteta
- [ ] PAT luotu Giteassa scopella `package` Read and Write
- [ ] `HELM_PASSWORD`-secret tallennettu repositoryn Secretsiin (se PAT)
- [ ] (tarvittaessa) `HELM_USER`-secret — oletus `github.actor`
---
## 5. Esimerkkejä eri polkurakenteista
### 5a. Hosti + org — Gitea user-taso
```
HELM_REGISTRY=gitea.app.keskikuja.site/niko
```
- OCI-ref: `oci://gitea.app.keskikuja.site/niko/git-pages:1.2.3`
- Paketti käyttäjän `niko` alla. Linkitys repoon tehdään Gitean UI:sta: paketin sivulta (Package → Settings) → linkitä repositoryyn.
- `HELM_PASSWORD` = Gitea PAT scopella `package`
### 5b. Hosti + org — Harbor
```
HELM_REGISTRY=harbor.example.com/projekti
```
- `HELM_USER` = Harbor-käyttäjä
- `HELM_PASSWORD` = Harbor-token
### 5c. Artifactory
```
HELM_REGISTRY=artifactory.example.com/helm-local
```
- `HELM_USER` = service account
- `HELM_PASSWORD` = API-token
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -e
RAW_VERSION=""
if [ -n "${VERSION_FILE-}" ] && [ -f "${VERSION_FILE-}" ]; then
RAW_VERSION=$(tr -d "$(printf '\xef\xbb\xbf')" < "${VERSION_FILE}" | sed -n 's/^version:[[:space:]]*\([^[:space:]]*\).*/\1/p')
if [ -z "${RAW_VERSION}" ]; then
if echo "${VERSION_FILE}" | grep -q -E '\.json$'; then
RAW_VERSION=$(jq -r '.version' "${VERSION_FILE}")
else
RAW_VERSION=$(cat "${VERSION_FILE}" | tr -d '[:space:]')
fi
fi
fi
if [ -z "${RAW_VERSION}" ]; then
if [ -f VERSION ]; then
RAW_VERSION=$(cat VERSION | tr -d '[:space:]')
elif [ -f package.json ]; then
RAW_VERSION=$(jq -r '.version' package.json)
elif [ -f pom.xml ]; then
RAW_VERSION=$(grep -oP '<version>\K[^<]+' pom.xml | head -1)
elif [ -f Chart.yaml ]; then
RAW_VERSION=$(tr -d "$(printf '\xef\xbb\xbf')" < Chart.yaml | sed -n 's/^version:[[:space:]]*\([^[:space:]]*\).*/\1/p')
else
echo "ERROR: No version source found (VERSION_FILE, VERSION, package.json, pom.xml, Chart.yaml)" >&2
exit 1
fi
fi
BASE_VERSION=$(echo "$RAW_VERSION" | cut -d'.' -f1-2)
echo "gitea-ci-library - Tunnistettu Major.Minor versio: $BASE_VERSION"
TAGS_JSON=$(curl -s -f -H "Authorization: token ${GITEA_TOKEN}" \
"${SERVER_URL}/api/v1/repos/${REPO}/tags")
TAG=$(echo "$TAGS_JSON" | jq -r --arg prefix "${GIT_TAG_PREFIX-}" --arg sha "${SHA}" '
if type == "array" then
.[] | select(.commit.sha == $sha and (.name | startswith($prefix))) | .name
else empty end' | head -1)
mkdir -p /tmp/build-ctx
if [ -n "$TAG" ]; then
echo "ARTIFACT_EXISTS=true" > /tmp/build-ctx/build.env
echo "NEXT_VERSION=$TAG" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Artefakti löytyi jo tagilla: $TAG."
else
echo "ARTIFACT_EXISTS=false" > /tmp/build-ctx/build.env
HIGHEST_PATCH=$(echo "$TAGS_JSON" | jq -r --arg prefix "${GIT_TAG_PREFIX-}" --arg bv "${GIT_TAG_PREFIX-}${BASE_VERSION}." '
if type == "array" then .[] | .name | select(startswith($bv)) | sub($bv; "") | tonumber else empty end' | sort -rn | head -1)
if [ -z "$HIGHEST_PATCH" ]; then NEXT_PATCH=0; else NEXT_PATCH=$((HIGHEST_PATCH + 1)); fi
FULL_VERSION="${BASE_VERSION}.${NEXT_PATCH}"
echo "NEXT_VERSION=$FULL_VERSION" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Uusi vapaa versio: $FULL_VERSION"
fi
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env sh
set -eu
DESCRIPTION="${1:-}"
CONTEXT="${2:-}"
SUITE="${3:-}"
STATUS="${4:-success}"
[ -n "$DESCRIPTION" ] || { echo "ERROR: description argument required" >&2; exit 1; }
[ -n "$CONTEXT" ] || { echo "ERROR: context argument required" >&2; exit 1; }
[ -n "$SUITE" ] || { echo "ERROR: suite argument required" >&2; exit 1; }
REPORT_DIR="reports/${SUITE}"
if [ ! -d "$REPORT_DIR" ]; then
echo "ERROR: $REPORT_DIR not found" >&2
sh .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT"
exit 1
fi
FILE_COUNT=0
SUBDIR_COUNT=0
ENTRIES=""
for f in "$REPORT_DIR"/*; do
[ -f "$f" ] || continue
base=$(basename "$f")
[ "$base" = "index.html" ] && continue
FILE_COUNT=$((FILE_COUNT + 1))
ENTRIES="${ENTRIES}file:${base}
"
done
for d in "$REPORT_DIR"/*/; do
[ -d "$d" ] || continue
base=$(basename "$d")
[ -f "$d/index.html" ] || continue
SUBDIR_COUNT=$((SUBDIR_COUNT + 1))
ENTRIES="${ENTRIES}dir:${base}
"
done
TOTAL=$((FILE_COUNT + SUBDIR_COUNT))
if [ "$TOTAL" -eq 0 ]; then
echo "ERROR: no reportable items in $REPORT_DIR" >&2
sh .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT"
exit 1
fi
SHA8=$(echo "${GITHUB_SHA:-xxxxxxxx}" | cut -c1-8)
humanize() {
name="$1"
name=$(echo "$name" | sed -e 's/\.[^.]*$//' -e 's/[-_]/ /g')
first=$(echo "$name" | cut -c1 | tr '[:lower:]' '[:upper:]')
rest=$(echo "$name" | cut -c2-)
echo "${first}${rest}"
}
generate_index() {
{
echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
echo "<title>$DESCRIPTION</title>"
echo '<style>body{font-family:sans-serif;margin:2em;max-width:960px}h1{color:#1e293b}ul{list-style:none;padding:0}li{margin:.5em 0;padding:.5em;background:#f8fafc;border-radius:6px}a{color:#2563eb;text-decoration:none}a:hover{text-decoration:underline}</style>'
echo "</head><body><h1>$DESCRIPTION</h1><ul>"
echo "$ENTRIES" | while IFS= read -r entry; do
[ -z "$entry" ] && continue
entry_type=$(echo "$entry" | cut -d: -f1)
entry_name=$(echo "$entry" | cut -d: -f2-)
if [ "$entry_type" = "file" ]; then
echo "<li><a href=\"$entry_name\">$(humanize "$entry_name")</a></li>"
else
cap=$(echo "$entry_name" | sed 's/\(.\).*/\1/' | tr '[:lower:]' '[:upper:]')$(echo "$entry_name" | sed 's/.//')
echo "<li><a href=\"$entry_name/index.html\">${cap}</a></li>"
fi
done
echo '</ul></body></html>'
} > "$REPORT_DIR/index.html"
}
mkdir -p "$SUITE"
if [ "$TOTAL" -eq 1 ]; then
cp -a "$REPORT_DIR/." "$SUITE/"
sh .ci/scripts/publish-git-pages.sh "$SUITE"
first_entry=$(echo "$ENTRIES" | head -1)
first_type=$(echo "$first_entry" | cut -d: -f1)
first_name=$(echo "$first_entry" | cut -d: -f2-)
if [ "$first_type" = "file" ]; then
SINGLE_ENTRY="$first_name"
else
SINGLE_ENTRY="${first_name}/index.html"
fi
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
cp -a "$REPORT_DIR/." "$SUITE/"
sh .ci/scripts/publish-git-pages.sh "$SUITE"
sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "$SUITE"
fi
rm -rf "$SUITE"
+1 -1
View File
@@ -21,7 +21,7 @@ while IFS='=' read -r key value || [ -n "$key" ]; do
done < "$CONF_FILE"
[ -z "${GITEA_TOKEN:-}" ] && echo "ERROR: GITEA_TOKEN secret is not set" >&2 && ERRORS=1
[ -z "${GIT_PAGES_PUBLISH_TOKEN:-}" ] && echo "ERROR: GIT_PAGES_PUBLISH_TOKEN secret is not set" >&2 && ERRORS=1
[ -z "${REPORTS_PUBLISH_TOKEN:-}" ] && echo "ERROR: REPORTS_PUBLISH_TOKEN secret is not set" >&2 && ERRORS=1
if [ "$ERRORS" -ne 0 ]; then
echo "FATAL: CI config validation failed" >&2
+32 -10
View File
@@ -17,6 +17,11 @@ POLL_INTERVAL="${DISPATCH_POLL_INTERVAL:-10}"
[ -z "$GITEA_API_URL" ] && echo "ERROR: gitea_api_url argument is required" >&2 && exit 1
[ -z "$GITEA_TOKEN" ] && echo "ERROR: gitea_token argument is required" >&2 && exit 1
# Generate unique dispatch_id for display_title matching
# Can be overridden via DISPATCH_ID env var (for tests)
DISPATCH_ID="${DISPATCH_ID:-$(xxd -l 4 -p /dev/urandom 2>/dev/null || openssl rand -hex 4 2>/dev/null || od -An -N4 -tx1 /dev/urandom | tr -d ' \n')}"
INPUTS_JSON=$(echo "$INPUTS_JSON" | jq --arg id "$DISPATCH_ID" '. + {dispatch_id: $id}')
DISPATCH_URL="$GITEA_API_URL/api/v1/repos/$TARGET_REPO/actions/workflows/$WORKFLOW_FILE/dispatches"
DISPATCH_BODY=$(jq -nc --arg ref "$REF" --argjson inputs "$INPUTS_JSON" '{ref: $ref, inputs: $inputs}')
@@ -32,19 +37,30 @@ if [ "$DISPATCH_CODE" != "201" ]; then
exit 1
fi
RUNS_URL="$GITEA_API_URL/api/v1/repos/$TARGET_REPO/actions/runs?status=running"
RUNS_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
-H "Authorization: token $GITEA_TOKEN" "$RUNS_URL")
RUN_ID=$(echo "$RUNS_RESP" | jq -r '.workflow_runs[0].id // empty')
if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
echo "ERROR: Could not find dispatched workflow run" >&2
exit 1
fi
# Poll: find dispatched run by display_title matching
RUN_ID=""
TIMEOUT_SECONDS=$(awk "BEGIN {printf \"%.3f\", $TIMEOUT_MINUTES * 60}")
START_TIME=$(date +%s)
while [ -z "$RUN_ID" ]; do
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
if awk -v e="$ELAPSED" -v t="$TIMEOUT_SECONDS" 'BEGIN { exit !(e >= t) }'; then
echo "ERROR: Timeout after ${TIMEOUT_MINUTES} minutes — run not found" >&2
exit 124
fi
RUNS_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
"$GITEA_API_URL/api/v1/repos/$TARGET_REPO/actions/runs?event=workflow_dispatch&limit=10" \
-H "Authorization: token $GITEA_TOKEN")
RUN_ID=$(echo "$RUNS_RESP" | jq -r --arg id "$DISPATCH_ID" \
'[.workflow_runs[] | select(.display_title | contains($id))] | .[0].id // empty')
[ -z "$RUN_ID" ] && sleep "$POLL_INTERVAL"
done
# Poll: wait for run to complete
while true; do
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
@@ -61,6 +77,12 @@ while true; do
if [ "$STATUS" = "completed" ]; then
CONCLUSION=$(echo "$RUN_RESP" | jq -r '.conclusion // "failure"')
if [ "$CONCLUSION" = "success" ]; then
GITOPS_COMMIT=""
BRANCH_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
"$GITEA_API_URL/api/v1/repos/$TARGET_REPO/branches/$REF" \
-H "Authorization: token $GITEA_TOKEN") || true
GITOPS_COMMIT=$(echo "$BRANCH_RESP" | jq -r '.commit.id // empty')
echo "GITOPS_COMMIT=$GITOPS_COMMIT"
exit 0
fi
echo "ERROR: Workflow completed with conclusion: $CONCLUSION" >&2
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
echo "gitops-dispatch: validating env vars..."
: "${GITOPS_FILE:?}"
: "${GITOPS_YQ_TPL:?}"
: "${GITOPS_VERSION:?}"
: "${GITOPS_SOURCE_REPO:?}"
: "${GITOPS_SOURCE_COMMIT:?}"
: "${GITOPS_REPO:?}"
: "${GITOPS_WORKFLOW:?}"
: "${GITEA_API_URL:?}"
: "${GITEA_TOKEN:?}"
TIMEOUT="${GITOPS_DISPATCH_TIMEOUT:-30}"
echo "gitops-dispatch: constructing inputs..."
INPUTS=$(jq -nc \
--arg file "$GITOPS_FILE" \
--arg yq_tpl "$GITOPS_YQ_TPL" \
--arg version "$GITOPS_VERSION" \
--arg source_repo "$GITOPS_SOURCE_REPO" \
--arg source_commit "$GITOPS_SOURCE_COMMIT" \
--arg git_tag_prefix "${GITOPS_TAG_PREFIX:-}" \
--arg extra_cmd "${GITOPS_EXTRA_CMD:-}" \
--arg author_name "${GIT_USER_NAME:-}" \
--arg author_email "${GIT_USER_EMAIL:-}" \
'{file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_tag_prefix, extra_cmd: $extra_cmd, author_name: $author_name, author_email: $author_email}')
DIR="$(cd "$(dirname "$0")" && pwd)"
echo "gitops-dispatch: dispatching to $GITOPS_REPO/$GITOPS_WORKFLOW..."
set +e
OUTPUT=$(bash "$DIR/dispatch-workflow.sh" \
"$GITOPS_REPO" "$GITOPS_WORKFLOW" "main" \
"$INPUTS" "$GITEA_API_URL" "$GITEA_TOKEN" "$TIMEOUT" 2>&1)
EXIT=$?
set -e
echo "=== DISPATCH OUTPUT (exit=$EXIT) ==="
echo "$OUTPUT"
echo "=== END DISPATCH ==="
STATUS="failure"
GITOPS_SHA=""
if [ "$EXIT" = "0" ]; then
STATUS="success"
GITOPS_SHA=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2)
fi
COMPONENT="${GITOPS_TAG_PREFIX:-${GITOPS_FILE}}"
echo "GITOPS_SUMMARY=${COMPONENT}|${GITOPS_VERSION}|${STATUS}|${GITOPS_SHA}|${GITOPS_REPO}"
exit "$EXIT"
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_gitops_fail() {
local MSG="${1:-GitOps update failed}"
echo "[ERROR] ${MSG}" >&2
if [ -n "${GITOPS_REPO:-}" ] && [ -n "${GITOPS_SHA:-}" ] && \
[ -n "${SOURCE_REPO:-}" ] && [ -n "${SOURCE_COMMIT:-}" ] && \
[ -n "${GITEA_API_URL:-}" ] && [ -n "${GITEA_TOKEN:-}" ]; then
local env repo context
env=$(dirname "${INPUT_FILE}")
repo=$(basename "${SOURCE_REPO}")
context="${repo} ${GITHUB_RUN_ID:-unknown}"
[ -n "${GIT_TAG_PREFIX:-}" ] && context="${repo}/${GIT_TAG_PREFIX} ${GITHUB_RUN_ID:-unknown}"
local SOURCE_URL="${GITEA_API_URL}/${SOURCE_REPO}/commit/${SOURCE_COMMIT}"
ROOT_REPO="${GITOPS_REPO}" ROOT_COMMIT="${GITOPS_SHA}" \
GITEA_API_URL="${GITEA_API_URL}" GITEA_TOKEN="${GITEA_TOKEN}" \
bash "${SCRIPT_DIR}/report-status.sh" failure "Install to ${env} ${VERSION}" \
"${context}" "" "${SOURCE_URL}" 2>/dev/null || true
fi
exit 1
}
_gitops_validate() {
[ -n "${INPUT_FILE:-}" ] || _gitops_fail "INPUT_FILE is required"
[ -n "${YQ_TPL:-}" ] || _gitops_fail "YQ_TPL is required"
[ -n "${VERSION:-}" ] || _gitops_fail "VERSION is required"
[ -n "${SOURCE_REPO:-}" ] || _gitops_fail "SOURCE_REPO is required"
[ -n "${SOURCE_COMMIT:-}" ] || _gitops_fail "SOURCE_COMMIT is required"
[ -n "${GITOPS_REPO:-}" ] || _gitops_fail "GITOPS_REPO is required"
[ -n "${GITEA_TOKEN:-}" ] || _gitops_fail "GITEA_TOKEN is required"
[ -n "${GITEA_API_URL:-}" ] || _gitops_fail "GITEA_API_URL is required"
}
_gitops_success() {
local env repo context
env=$(dirname "${INPUT_FILE}")
repo=$(basename "${SOURCE_REPO}")
context="${repo} ${GITHUB_RUN_ID:-unknown}"
[ -n "${GIT_TAG_PREFIX:-}" ] && context="${repo}/${GIT_TAG_PREFIX} ${GITHUB_RUN_ID:-unknown}"
local SOURCE_URL="${GITEA_API_URL}/${SOURCE_REPO}/commit/${SOURCE_COMMIT}"
ROOT_REPO="${GITOPS_REPO}" ROOT_COMMIT="${GITOPS_SHA}" \
GITEA_API_URL="${GITEA_API_URL}" GITEA_TOKEN="${GITEA_TOKEN}" \
bash "${SCRIPT_DIR}/report-status.sh" success \
"Install to ${env} ${VERSION}" \
"${context}" "" "${SOURCE_URL}"
}
_gitops_nochange() {
local env repo context
env=$(dirname "${INPUT_FILE}")
repo=$(basename "${SOURCE_REPO}")
context="${repo} ${GITHUB_RUN_ID:-unknown}"
[ -n "${GIT_TAG_PREFIX:-}" ] && context="${repo}/${GIT_TAG_PREFIX} ${GITHUB_RUN_ID:-unknown}"
local SOURCE_URL="${GITEA_API_URL}/${SOURCE_REPO}/commit/${SOURCE_COMMIT}"
ROOT_REPO="${GITOPS_REPO}" ROOT_COMMIT="${GITOPS_SHA}" \
GITEA_API_URL="${GITEA_API_URL}" GITEA_TOKEN="${GITEA_TOKEN}" \
bash "${SCRIPT_DIR}/report-status.sh" success \
"Install to ${env} ${VERSION} — no change" \
"${context}" "" "${SOURCE_URL}"
}
_gitops_substitute() {
echo "$1" | sed "s/{{VERSION}}/$2/g"
}
_gitops_update() {
local CLONE_DIR="${GITOPS_TARGET_DIR:-$(mktemp -d)}"
if [ -n "${GITOPS_CLONE_URL:-}" ]; then
git clone "${GITOPS_CLONE_URL}" "${CLONE_DIR}" || _gitops_fail "Failed to clone GitOps repo"
else
git clone "${CLONE_URL}" "${CLONE_DIR}" || _gitops_fail "Failed to clone GitOps repo"
fi
cd "${CLONE_DIR}" || _gitops_fail "Failed to enter clone directory"
yq eval -i "${YQ_EXPR}" "${INPUT_FILE}" || _gitops_fail "Failed to update ${INPUT_FILE}"
if [ -n "${GITOPS_EXTRA_CMD:-}" ]; then
eval "${GITOPS_EXTRA_CMD}" || _gitops_fail "Extra command failed: ${GITOPS_EXTRA_CMD}"
git add -A || _gitops_fail "Failed to stage all changes"
else
git add "${INPUT_FILE}" || _gitops_fail "Failed to stage ${INPUT_FILE}"
fi
if git diff --cached --quiet; then
echo "No changes — ${INPUT_FILE} already at ${VERSION}"
GITOPS_SHA="$(git rev-parse HEAD)"
_gitops_nochange
exit 0
fi
git -c user.name="${GIT_USER_NAME:-gitea-ci-bot}" \
-c user.email="${GIT_USER_EMAIL:-ci@keskikuja.site}" \
commit -m "[skip ci] gitops: update version to ${VERSION}" || _gitops_fail "Failed to commit"
GITOPS_SHA="$(git rev-parse HEAD)"
git push || _gitops_fail "Failed to push"
_gitops_success
}
_gitops_validate
YQ_EXPR=$(_gitops_substitute "${YQ_TPL}" "${VERSION}")
GITEA_HOST=$(echo "${GITEA_API_URL}" | sed 's|https://||' | sed 's|http://||')
CLONE_URL="${GITOPS_CLONE_URL:-https://${GITEA_TOKEN}@${GITEA_HOST}/${GITOPS_REPO}.git}"
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
_gitops_update
fi
+86 -33
View File
@@ -1,64 +1,117 @@
#!/usr/bin/env bash
set -euo pipefail
#!/usr/bin/env sh
set -eu
SUITE_PATH="${1:-}"
[ -n "$SUITE_PATH" ] || { echo "ERROR: suite_path argument required" >&2; exit 1; }
[ -n "${GITEA_API_URL:-}" ] || { echo "ERROR: GITEA_API_URL is not set" >&2; exit 1; }
[ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; }
[ -n "${GIT_PAGES_PUBLISH_TOKEN:-}" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${GITHUB_REPOSITORY:-}" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; }
[ -n "${GITHUB_SHA:-}" ] || { echo "ERROR: GITHUB_SHA is not set" >&2; exit 1; }
[ -n "${GITHUB_REF_NAME:-}" ] || { echo "ERROR: GITHUB_REF_NAME is not set" >&2; exit 1; }
OWNER="${GITHUB_REPOSITORY%%/*}"
REPO="${GITHUB_REPOSITORY##*/}"
SHA8="${GITHUB_SHA:0:8}"
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; }
[ -d "$SUITE" ] || { echo "ERROR: not a directory: $SUITE" >&2; exit 1; }
PUBLISH_SITE_URL="${GIT_PAGES_URL}/"
EMPTY=1
for _f in "$SUITE"/* "$SUITE"/.*; do
[ -e "$_f" ] && { EMPTY=0; break; }
done
[ "$EMPTY" -eq 0 ] || { echo "ERROR: no files to publish in $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}"
# Kopioi raporttitiedostot
mkdir -p "$WORK/$SUITE"
cp -a "$SUITE/." "$WORK/$SUITE/"
# Generoi index.html (sama logiikka kuin nykyään)
cd "$WORK/$SUITE"
if [ ! -f "index.html" ]; then
ITEM_LIST=""
ITEM_COUNT=0
for f in *; do
[ -f "$f" ] || continue
[ "$f" = "index.html" ] && continue
ITEM_LIST="${ITEM_LIST}file:${f}
"
ITEM_COUNT=$((ITEM_COUNT + 1))
done
for d in */; do
[ -d "$d" ] || continue
d="${d%/}"
[ -f "$d/index.html" ] || continue
ITEM_LIST="${ITEM_LIST}dir:${d}
"
ITEM_COUNT=$((ITEM_COUNT + 1))
done
if [ "$ITEM_COUNT" -gt 1 ]; then
{
echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
echo "<title>Test report ${SHA8}</title>"
echo '<style>body{font-family:sans-serif;margin:2em;max-width:960px}'
echo 'h1{color:#1e293b}ul{list-style:none;padding:0}'
echo 'li{margin:.5em 0;padding:.5em;background:#f8fafc;border-radius:6px}'
echo 'a{color:#2563eb;text-decoration:none}a:hover{text-decoration:underline}'
echo '</style></head><body>'
echo "<h1>Test report <code>${SHA8}</code></h1><ul>"
echo "$ITEM_LIST" | while IFS= read -r item; do
[ -z "$item" ] && continue
item_type=$(echo "$item" | cut -d: -f1)
item_name=$(echo "$item" | cut -d: -f2-)
label=$(echo "$item_name" | sed -e 's/\.[^.]*$//' -e 's/[-_]/ /g')
first=$(echo "$label" | cut -c1 | tr '[:lower:]' '[:upper:]')
rest=$(echo "$label" | cut -c2-)
if [ "$item_type" = "file" ]; then
echo "<li><a href=\"$item_name\">${first}${rest}</a></li>"
else
echo "<li><a href=\"$item_name/index.html\">${first}${rest}</a></li>"
fi
done
echo '</ul></body></html>'
} > "index.html"
fi
fi
mkdir -p "$TARGET"
cp -a "$REPORT_DIR/." "$TARGET/"
cat > "$WORK/${OWNER}/${REPO}/reports/${SHA8}/.meta" <<EOF
{"branch":"${GITHUB_REF_NAME:-}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
# .meta tiedosto retentionia varten
cat > ".meta" <<EOF
{"branch":"${BRANCH}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF
find "$WORK/$OWNER" \( -type f -o -type l \) -print | sed "s|^${WORK}/||" | tar -cf "$TAR" -C "$WORK" -T -
publish() {
local method="$1"
curl -sS -X "$method" "$PUBLISH_SITE_URL" \
-u "${PAGES_USER}:${GIT_PAGES_PUBLISH_TOKEN}" \
-H "Content-Type: application/x-tar" \
-H "Atomic: no" \
-H "Create-Parents: yes" \
--data-binary @"$TAR" \
-o /tmp/git-pages-publish-response.txt \
-w "%{http_code}"
}
cd "$WORK"
HTTP_CODE=$(publish PATCH)
# Pakkaa: pelkät tiedostot ilman $SUITE-etuliitettä
tar czf "$TAR" -C "$WORK/$SUITE" .
# PUT upload-sidecariin
PUBLISH_URL="${GIT_PAGES_URL}/${OWNER}/${REPO}/${BRANCH}/${SHA8}/${SUITE}/"
HTTP_CODE=$(curl -sS -X PUT "$PUBLISH_URL" \
-u "${PAGES_USER}:${REPORTS_PUBLISH_TOKEN}" \
-H "Content-Type: application/tar+gz" \
--data-binary @"$TAR" \
-o /tmp/git-pages-publish-response.txt \
-w "%{http_code}")
case "$HTTP_CODE" in
200|201|204) ;;
*)
echo "ERROR: git-pages publish HTTP ${HTTP_CODE}" >&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}"
+1 -1
View File
@@ -7,7 +7,7 @@ SUITE_PATH="${1:-}"
[ -n "${GITEA_API_URL:-}" ] || { echo "ERROR: GITEA_API_URL is not set" >&2; exit 1; }
[ -n "${GITEA_TOKEN:-}" ] || { echo "ERROR: GITEA_TOKEN is not set" >&2; exit 1; }
[ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; }
[ -n "${GIT_PAGES_PUBLISH_TOKEN:-}" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; }
SCRIPT_DIR="$(dirname "$0")"
+6 -6
View File
@@ -1,11 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
# https://docs.gitea.com/api/next/#tag/repository/operation/repoCreateStatus
#!/usr/bin/env sh
set -eu
STATE="${1:-}"
DESCRIPTION="${2:-}"
KEY="${3:-commit-${GITHUB_SHA:0:8}}"
SHA8=$(echo "${GITHUB_SHA:-}" | cut -c1-8)
KEY="${3:-commit-${SHA8}}"
SUITE="${4:-}"
CUSTOM_URL="${5:-}"
@@ -18,7 +17,8 @@ if [ -n "$CUSTOM_URL" ]; then
URL="$CUSTOM_URL"
elif [ -n "$SUITE" ]; then
SUITE="${SUITE%/}/"
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/${SUITE}"
SHA8_CUT=$(echo "$GITHUB_SHA" | cut -c1-8)
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
+227
View File
@@ -0,0 +1,227 @@
---
name: ci-container-build
description: |
Creating or modifying CI container build workflows. Activates when consumer
needs to build a custom CI container image (multi-tool image for tests,
linting, validation) that is pushed to a container registry for use in
other pipeline jobs.
activation-gate: |
User mentions CI container, custom CI image, ci-container-build, Dockerfile
for CI tools, multi-tool container, or needs to build/push a container that
other CI jobs will use as their runtime environment.
category: ci
impact: high
---
# CI Container Build — Template
Template jolla consumer luo oman CI-kontin build-workflown.
Kontti sisältää useamman työkalun yhdistelmän (esim. helm + kubeconform + xsltproc),
jota muut jobit käyttävät ajonaikaisena ympäristönä.
## Rakenne
Vain `workflow_dispatch`**ei automaattista buildausta koskaan**. Kontti buildataan
manuaalisesti Gitea Actions UI:sta. Tiedoston ilmestyessä main-haaraan workflow näkyy
välittömästi Actions-tabissa ajettavana.
Build-prosessi lataa ensin `config-provider.yml`:llä `DOCKER_REGISTRY`:n
conf-tiedostosta, sitten buildaa ja puskaa kontin.
Kun kontti on pushattu registryyn, se on muiden pipeline-jobien käytettävissä
`latest`-tägillä — rebuild = käyttöönotto. Mitään versioviittauksia ei tarvitse
päivittää.
## Offline-periaate (DoD)
CI-kontin **Definition of Done**:
> Kontti ei lataa mitään pipeline-vaiheessa (`workflow run` -stepit) eikä kontin
> runtime-prosessissa (`container:` / `docker run`). Kaikki riippuvuudet
> (kielikohtaiset paketit, työkalut, binäärit) on joko:
> - Pre-cachattu kontin **build-vaiheessa** Dockerfilessä, TAI
> - Kopioitu multi-stage buildilla toisesta imagesta (`COPY --from`)
>
> Ainoa sallittu lataushetki on `docker build`. Sen jälkeen kontti toimii
> ilman verkkoyhteyttä.
**Miksi:** Toistettavuus, air gap -yhteensopivuus, nopeus. Pipeline ei saa
epäonnistua sen takia että ulkoinen registry on alhaalla tai että `go mod download`
joutuu latamaan 100 modulia jokaisella testiajolla.
**Kielikohtaiset pre-cachet:** Jos kontissa ajetaan kielikohtaista testiä
(Go, Java, Node, Python, ...), kaikki kielikohtaiset riippuvuudet on
pre-cachattava Dockerfilessä build-vaiheessa:
- Go: `COPY go.mod go.sum ./``RUN go mod download`
- Java/Maven: `COPY pom.xml ./``RUN mvn dependency:go-offline`
- Node: `COPY package.json package-lock.json ./``RUN npm ci --omit=dev`
- Python: `COPY requirements.txt ./``RUN pip wheel --wheel-dir=/wheels -r requirements.txt``COPY --from` käyttöön
## Nimeäminen
CI-kontin build-workflow noudattaa samaa nimeämiskonventiota kuin muutkin
tiedostot `.gitea/workflows/`-kansiossa:
```
<komponentti>.ci-feature.yml ← feature-haaran reititin
<komponentti>.ci-main.yml ← main-haaran reititin
<komponentti>.<testityyppi>.yml ← yksittäinen testi tai operaatio
<komponentti>.ci-container-build-<kontti>.yml ← CI-kontin build-workflow
<komponentti>.gitea-env.conf ← komponenttikohtainen konfiguraatio
```
Single repossa `<komponentti>` jätetään pois — tiedostot ovat suoraan `ci-feature.yml`,
`ci-main.yml`, `<testityyppi>.yml`, `ci-container-build-<kontti>.yml`.
Monorepossa prefiksi pitää komponentin tiedostot yhdessä: `ls <komponentti>.*` löytää kaikki
kerralla. **Olemassaolevia prefiksejä ei saa poistaa.**
Esimerkkejä:
```
.gitea/workflows/chart.ci-container-build-helm.yml
.gitea/workflows/api.ci-container-build-node.yml
.gitea/workflows/ci-container-build-bats.yml ← single repo
```
## Template
> **Korvaa kaikki `__SUURAAKKOSET__`-placeholderit projektin todellisilla arvoilla.**
> Ainoastaan `${{ ... }}`-syntaksilla merkityt Gitea Actions -muuttujat ovat ajonaikaisia
> eikä niitä korvata.
Luo `.gitea/workflows/__KOMPONENTTI__.ci-container-build-__KONTTI__.yml`
(single repo: `ci-container-build-__KONTTI__.yml`):
```yaml
name: CI Container Build & Push
on:
workflow_dispatch:
inputs:
config_path:
required: true
type: string
description: 'Polku .gitea-env.conf-tiedostoon (esim. .gitea/workflows/chart.gitea-env.conf)'
dockerfile_path:
required: true
type: string
description: 'Polku Dockerfileen (esim. ci-helm.Dockerfile)'
image_name:
required: true
type: string
description: 'Kontin nimi ilman registry-polkua (esim. ci-helm)'
tag:
required: true
type: string
default: 'latest'
description: 'Image-tägi'
jobs:
load-config:
uses: __OWNER__/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
uses: __OWNER__/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
dockerfile_path: ${{ inputs.dockerfile_path }}
image_name: ${{ inputs.image_name }}
tag: ${{ inputs.tag }}
```
### Käyttö
**Gitea Actions UI:sta** (heti kun tiedosto on main-haarassa):
```
Gitea → Actions → CI Container Build & Push → Run workflow
config_path: .gitea/workflows/__KOMPONENTTI__.gitea-env.conf
dockerfile_path: ci-__TYÖKALU__.Dockerfile
image_name: ci-__TYÖKALU__
tag: latest
```
### Dockerfile
Dockerfile yhdistää tarvitut työkalut yhteen konttiin.
**Kaikki riippuvuudet ladataan build-vaiheessa — kontti on täysin itseriittoinen.**
```dockerfile
# Tapa A: COPY --from toisesta imagesta
FROM __BASE_IMAGE__:__VERSION__
COPY --from=__SOURCE_IMAGE__:__VERSION__ /path/to/binary /usr/local/bin/
RUN apk add --no-cache __PAKETIT__
# Tapa B: Build-vaiheen curl-lataus
FROM __BASE_IMAGE__:__VERSION__
RUN apk add --no-cache curl __PAKETIT__ && \
curl -fsSL __URL__/__BINARY__.tar.gz | tar xz -C /usr/local/bin && \
apk del curl
# Tapa C: Multi-stage + kielikohtainen pre-cache
FROM __BASE_IMAGE__:__VERSION__ AS deps
COPY go.mod go.sum ./
RUN go mod download
FROM deps AS build
COPY . .
RUN go test -c -o /tmp/test.bin ./...
FROM __BASE_IMAGE__:__VERSION__
COPY --from=deps /go/pkg/mod /go/pkg/mod
COPY --from=build /tmp/test.bin /usr/local/bin/test
```
`COPY --from` on kevyempi (ei curl-asennusta). `curl` (Tapa B) on sallittu
vain build-vaiheessa — `apk del curl` poistaa työkalun ennen runtimea.
Tapa C pre-cacheaa kielikohtaiset riippuvuudet ja tuottaa täysin
offline-runtime-kontin.
## Testaus ennen julkaisua
Konttia ei saa pushata registryyn ennen kuin se on validoitu.
### 1. Aja testit kontin sisällä
Testit on ajettava **kontin sisällä**, ei suoraan lokaalilla koneella.
```bash
# OIKEIN — kontin sisällä
docker build -t ci-tyokalu:test .
docker run --rm -v "$(pwd):/repo" -w /repo ci-tyokalu:test bash -c 'bats tests/'
# VÄÄRIN — lokaalit binäärit vs kontti
bats tests/ # eri bash/työkalut kuin kontissa
bashcov -- bats tests/ # eri ruby-versio kuin kontissa
```
Lokaali ympäristö (macOS, eri kirjastoversiot) poikkeaa aina kontista.
Testi voi mennä läpi lokaalissa mutta failata CI:ssä, tai päinvastoin.
### 2. Fragile-testien seulonta (10x ajo)
Aja koko testipaketti **10 kertaa peräkkäin** kontin sisällä ennen pushausta:
```bash
for i in $(seq 1 10); do
echo "=== RUN $i ==="
docker run --rm -v "$(pwd):/repo" -w /repo ci-tyokalu:test \
bash -c 'bats tests/' || exit 1
done
```
Jos yksikin ajo failaa, kontissa on fragile testi — korjaa ennen pushausta.
Fragile testit syövät devaukseen käytettyä aikaa turhilla uusinta-ajoilla.
## Mitä EI kannata tehdä
- Älä lisää `workflow_call`-triggariä — CI-konttia ei koskaan buildata automaattisesti
- Älä poista `<komponentti>.`-prefiksiä olemassaolevista tiedostoista — ne kuuluvat monorepo-nimeämiskonventioon
- Älä sisällytä CI-konttiin mitään sovelluskoodia — vain työkalut
- Älä koskaan lataa mitään pipeline- tai runtime-vaiheessa — kaikki lataukset kuuluvat `docker build` -vaiheeseen (Offline-periaate)
- Älä jätä kielikohtaisia riippuvuuksia pre-cachaamatta — `go mod download`, `npm install`, `mvn dependency:go-offline` jne. ajetaan Dockerfilessä, ei pipelinessä
+636
View File
@@ -0,0 +1,636 @@
# Consumer Pipelines — Reference
Mallipohjat, esimerkit ja konfiguraatiot. Katso säännöt `SKILL.md`:stä.
## Pre-cache-esimerkit (Offline Container)
Alla Dockerfile-esimerkit kielikohtaisista pre-cacheista. Kaikki ajetaan
build-vaiheessa — kontti on täysin itseriittoinen eikä lataa mitään
pipeline- tai runtime-vaiheessa.
### Go
```dockerfile
FROM golang:1.24-alpine AS deps
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
FROM deps AS test-build
COPY . .
RUN go test -c -o /tmp/test.bin ./...
FROM alpine:3.21
RUN apk add --no-cache git nodejs
COPY --from=deps /go/pkg/mod /go/pkg/mod
COPY --from=test-build /tmp/test.bin /usr/local/bin/test
```
### Node.js
```dockerfile
FROM node:22-alpine AS deps
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:22-alpine
RUN apk add --no-cache git
COPY --from=deps /build/node_modules /app/node_modules
COPY . /app
WORKDIR /app
```
### Java / Maven
```dockerfile
FROM maven:3.9-eclipse-temurin-21 AS deps
WORKDIR /build
COPY pom.xml ./
RUN mvn dependency:go-offline -B
FROM maven:3.9-eclipse-temurin-21 AS build
COPY --from=deps /root/.m2 /root/.m2
COPY . .
RUN mvn package -B -DskipTests
FROM eclipse-temurin:21-jre
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
COPY --from=build /build/target/*.jar /app/app.jar
WORKDIR /app
```
### Python
```dockerfile
FROM python:3.12-alpine AS deps
WORKDIR /build
COPY requirements.txt ./
RUN pip wheel --wheel-dir=/wheels -r requirements.txt
FROM python:3.12-alpine
RUN apk add --no-cache git
COPY --from=deps /build/wheels /wheels
COPY --from=deps /build/requirements.txt /
RUN pip install --no-index --find-links=/wheels -r /requirements.txt && rm -rf /wheels
COPY . /app
WORKDIR /app
```
### Helm + Node.js (korvaa helm-build-push.yml:n runtime-apk)
```dockerfile
FROM alpine/helm:3.16.0 AS helm-bin
FROM node:22-alpine
RUN apk add --no-cache git
COPY --from=helm-bin /usr/bin/helm /usr/local/bin/helm
```
Tämä kontti korvaa `helm-build-push.yml`:n `alpine/helm:3.19.0`-image-riippuvuuden
ja poistaa tarpeen asentaa node.js runtime-vaiheessa.
## Reititin — täydellinen esimerkki
```yaml
jobs:
load-config:
uses: <owner>/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
<test-1>:
needs: [load-config]
uses: ./.gitea/workflows/<component>.<test-1>.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
<test-2>:
needs: [load-config]
uses: ./.gitea/workflows/<component>.<test-2>.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
report-summary:
needs: [load-config, <test-1>, <test-2>]
if: always()
uses: <owner>/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: <suite-1> <suite-2>
```
## CI-kontin build — parametroitu workflow
CI-kontin build on `workflow_dispatch`-triggeröity job, joka näkyy Gitea Actionsissa kuten Jenkinsin
parametroitu job — käyttäjä antaa inputit UI:sta ennen ajoa.
```yaml
name: CI Container Build <työkalu>
on:
workflow_dispatch:
inputs:
config_path:
required: true
type: string
default: '.gitea/workflows/<komponentti>.gitea-env.conf'
description: 'Polku .gitea-env.conf-tiedostoon'
dockerfile_path:
required: true
type: string
default: '<komponentti>/Dockerfile.ci-<työkalu>'
description: 'Polku Dockerfileen'
image_name:
required: true
type: string
default: 'ci-<työkalu>'
description: 'Kontin nimi ilman registry-polkua'
tag:
required: true
type: string
default: 'latest'
description: 'Image-tägi'
jobs:
load-config:
uses: <owner>/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
uses: <owner>/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
dockerfile_path: ${{ inputs.dockerfile_path }}
image_name: ${{ inputs.image_name }}
tag: ${{ inputs.tag }}
```
### CI-kontin ajaminen testijobissa
**Ainoa sallittu tapa** consumer-puolella on `container:`-direktiivi. `docker run` komennolla
kontin käynnistäminen stepin sisällä on anti-pattern. `container:`-direktiivillä kaikki stepit
ajetaan samassa kontissa — tiedostot ovat suoraan filesystemillä eikä erillistä
volyyminhallintaa tarvita.
```yaml
jobs:
<työkalu>:
runs-on: ubuntu-latest
container:
image: ${{ inputs.<image-name> }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: <owner>/gitea-ci-library
path: .ci
- name: Run <työkalu>
shell: bash
run: |
mkdir -p "reports/<suite>"
<komento> > "reports/<suite>/results.txt" 2>&1
- name: Post-process reports
if: always()
run: |
<mahdollinen_raporttien_jälkikäsittely>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
```
Monorepossa context ja description sisältävät komponentin nimen:
```yaml
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
```
**Usean runnerin cache-ongelma:** Jos eri kerroilla käynnistyy eri runnereita,
niillä voi olla eri versio `latest`-imagen digesteistä. Ratkaisuja:
- Rebuildaa kontti ja aja `docker pull <image>` manuaalisesti kaikilla runnereilla
- Käytä versioitua tagia (`v2`, `v3`, ...) ja päivitä workflow'n default buildauksen jälkeen
**Mallit:**
- `example-cucumber-tests.yml` — ei post-processia
- `example-bats-tests.yml` — post-process coverage + report
## Raporttitasot — tarkat YAML-mallit
### Taso 1: Ei jälkikäsittelyä
Single repo:
```yaml
- name: Run tests
shell: bash
run: |
mkdir -p "reports/<suite>"
<testikomento>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
```
Monorepo:
```yaml
- name: Run tests
shell: bash
run: |
mkdir -p "reports/<suite>"
<testikomento>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
```
### Taso 2: Jälkikäsittely tarvitaan
Single repo:
```yaml
- name: Run tests
shell: bash
run: |
mkdir -p "reports/<suite>"
<testikomento> > "reports/<suite>/results.txt" 2>&1
- name: Post-process coverage
if: always()
run: <siirrä coverage-data reports/<suite>/coverage/-hakemistoon>
- name: Post-process test report
if: always()
run: <HTML-generointi raa'asta outputista>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
```
Monorepo:
```yaml
- name: Run tests
shell: bash
run: |
mkdir -p "reports/<suite>"
<testikomento> > "reports/<suite>/results.txt" 2>&1
- name: Post-process coverage
if: always()
run: <siirrä coverage-data reports/<suite>/coverage/-hakemistoon>
- name: Post-process test report
if: always()
run: <HTML-generointi raa'asta outputista>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
```
### Väärin vs oikein — yksi asia per step
```yaml
# VÄÄRIN — helm template fail → kubeconform jää ajamatta, report jää tekemättä
- name: Run tests
run: |
helm template ... > /tmp/manifests.yaml
kubeconform ... > results.txt 2>&1
# OIKEIN — erilliset stepit
- name: Helm template
run: helm template platform-helm/ -f values.yaml > /tmp/manifests.yaml 2>&1
- name: Kubeconform
if: success()
run: |
mkdir -p reports/kubeconform
kubeconform ... > reports/kubeconform/results.txt 2>&1
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "Helm kubeconform" helm-test kubeconform ${{ job.status }}
```
Monorepossa:
```yaml
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: Helm kubeconform" <komponentti>.helm-test kubeconform ${{ job.status }}
```
### Väärin vs oikein — post-process
```yaml
# VÄÄRIN — jos coverage epäonnistuu, report jää generoimatta
- name: Post-process reports
run: |
bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
bash .ci/.gitea/scripts/bats-report.sh reports/bats
# OIKEIN — erilliset stepit if: always()
- name: Post-process coverage
if: always()
run: bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
- name: Post-process test report
if: always()
run: bash .ci/.gitea/scripts/bats-report.sh reports/bats
```
## Raportin julkaisukelpoisuus
`ci-report.sh` päättää onko raportti julkaisukelpoinen skannaamalla `reports/<suite>/`-hakemistoa.
### Mitä skannataan
| Mitä | Sääntö |
|---|---|
| **Tiedostot (FILES)** | Kaikki `reports/<suite>/`-juuressa olevat tiedostot paitsi `index.html` |
| **Alihakemistot (SUBDIRS)** | Vain ne, joissa on `index.html` |
### Julkaisukelpoisuus
| Tila | Seuraus |
|---|---|
| `FILES + SUBDIRS = 0` | **Failure**`ci-report.sh` palauttaa virheen, raporttia ei julkaista |
| `FILES + SUBDIRS = 1` | Suora linkki itemiin — ei generoi index-sivua |
| `FILES + SUBDIRS > 1` | Generoi `reports/<suite>/index.html`-sivun, linkit kaikkiin itemeihin |
### Hakemistorakenne
```
reports/<suite>/
├── results.txt ← testin stdout (skannataan FILES)
├── test-report.html ← generoitu HTML (skannataan FILES)
└── <mikä tahansa>/ ← alihakemisto (skannataan SUBDIRS)
└── index.html ← VAIN jos tämä on olemassa
```
### Esimerkki: coverage-näkymä
```
reports/<suite>/coverage/index.html ← on olemassa
```
Coverage-dataa ei siirretä automaattisesti. Testin tai post-process-stepin pitää
siirtää coverage `reports/<suite>/coverage/`-hakemistoon ja varmistaa että `index.html` on mukana.
**Provider vastuulla:** `ci-report.sh` (provider-skripti) hoitaa sekä hakemistorakenteen
skannauksen, `index.html`-generoinnin että julkaisun git-pagesiin. Consumer tuottaa
vain raakatiedostot `reports/<suite>/`-hakemistoon — `ci-report.sh` päättää
julkaisukelpoisuuden ja generoi tarvittavan navigaation.
## Debug-ohje: raportti ei näy
### 1. Aja lokaalisti samalla komennolla kuin CI
```bash
mkdir -p reports/bats
bashcov -- bats tests/ > reports/bats/results.txt 2>&1
echo "exit: $?"
ls -la reports/bats/
```
### 2. Lisää `echo "DEBUG: ..." >&2` ennen ja jälkeen kriittisen operaation
```bash
echo "DEBUG: coverage exists? $([ -d coverage ] && echo YES || echo NO)" >&2
echo "DEBUG: target/index.html exists? $([ -f reports/suite/coverage/index.html ] && echo YES || echo NO)" >&2
```
### 3. Tarkista kutsuparametrit
Yleisin virhe: skripti odottaa `$1` = X, mutta kutsuja antaa `$1` = Y ja `$2` = X.
### 4. Tarkista tiedostopolut
1. Onko lähdetiedosto olemassa ennen kopiointia?
2. Onko kohde olemassa kopioinnin jälkeen?
3. Onko `index.html` subdirissä (vaaditaan `ci-report.sh`:lle)?
### 5. Poista debug-echot kun ongelma on korjattu
### 6. Älä kokeile — debuggaa
Kokeilu = arvaus. Debuggaus = lisää echo, aja, lue logi, eristä ongelma. Vasta sitten korjaa.
## Konfiguraatiotiedosto (.gitea-env.conf)
Tiedosto on `key=value`-muotoinen (kuten `.env`). Kommentit ja tyhjät rivit sallittuja.
### Single repo
```ini
# .gitea/workflows/gitea-env.conf
GITEA_API_URL=https://gitea.example.com
GIT_PAGES_URL=https://reports.example.com
```
### Docker-artifaktin buildaavat projektit
```ini
DOCKER_REGISTRY=gitea.example.com/myorg
DOCKER_IMAGE_NAME=my-service
DOCKER_UI_URL=https://gitea.example.com/myorg/-/packages/container
#DOCKERFILE=Dockerfile.platform # valinnainen, oletus Dockerfile
```
`DOCKER_UI_URL` ei sisällä image-nimeä — se on puhdas container-registryn osoite.
Image-nimi lisätään automaattisesti URL:iin `docker-build-push.yml`:ssä.
### Helm-artifaktin buildaavat projektit
```ini
HELM_REGISTRY=gitea.example.com/myorg
GIT_TAG_PREFIX=git-pages/
VERSION_FILE=git-pages/Chart.yaml
```
| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `HELM_REGISTRY` | **kyllä** | Registry host + owner, esim. `gitea.example.com/myorg`. **Tyhjä pysäyttää workflow'n.** |
| `GIT_TAG_PREFIX` | ei | Etuliite git-tägille. Pakollinen monorepossa. |
| `VERSION_FILE` | ei | Polku version lähteeseen. Oletus: juuren `Chart.yaml`. |
### Salaisuudet (Gitea Settings → Secrets)
| Secret | Pakollinen |
|---|---|
| `GITEA_TOKEN` | Aina (Gitean sisäinen, automaattisesti saatavilla) |
| `REPORTS_PUBLISH_TOKEN` | Aina |
| `DOCKER_USERNAME` | Vain jos buildaat kontteja |
| `DOCKER_PASSWORD` | Vain jos buildaat kontteja |
| `HELM_USER` | Vain jos pushaat Helm chartin OCI-rekisteriin (oletus `github.actor`) |
| `HELM_PASSWORD` | Vain jos pushaat Helm chartin OCI-rekisteriin |
## Monorepo
Monorepossa yhdessä repossa asuu useampi julkaistava komponentti. Jokaiselle komponentille
oma conf-tiedosto `.gitea/workflows/<komponentti>.gitea-env.conf`.
### Suositus: komponentit omiin juurihakemistoihin
On suositeltavaa sijoittaa jokaisen komponentin koko lähdekoodi omaan juuritason
hakemistoonsa (`api/`, `frontend/`, `shared/`). Tämä helpottaa `paths:`-filtteröintiä,
pitää komponentit selkeästi erillään, ja tekee repossa navigoinnista suoraviivaista.
### Ongelmat ja ratkaisut
| Ongelma | Ratkaisu |
|---|---|
| Monta komponenttia, yksi repo — mikä triggeröi? | `paths:`-filtteri: komponentin hakemisto + sen CI-workflow't ja conf-tiedosto |
| Jokaisella komponentilla oma versio | `VERSION_FILE=<komponentti>/package.json` confissa |
| Git-tägit sekaisin ellei nimiavaruutta | `GIT_TAG_PREFIX=<komponentti>/` confissa → tägi `<komponentti>/1.2.3` |
| Eri julkaisutahdit | Riippumattomat CI-triggerit, omat versiopolut |
### Komponenttikohtainen conf
```ini
# .gitea/workflows/<komponentti>.gitea-env.conf
GITEA_API_URL=https://gitea.example.com
GIT_PAGES_URL=https://reports.example.com
DOCKER_REGISTRY=gitea.example.com/myorg
DOCKER_IMAGE_NAME=<image-nimi>
DOCKER_UI_URL=https://gitea.example.com/myorg/-/packages/container
GIT_TAG_PREFIX=<komponentti>/
# Jompikumpi — JSON (.version-kenttä) tai plain text:
VERSION_FILE=<komponentti>/package.json
#VERSION_FILE=<komponentti>/VERSION
```
### Monorepo reititin
```yaml
name: CI <Komponentti> Main
on:
push:
branches:
- main
paths:
- <komponentti>/**
- .gitea/workflows/<komponentti>.*
jobs:
load-config:
uses: <owner>/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: .gitea/workflows/<komponentti>.gitea-env.conf
check-version:
needs: [load-config]
uses: <owner>/gitea-ci-library/.gitea/workflows/check-version.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
<testit>:
needs: [load-config, check-version]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: ./.gitea/workflows/<komponentti>.<testi>.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
build-push:
needs: [load-config, check-version, <testit>]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: <owner>/gitea-ci-library/.gitea/workflows/docker-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
report-summary:
name: Report Summary
needs: [load-config, build-push]
if: always()
uses: <owner>/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: '<suite-1> <suite-2>'
```
**Commit status -kontekstit monorepossa:** Testiraporttien `ci-report.sh`-kutsussa
context ja description sisältävät komponentin nimen:
```yaml
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: Unit test report" <komponentti>.unit-tests bats ${{ job.status }}
```
### Version elinkaari per komponentti
`GIT_TAG_PREFIX` takaa että eri komponenttien versiohistoria pysyy erillään.
Git-tägi `<komponentti>/0.2.3` ei sekoitu toisen komponentin tägeihin.
`check-version.yml` suodattaa ja laskee seuraavan patchin vain kyseisen
komponentin etuliitteellä. Idempotenttius toimii komponenttikohtaisesti:
jos commitilla on jo tägi, pipeline skipataan `if: artifact_exists != 'true'`.
### Mitä EI kannata tehdä monorepossa
- Älä aja kaikkia komponentteja samasta triggeristä — `paths:` pitää CI:t erillisinä
- Älä käytä samaa versionhallintatiedostoa usealle komponentille
- Älä anna monorepo-parametreja pipeline-overrideina — kaikki kuuluu conf-tiedostoon
- Älä rajaa `paths:` pelkkään komponentin hakemistoon — CI ei triggeröidy workflow- tai conf-muutoksista
## Versionhallinta
`check-version.yml` lukee version automaattisesti prioriteettijärjestyksessä:
| # | Lähde | Formaatti |
|---|---|---|
| 1 | `VERSION_FILE` confissa | Määritelty polku |
| 2 | `VERSION`-tiedosto (root) | Plain text |
| 3 | `package.json` (root) | `.version`-kenttä |
| 4 | `pom.xml` (root) | `<version>`-elementti |
`major.minor` otetaan tästä. Patch lasketaan automaattisesti git-tageista.
Esim. `VERSION` = `0.2`, tagit = `0.2.0`, `0.2.1` → seuraava `0.2.2`.
## Branch protection (PR-gate)
Gitean Settings → Branches → Add Rule:
- **Branch:** `main`
- **Enable Require Status Checks:** päälle
- **Status checks:** valitse testijobien nimet
## Provider-rajapinnat — referenssi
### Workflowt
| Workflow | Käyttötarkoitus |
|---|---|
| `config-provider.yml` | Lataa + validoi `.conf`, tuottaa `env_json` |
| `check-version.yml` | Tarkistaa onko commit buildattu, laskee version |
| `docker-build-push.yml` | Buildaa + puskea Docker-imagen, tagittaa commitin |
| `helm-build-push.yml` | Paketoi + puskea Helm chartin OCI-rekisteriin, tagittaa commitin |
| `report-summary.yml` | `GITHUB_STEP_SUMMARY`-taulukko raporttilinkeillä (Gitea 1.27+) |
### Skriptit (kutsutaan `.ci/scripts/`-polun kautta)
| Skripti | Käyttötarkoitus |
|---|---|
| `ci-report.sh` | Yhdistetty raportointi: julkaisee git-pagesiin ja asettaa commit-statuksen. Korvaa erilliset `publish-git-pages.sh` + `report-status.sh` -kutsut. Käyttö: `bash .ci/scripts/ci-report.sh "<kuvaus>" <context> <suite> ${{ job.status }}` |
| `report-status.sh` | POSTaa commit-statuksen linkillä (kutsutaan `ci-report.sh`:n sisältä) |
| `publish-git-pages.sh` | Julkaisee raporttihakemiston git-pagesiin (kutsutaan `ci-report.sh`:n sisältä) |
| `ci-validate.sh` | Validoi `.conf`-tiedoston (kutsutaan `config-provider.yml`:stä) |
+580
View File
@@ -0,0 +1,580 @@
---
name: consumer-pipelines
description: |
Creating or modifying consumer CI pipelines, .gitea/workflows/ files,
reusable test workflows, monorepo CI configuration, or CI routing files
(ci-feature.yml, ci-main.yml, ci-*.yml). Activates when the user asks to
build, fix, or change consumer-side Gitea Actions pipelines that use
gitea-ci-library providers.
activation-gate: |
User mentions consumer pipelines, ci-feature.yml, ci-main.yml, test
workflows, .gitea/workflows/ files, monorepo CI, routing files, or asks
to create/modify CI pipelines on top of gitea-ci-library.
category: ci
impact: high
---
# Consumer Pipelines — Pipeline Standards
Säännöt joilla consumer-projektit rakentavat CI-pipelinejä `gitea-ci-library`-kirjaston päälle.
Nämä eivät ole provider-kirjaston sääntöjä — ne kuvaavat miten consumerin kuuluu käyttää kirjastoa oikein.
Katso tarkat mallipohjat ja esimerkit `REFERENCE.md`:stä.
## 1. Reitittimen puhtaus
Reitittimet (`ci-feature.yml`, `ci-main.yml`) eivät sisällä `run:`-steppejä. Ne koostuvat vain:
```yaml
uses:
needs:
if:
secrets: inherit
with:
env_json:
<parametrit>:
```
Jokainen job vastaa yhtä loogista testiä tai operaatiota. Reititin on orkestraattori — kaikki suorittava
logiikka on omassa `workflow_call`-tiedostossaan.
Katso täydellinen esimerkki `REFERENCE.md`:stä.
## 2. Yksi asia per tiedosto
Ei monoliittista `ci-tests.yml`. Jokainen testityyppi tai operaatio on oma `workflow_call`-tiedostonsa.
**Miksi:**
- Testit ajetaan rinnakkain (ei keinotekoisia riippuvuuksia)
- Yhden testin fail ei estä muita
- Testattavissa itsenäisesti `workflow_dispatch`:llä
- Diff näyttää heti mitä testiä muutettiin
## 3. Exit-koodin käsittely
`set -e` on oletuksena käytössä Gitea Actions -stepeissä — ensimmäinen feilaava komento pysäyttää stepin
ja exit-koodi välittyy natiivisti. Ylimääräistä `EXIT=$?` + `echo >> GITHUB_ENV` -käärettä ei tarvita.
```yaml
- name: Run tests
shell: bash
run: |
<testikomento> > results.txt 2>&1
```
**Miksi ei pipeä (`| tee`):** `|` syö exit-koodin. Käytä redirectiä `>`.
**Yksi asia per step:** Älä koskaan niputa useaa komentoa samaan `run:`-blockiin. `bash -e` pysäyttää
koko stepin ensimmäisellä failaavalla komennolla, ja loput jäävät ajamatta. Sama pätee post-process-steppeihin.
## 4. Konttipolitiikka
1. **Julkiset registry-kontit kiinteällä versiolla**`alpine/helm:3.19.0`, `node:22`, `maven:3.9-eclipse-temurin-21`.
Toistettavuus ja turvallisuus eivät saa riippua ulkoisesta `latest`:sta.
2. **Projektin omat CI-kontit `latest`-tägillä** — buildattu `ci-container-build-<kontti>.yml`:llä.
Kontin build-pipeline päivittää `latest`:n automaattisesti. Rebuild = käyttöönotto
kaikissa pipelineissa ilman versioviittauksien päivittelyä.
3. **Ei koskaan `curl`-latauksia CI-ajon sisällä** — työkalujen asennus CI-stepeissä hidastaa,
epäluotettavaa, ja vaikeuttaa toistettavuutta.
4. **Konttikuva hallitaan workflow'ssa, ei kutsujassa** — jos workflow vaatii tietyn
konttikuvan, se määritellään oletuksena (`default:`) workflow'n inputissa.
Kutsujan ei tarvitse tietää eikä välittää image-nimeä ellei halua ylikirjoittaa.
CI-kontin build-workflow'n template: `skills/ci-container-build/SKILL.md`.
### 4.1 Offline Container -vaatimus (DoD)
CI-kontin (ja kaikkien pipeline-konttien) on oltava täysin itseriittoisia:
> Kontti ei lataa mitään pipeline-vaiheessa (`workflow run` -stepit) eikä
> kontin runtime-prosessissa (`container:` / `docker run`). Kaikki
> riippuvuudet pre-cachataan `docker build` -vaiheessa.
> Ainoa sallittu lataushetki on `docker build`.
**Esimerkkejä rikkomuksista:**
- `apk add`, `apt-get install`, `npm install`, `go mod download`, `pip install`
pipeline-stepissä
- `curl <url> | tar xz` runtime-vaiheessa
- Node.js-konttikuva ilman nodea (joudutaan asentamaan lennossa)
### 4.2 Kielikohtainen pre-cache
Kun kontissa testataan kielikohtaista koodia, kaikki riippuvuudet on
pre-cachattava Dockerfilessä, ei pipeline-stepissä:
| Kieli | Pre-cache Dockerfilessä |
|---|---|
| Go | `COPY go.mod go.sum ./``RUN go mod download` |
| Java/Maven | `COPY pom.xml ./``RUN mvn dependency:go-offline` |
| Node | `COPY package.json package-lock.json ./``RUN npm ci --omit=dev` |
| Python | `COPY requirements.txt ./``RUN pip install -r requirements.txt` |
Katso tarkat Dockerfile-esimerkit `REFERENCE.md`:stä.
### 4.3 CI-kontin ajaminen jobissa
Ainoa sallittu tapa on `container:`-direktiivi. `docker run` komennolla kontin
käynnistäminen stepin sisällä on anti-pattern.
Katso CI-kontin template `REFERENCE.md`:stä.
**Huomio `actions/checkout@v4`:stä:** `container:`-direktiivillä kaikki stepit
ajetaan kontin *sisällä* — myös `actions/checkout@v4`. Se on JavaScript-action
joka vaatii sekä `nodejs` että `git`. Varmista että CI-kontin Dockerfilessä on
molemmat — muuten checkout ei toimi ja pipeline failaa.
### 4.4 Build-konteksti, `.dockerignore` ja `COPY`
**Build-konteksti** on aina tiedoston (Dockerfile, Chart.yaml) oman hakemiston
juuri (`dirname "${DOCKERFILE}"` / `dirname "${CHART_FILE}"`). Kaikki
suhteelliset polut — ignore-tiedosto, `COPY`, `ADD` — ovat suhteessa tähän
kontekstiin.
| Tiedosto | Konteksti | Ignore-tiedosto | Käyttö |
|---|---|---|---|
| `Dockerfile` | `.` | `./.dockerignore` | `docker build` / `COPY src/ src/` |
| `api/Dockerfile` | `api/` | `api/.dockerignore` | `docker build` / `COPY src/ src/` |
| `Chart.yaml` (`VERSION_FILE`) | `.` | `./.helmignore` | `helm package` |
| `api/Chart.yaml` (`VERSION_FILE`) | `api/` | `api/.helmignore` | `helm package` |
Helm chartin polku luetaan confin `VERSION_FILE`-kentästä — sama rivi jota
`check-version.yml` käyttää version lähteenä. Yksi conf-rivi ohjaa molempia:
sekä versionlaskentaa että chartin sijaintia.
**Mitä ignore-tiedosto sisältää:** Kaikki mikä EI ole konttiin tai chart-pakettiin
tarkoitettua koodia tai resurssia, ON oltava ignore-tiedostossa:
- Git- ja CI-historia (`.git/`, `.gitea/`, `.github/`)
- Testikoodi, testidata, testiraportit (`tests/`, `reports/`, `coverage/`)
- Dokumentaatio (`docs/`, `guides/`, `*.md`, `CHANGELOG`, `README`)
- Editori- ja työkalukonfiguraatio (`.vscode/`, `.cursor/`, `.idea/`, `.DS_Store`)
- Riippuvuudet jotka asennetaan Dockerfilessä (`node_modules/`)
- Väliaikaistiedostot (`tmp/`, `*.log`)
- Projektikohtaiset konfiguraatiot (`.env`, `*.conf`, `CURRENT_PROVIDER_VERSION`)
**Miksi:** Build-kontekstin koko vaikuttaa suoraan `docker build` -nopeuteen.
Raskas konteksti (etenkin `.git/` ja `node_modules/`) hidastaa buildia ja
kuluttaa runnerin resursseja turhaan. Ylimääräiset tiedostot kontissa ovat
**tietoturvariski** — tokenit, `.env` ja sensitiivinen data voivat päätyä
kontin layeriin jos `.dockerignore` ei ole kattava.
### 4.5 `COPY`-kuri — kopioi vain tarvittava
`COPY . .` on kielletty. Jokainen `COPY` kopioi vain tarvittavat tiedostot
tai hakemistot:
```dockerfile
# VÄÄRIN
COPY . .
# OIKEIN
COPY package.json package-lock.json ./
COPY src/ src/
COPY public/ public/
```
**Miksi:**
- Layer-cache: `COPY . .` rikkoo välimuistin — mikä tahansa muutos
tiedostossa tyhjentää koko layerin
- Tietoturva: konttiin voi päätyä ylimääräisiä tiedostoja vaikka
`.dockerignore` olisi kattava (unohtunut ignore-rivi, uusi työkalu
joka luo tiedostoja build-kontekstiin)
- Luettavuus: `COPY . .` ei kerro mitä kontti todella sisältää
- Kontin koko: eksplisiittinen `COPY` pitää image-koon kurissa
### 4.6 `.helmignore` — pidä chart-paketti siistinä
`helm package` käyttää `.helmignore`-tiedostoa samalla periaatteella kuin
`docker build` käyttää `.dockerignore`a:
- Chart-hakemisto luetaan confin `VERSION_FILE`-kentästä (`dirname "${VERSION_FILE}"`)
- ignore-tiedosto luetaan chart-hakemiston juuresta (sama konteksti kuin
`Chart.yaml`, ks. 4.4)
- Kaikki turha (testit, docs, git, CI-konffit, kuvat) on poissuljettava
- Jos `.helmignore` puuttuu, `helm package` paketoi mukaan kaikki
chart-hakemiston tiedostot — turhaa bulkkia registryyn
**`.helmignore` on pakollinen** jokaiselle chartille. Minimisisältö:
```
.git/
.gitignore
tests/
docs/
*.md
.DS_Store
```
## 5. Raporttitasot
Testi tuottaa raportin `reports/<suite>/`-hakemistoon. Yksi `ci-report.sh`-kutsu hoitaa sekä
julkaisun että commit-statuksen.
### Taso 1: Ei jälkikäsittelyä
Kun testi tuottaa raportit suoraan (kuten `pytest --html` tai `cucumber-js --format html`):
- testi kirjoittaa `reports/<suite>/`-hakemistoon
- `ci-report.sh` julkaisee ja asettaa commit-statuksen
### Taso 2: Jälkikäsittely tarvitaan
Kun testi tuottaa raakadataa (stdout, coverage-tiedostot) joka pitää muuntaa tai siirtää
`reports/<suite>/`-hakemistoon. **Jokainen operaatio omassa stepissään** `if: always()`.
Tarkat YAML-mallit molemmista tasoista: `REFERENCE.md`.
**Subdir-sääntö:** Alihakemisto näkyy indexissä VAIN jos se sisältää `index.html`:n.
## 6. Nimeäminen
Tiedostonimet `.gitea/workflows/`-kansiossa noudattavat yhtenäistä rakennetta:
```
<komponentti>.ci-feature.yml ← feature-haaran reititin
<komponentti>.ci-main.yml ← main-haaran reititin
<komponentti>.<testityyppi>.yml ← yksittäinen testi tai operaatio
<komponentti>.ci-container-build-<kontti>.yml ← CI-kontin build-workflow
<komponentti>.gitea-env.conf ← komponenttikohtainen konfiguraatio
```
Single repossa `<komponentti>` jätetään pois.
Monorepossa prefiksi pitää komponentin tiedostot yhdessä.
### 6.1 Commit status -nimeäminen
`ci-report.sh`-kutsun `description` (2. argumentti) ja `context` (3. argumentti)
noudattavat seuraavaa kaavaa:
**Single repo:**
```
context: <testityyppi> (esim. unit-tests, acc-tests)
description: <Test type> test report (esim. Unit test report)
```
**Monorepo:**
```
context: <komponentti>.<testityyppi> (esim. library.unit-tests)
description: <Komponentti>: <Test type> test report (esim. Library: Unit test report)
```
> Gitea YAML: `run:` laita lainausmerkeillä `run: |`-blockiin — Gitea ei tue lainausmerkkejä yhden rivin `run:`-komennoissa.
>
> ```yaml
> - name: Report
> if: always()
> run: |
> bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
> ```
Build/push-status (Docker, Helm) on providerin hallussa — consumer ei vaikuta
niiden nimeämiseen.
## 7. Artifact-kuri
Gitea Actionsin `upload-artifact` jättää pysyvän tiedoston. Artifakteja ei käytetä
`workflow_call`:ien väliseen datan siirtoon ellei se ole teknisesti välttämätöntä.
**Ensisijainen ratkaisu:** jokainen testi tuottaa tarvitsemansa datan itse. Ei
`upload-artifact` + `download-artifact` -riippuvuuksia.
## 8. Report-Summary — pakollinen jokaisen pipelinen lopuksi
Jokaisen reitittimen (oli se `ci-main.yml`, `ci-feature.yml` tai mikä tahansa) viimeinen job on `report-summary`.
**Säännöt:**
- `needs:` sisältää **kaikki edeltävät jobit** — summary odottaa että kaikki on valmis (onnistui tai ei)
- `if: always()` — ajetaan aina, vaikka pipeline olisi keskeytetty tai joku jobi failannut
- `suites:` on välilyönnein eroteltu lista suiten nimistä (esim. `bats cucumber`). Tyhjä merkkijono sallittu jos testisuiteja ei ole.
- Provider (`report-summary.yml`) hoitaa summaryn logiikan — reititin vain kutsuu
**Miksi aina:**
- Gitea 1.27+ näyttää `GITHUB_STEP_SUMMARY`:n Actions UI:ssa. Ilman summarya pipeline näyttää epätäydelliseltä.
- Summaryyn voidaan myöhemmin lisätä muutakin kuin testilinkkejä (build-artefaktit, deploy-tiedot).
- Yhtenäinen rakenne jokaisessa pipeline-parissa vähentää kysymyksiä.
YAML-malli: `REFERENCE.md`.
## 9. ADR-yhteenveto — consumerin kannalta oleelliset säännöt
### Reititin ei sisällä suorittavaa koodia (ADR 0010)
`ci-feature.yml` ja `ci-main.yml` koostuvat **vain** `uses:`, `needs:` ja `if:`-avainsanoista.
Ei `run:`-komentoja, ei inline-skriptejä, ei `actions/checkout`.
### Yksi steppi = yksi workflow_call-tiedosto
Jokainen job reitittimessä on oma `workflow_call`-tiedostonsa.
Ei kahta eri komentoa samassa workflow'ssa.
### Provider-versio on `@v1` (ADR 0009)
Kaikki provider-viittaukset käyttävät `@v1`-tagia. `@main` on vain providerin oman repon
sisäiseen dogfood-käyttöön. Breaking changet kielletty — `v1`-rajapinta on pysyvä.
### Paikalliset `uses:` eivät käytä refiä
Gitea act runner v1.0.8 muodostaa paikallisista `uses: ./.gitea/workflows/*.yml@main`-viittauksista
epävalidin git-refin `main@<sha>`.
Paikallisista `uses:`-direktiiveistä EI koskaan käytetä `@main`- tai muuta ref-päätettä:
- `uses: ./.gitea/workflows/chart.helm-lint.yml` ← oikein
- `uses: ./.gitea/workflows/chart.helm-lint.yml@main` ← väärin
Ilman refiä runner käyttää workflow'ta triggeröivästä commitista.
### Exit-koodi on ainoa onnistumisen mittari (ADR 0008)
Ei pipeä (`|`) komennon perässä — se syö exit-koodin. Käytä redirectiä (`> file 2>&1`).
### Providerin checkout ei kuulu consumerille
Providerin scriptit haetaan `actions/checkout`-stepillä `.ci/`-polkuun.
Consumer ei kopioi eikä muokkaa providerin tiedostoja.
## 10. Build & Push -providerit
### `docker-build-push.yml` — Docker image build & push
Buildaa ja pushee Docker-imagen OCI-registryyn. Ajaa suoraan runnerilla
(ei `container:`-direktiiviä), joten `actions/checkout` toimii natiivisti.
**`env_json`-avaimet (pakolliset):**
```yaml
DOCKER_REGISTRY: gitea.app.keskikuja.site/niko
DOCKER_IMAGE_NAME: my-app
```
**Inputit:**
| Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
| `version` | Kyllä | Version string (check-version output) |
| `build_args` | Ei | Monirivinen KEY=VALUE-lista välitettynä `--build-arg`-lipuiksi `docker build` -komennolle |
**Käyttö reitittimessä:**
```yaml
docker-build-push:
uses: OWNER/gitea-ci-library/.gitea/workflows/docker-build-push.yml@v1
needs: [check-version]
if: needs.check-version.outputs.artifact_exists == 'false'
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
build_args: |
VERSION=${{ needs.check-version.outputs.version }}
```
**Dockerfilessä** ARG otetaan vastaan oletusarvolla ja kirjoitetaan tiedostoksi:
```dockerfile
ARG VERSION=unknown
RUN mkdir -p /app && echo "$VERSION" > /app/version.txt
```
Katso referenssi: repon juuren `Dockerfile` (`gitea-ci-library/Dockerfile`).
Tarkka input/secret-lista: `docs/workflows.md`.
### `helm-build-push.yml` — Helm chart build & push
Pakkaa ja pushee Helm-chartin OCI-registryyn. Käyttää `alpine/helm`-konttia.
**`env_json`-avaimet (pakolliset):**
```yaml
HELM_REGISTRY: gitea.app.keskikuja.site/niko
VERSION_FILE: platform-helm/Chart.yaml # versionlähde, chart_path määrää chart-hakemiston
```
**Inputit:**
| Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
| `version` | Kyllä | Version string (check-version output) |
| `chart_path` | Kyllä | Polku Chart.yaml-hakemistoon (esim. `platform-helm`) |
| `extra_dependency_paths` | Ei | Pilkulla erotellut polut subcharttien dependeinceille |
**Käyttö reitittimessä:**
```yaml
helm-build-push:
uses: OWNER/gitea-ci-library/.gitea/workflows/helm-build-push.yml@v1
needs: [check-version]
if: needs.check-version.outputs.artifact_exists == 'false'
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
chart_path: platform-helm
# extra_dependency_paths: subchart-a,subchart-b # tarvittaessa
```
`chart_path` on eksplisiittinen polku chart-hakemistoon (esim. `platform-helm`).
`VERSION_FILE` määrää version lähteen (`Chart.yaml:n` `version`-kenttä) —
nämä voivat olla eri polkuja, mutta tyypillisesti molemmat osoittavat samaan
chart-hakemistoon.
**`extra_dependency_paths`:** Jos chartilla on alikarttoja (subchartteja) jotka
vaativat `helm dependency update` -ajon ennen päächartin buildia, anna niiden
polut pilkulla eroteltuna. Provider ajaa `helm dependency update` jokaiselle
polulle ennen päächartin buildia.
**Yksittäisten Helm-UI-linkkien raportointi:** `HELM_UI_URL` on
tarkoitettu yleiselle registry UI:lle — provider muodostaa linkin
`${HELM_UI_URL}/${CHART_NAME}/${VERSION}` automaattisesti.
Tarkka input/secret-lista: `docs/workflows.md`.
## 11. Multi-artifact monorepo -komponentti
Yksi monorepo-komponentti voi tuottaa useita artefakteja (esim. Docker image
+ Helm chart). Kukin artefakti on **omassa reitittimessään** — ei yhtä
monoliittista pipelinea. Tämä on tietoinen arkkitehtuurivalinta:
- Reitittimet ovat itsenäisiä: eri `paths:`-triggerit, eri tagit, eri confit
- Yksi commit voi triggeröidä molemmat rinnakkain
- Yhden artefaktin build tai testi ei estä toista
### Esimerkki: `platform-helm` joka tuottaa Docker-imagen ja Helm chartin
```
.gitea/workflows/
├── platform-helm.ci-main.yml # Docker build & push
├── platform-helm.gitea-env.conf # Docker-konffi
├── platform-helm.helm-ci-main.yml # Helm build & push
├── platform-helm.helm-gitea-env.conf # Helm-konffi
├── platform-helm.helm-chart-lint.yml # Chart-testi
└── platform-helm.ci-container-build-helm.yml # CI-kontin build
```
### `platform-helm.gitea-env.conf` (Docker)
```ini
DOCKER_REGISTRY=gitea.app.keskikuja.site/niko
DOCKER_IMAGE_NAME=platform-helm
GIT_TAG_PREFIX=platform-helm/
```
### `platform-helm.helm-gitea-env.conf` (Helm)
```ini
HELM_REGISTRY=gitea.app.keskikuja.site/niko
VERSION_FILE=platform-helm/Chart.yaml
GIT_TAG_PREFIX=chart/
```
### Reitittimet
**`platform-helm.ci-main.yml`** — Docker-buildi, testit, oma tagi:
```yaml
name: platform-helm CI Main
on:
push:
branches: [main]
paths:
- platform-helm/**
- .gitea/workflows/platform-helm.*
jobs:
load-config:
uses: OWNER/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: .gitea/workflows/platform-helm.gitea-env.conf
check-version:
needs: [load-config]
uses: OWNER/gitea-ci-library/.gitea/workflows/check-version.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
test:
needs: [load-config, check-version]
uses: ./.gitea/workflows/platform-helm.sbom-lint.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
build-push:
needs: [load-config, check-version, test]
if: needs.check-version.outputs.artifact_exists == 'false'
uses: OWNER/gitea-ci-library/.gitea/workflows/docker-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
report-summary:
needs: [load-config, test, build-push]
if: always()
uses: OWNER/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: ''
```
**`platform-helm.helm-ci-main.yml`** — Helm-buildi, chart-testi, oma tagi:
```yaml
name: platform-helm Helm CI Main
on:
push:
branches: [main]
paths:
- platform-helm/**
- .gitea/workflows/platform-helm.helm*
jobs:
load-config:
uses: OWNER/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: .gitea/workflows/platform-helm.helm-gitea-env.conf
check-version:
needs: [load-config]
uses: OWNER/gitea-ci-library/.gitea/workflows/check-version.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
chart-lint:
needs: [load-config, check-version]
uses: ./.gitea/workflows/platform-helm.helm-chart-lint.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
helm-build-push:
needs: [load-config, check-version, chart-lint]
if: needs.check-version.outputs.artifact_exists == 'false'
uses: OWNER/gitea-ci-library/.gitea/workflows/helm-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
report-summary:
needs: [load-config, chart-lint, helm-build-push]
if: always()
uses: OWNER/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: ''
```
### Säännöt
- Jokaisella artefaktilla on oma reititin, oma conf, omat testit
- Conf-tiedoston nimi erottaa artefaktit: `<komponentti>.gitea-env.conf` vs
`<komponentti>.helm-gitea-env.conf`
- `<komponentti>.helm-`-prefiksi erottaa Helm-artefaktin tiedostot
- `GIT_TAG_PREFIX` pitää tagit erillään: `platform-helm/1.2.3` vs `chart/1.2.3`
- Molemmat reitittimet voivat triggeröityä samasta commitista
+251
View File
@@ -0,0 +1,251 @@
---
name: gitops-update
description: |
Getting GitOps configuration updates working for a consumer project —
GitOps repo setup, consumer pipeline wiring, secrets, and commit-status
output.
activation-gate: |
User mentions GitOps update, gitops-update, dispatch to another repo,
two-repo version bump, cross-repo deployment, or wiring build output to
config repo.
category: ci
impact: high
---
# GitOps Update — consumer setup
## What you need
- **GitOps repo** — holds the configuration files (e.g. `Chart.yaml`, `values.yaml`)
- **Consumer repo** — builds artifacts and triggers the update
- **Bottitoken** — Gitea token with write access to the GitOps repo only
Two repos, isolated access. The consumer never writes to GitOps directly;
it dispatches a workflow that clones, updates, commits, and pushes.
---
## 1. GitOps-repo setup
Create `.gitea/workflows/gitops-service.yaml`:
```yaml
name: GitOps Update
run-name: "GitOps (${{ inputs.dispatch_id || 'manual' }})"
on:
workflow_dispatch:
inputs:
file:
required: true
type: string
yq_tpl:
required: true
type: string
version:
required: true
type: string
source_repo:
required: true
type: string
source_commit:
required: true
type: string
dispatch_id:
required: false
type: string
git_tag_prefix:
required: false
type: string
extra_cmd:
required: false
type: string
author_name:
required: false
type: string
author_email:
required: false
type: string
env:
INPUT_FILE: ${{ inputs.file }}
YQ_TPL: ${{ inputs.yq_tpl }}
VERSION: ${{ inputs.version }}
SOURCE_REPO: ${{ inputs.source_repo }}
SOURCE_COMMIT: ${{ inputs.source_commit }}
GITOPS_REPO: ${{ github.repository }}
GITEA_API_URL: ${{ gitea.server_url }}
GIT_TAG_PREFIX: ${{ inputs.git_tag_prefix || '' }}
GITOPS_EXTRA_CMD: ${{ inputs.extra_cmd || '' }}
GIT_USER_NAME: ${{ inputs.author_name || '' }}
GIT_USER_EMAIL: ${{ inputs.author_email || '' }}
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Install yq
run: |
wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq
- name: Run GitOps update
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
bash .ci/scripts/gitops-update.sh
```
> **⚠️ yq ladataan lennossa.** Tämä on väliaikainen kompromissi. Myöhemmin
> julkaistaan Docker Hubiin custom CI-kontti, jossa nodejs + git + yq
> valmiina. Sama patterni kuin `ci-bats` ja `ci-cucumber`.
> Ks. `skills/ci-container-build/SKILL.md`.
Key points:
- `run-name` must include `dispatch_id` — the consumer's poll step uses it to find the run
- `secrets.GITEA_TOKEN` is the **auto-token** — write access to the GitOps repo only, no consumer access needed
- Commit message becomes `"[skip ci] gitops: update version to X.Y.Z"` — used by consumer to find the commit SHA
---
## 2. Consumer-repo setup
### 2.1 Token
Create a Gitea token with write access to the GitOps repo:
1. Gitea → `Settings``Applications``Generate Token`
2. Select the GitOps repo, grant write access
3. Save as an Actions secret in the consumer repo: **`GITOPS_DISPATCH_TOKEN`**
### 2.2 Pipeline call
Add a job after your build step that calls the dispatch workflow:
```yaml
gitops-update:
needs: [build-push]
if: success()
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.version.outputs.version }}
GITOPS_FILE: dev/Chart.yaml
GITOPS_YQ_TPL: '.version = "{{VERSION}}"'
GITOPS_REPO: niko/your-gitops-repo
```
This single job handles: dispatch → poll → find commit SHA → set commit-status on your commit → produce `GITOPS_SUMMARY` output.
To run extra commands (e.g. `helm dependency update`) after the version bump and before the commit:
```yaml
gitops-update:
needs: [load-config, check-version, helm-build-push]
if: success()
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
GITOPS_FILE: Chart.yaml
GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "agent-platform-helm") | .version) = "{{VERSION}}"'
GITOPS_REPO: niko/agent-platform-gitops
GITOPS_EXTRA_CMD: helm dependency update
```
When `GITOPS_EXTRA_CMD` is set, the script runs it after `yq` and stages all changes (`git add -A`) instead of only the input file — so any files generated by the extra command (e.g. `Chart.lock`, `charts/`) are included in the commit.
By default the GitOps commit is made as `gitea-ci-bot`. To use the original commit author instead, the dispatch workflow resolves it automatically from the consumer repo — no extra config needed. Just ensure the GitOps repo's `gitops-service.yaml` template has the `author_name` and `author_email` inputs and env mappings.
### 2.3 Parameters
| Input | Required | Description |
|---|---|---|---|
| `env_json` | Yes | Config JSON with `GITEA_API_URL`, optional `GIT_TAG_PREFIX` (for multi-component repos) |
| `version` | Yes | Version to write (e.g. `0.2.3`) |
| `GITOPS_FILE` | Yes | Path in GitOps repo (e.g. `dev/Chart.yaml`) |
| `GITOPS_YQ_TPL` | Yes | yq expression, `{{VERSION}}` is replaced at runtime |
| `GITOPS_REPO` | Yes | GitOps repo slug (e.g. `niko/agent-platform-gitops`) |
| `GITOPS_EXTRA_CMD` | No | Shell command to run after yq update, before git commit (e.g. `helm dependency update`) |
### 2.4 Output
The workflow produces a `summary` output in pipe format:
```
component|version|status|commit_sha|repo
agent-platform-helm|0.2.3|success|abc789def|niko/agent-platform-gitops
```
Pass it to `report-summary.yml` for the pipeline summary:
```yaml
report-summary:
needs: [load-config, gitops-update]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: bats cucumber
gitops: ${{ needs.gitops-update.outputs.summary }}
```
---
## 3. Token summary
| Token | Where | Scope | Purpose |
|---|---|---|---|
| `GITOPS_DISPATCH_TOKEN` (manual) | Consumer secrets | write GitOps repo | Dispatches the GitOps workflow |
| `GITHUB_TOKEN` (auto) | Consumer workflow | write consumer repo | Sets commit-status on consumer's commit |
| `GITEA_TOKEN` (auto) | GitOps workflow | write GitOps repo | Clone, push, commit-status in GitOps repo |
---
## 4. What happens at runtime
1. Consumer's `gitops-dispatch.yml` generates a unique `dispatch_id` and POSTs it to the GitOps repo
2. GitOps workflow clones its own repo, applies `yq`, runs `GITOPS_EXTRA_CMD` if set, then commits + pushes
3. Consumer polls the GitOps repo's runs until the workflow completes
4. Consumer lists recent commits and finds the matching one by commit message `"gitops: update version to X.Y.Z"`
5. Consumer sets commit-status `gitops/{repo}[/{prefix}]` on its own commit with a link to the exact GitOps commit
6. If no matching commit is found (no change or error), the job fails
7. On failure, `GITOPS_SUMMARY` still flows through `report-summary` with `status=failure`
---
## 5. GIT_TAG_PREFIX (optional)
If the same consumer repo dispatches updates for multiple components (e.g. Docker image + Helm chart), set `GIT_TAG_PREFIX` in your `gitea-env.conf`:
```
GIT_TAG_PREFIX=docker/
```
Each component gets its own commit-status context:
| Prefix | Context |
|---|---|
| (empty) | `gitops/agent-platform` |
| `docker/` | `gitops/agent-platform/docker` |
| `helm/` | `gitops/agent-platform/helm` |
This prevents status overwrites between parallel dispatch jobs.
---
## 6. What you do NOT need to know
- How `gitops-update.sh` works internally
- How the polling finds the run
- How the commit SHA is extracted
- Race condition handling
- CI container plans
All of that is handled by `gitops-dispatch.yml`. You just call it.
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env bats
source "$BATS_TEST_DIRNAME/helpers/mock-api.sh"
setup() {
export GITEA_TOKEN=test-token
export GIT_TAG_PREFIX=""
export SERVER_URL="http://localhost:18080"
export REPO="niko/test"
export SHA="abc123"
rm -rf /tmp/build-ctx
}
teardown() {
mock_stop 2>/dev/null || true
rm -rf /tmp/build-ctx
}
@test "VERSION_FILE=Chart.yaml extracts version from YAML" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/Chart.yaml"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "VERSION_FILE=VERSION extracts version from plain text" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "VERSION_FILE=package.json extracts version from JSON" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/package.json"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "VERSION_FILE=subdir/Chart.yaml extracts version from monorepo" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/subdir/Chart.yaml"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.4.0" ]
}
@test "no VERSION_FILE, root VERSION found" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
WORKDIR=$(mktemp -d)
cp "$BATS_TEST_DIRNAME/fixtures/check-version/VERSION" "$WORKDIR/VERSION"
SCRIPT="$PWD/scripts/check-version.sh"
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
rm -rf "$WORKDIR"
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "no VERSION_FILE, root Chart.yaml found" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
WORKDIR=$(mktemp -d)
cp "$BATS_TEST_DIRNAME/fixtures/check-version/Chart.yaml" "$WORKDIR/Chart.yaml"
SCRIPT="$PWD/scripts/check-version.sh"
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
rm -rf "$WORKDIR"
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "tag exists for commit sets ARTIFACT_EXISTS=true" {
mock_set_sequence '[{"code": 200, "body": [{"name": "0.3.0", "commit": {"sha": "abc123"}}]}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "true" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "tag with prefix filters correctly" {
mock_set_sequence '[{"code": 200, "body": [{"name": "git-pages/0.3.0", "commit": {"sha": "abc123"}}, {"name": "docker/0.3.0", "commit": {"sha": "abc123"}}]}]'
mock_start
export GIT_TAG_PREFIX="git-pages/"
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "true" ]
[ "$NEXT_VERSION" = "git-pages/0.3.0" ]
}
@test "no tag, new version calculated" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "highest patch calculated correctly" {
mock_set_sequence '[{"code": 200, "body": [{"name": "0.3.0", "commit": {"sha": "def456"}}, {"name": "0.3.1", "commit": {"sha": "def456"}}]}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.2" ]
}
@test "VERSION_FILE=Chart-umbrella.yaml extracts only top-level version" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/Chart-umbrella.yaml"
run bash scripts/check-version.sh
echo "STATUS=$status"
echo "OUTPUT=$output"
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
echo "NEXT_VERSION=$NEXT_VERSION"
[ "$NEXT_VERSION" = "0.1.0" ]
}
@test "no version source exits with error" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
WORKDIR=$(mktemp -d)
SCRIPT="$PWD/scripts/check-version.sh"
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
rm -rf "$WORKDIR"
[ "$status" -eq 1 ]
[[ "$output" == *"ERROR"* ]]
}
+5 -5
View File
@@ -44,15 +44,15 @@ EOF
[[ "$output" == *"GITEA_TOKEN"* ]]
}
@test "missing GIT_PAGES_PUBLISH_TOKEN secret → exit 1" {
@test "missing REPORTS_PUBLISH_TOKEN secret → exit 1" {
cat > "$CONF_FILE" <<EOF
SOME_KEY=ok
EOF
export GITEA_TOKEN="sometoken"
unset GIT_PAGES_PUBLISH_TOKEN
unset REPORTS_PUBLISH_TOKEN
run bash scripts/ci-validate.sh
[ "$status" -eq 1 ]
[[ "$output" == *"GIT_PAGES_PUBLISH_TOKEN"* ]]
[[ "$output" == *"REPORTS_PUBLISH_TOKEN"* ]]
}
@test "valid config and all secrets → exit 0" {
@@ -61,7 +61,7 @@ API_URL=https://example.com
ANOTHER=https://test.fi
EOF
export GITEA_TOKEN="sometoken"
export GIT_PAGES_PUBLISH_TOKEN="sometoken"
export REPORTS_PUBLISH_TOKEN="sometoken"
run bash scripts/ci-validate.sh
[ "$status" -eq 0 ]
}
@@ -73,7 +73,7 @@ EOF
VALID_URL=https://example.com
EOF
export GITEA_TOKEN="sometoken"
export GIT_PAGES_PUBLISH_TOKEN="sometoken"
export REPORTS_PUBLISH_TOKEN="sometoken"
run bash scripts/ci-validate.sh
[ "$status" -eq 0 ]
}
+20 -19
View File
@@ -3,6 +3,7 @@
setup() {
source tests/helpers/mock-api.sh
export DISPATCH_POLL_INTERVAL="0.1"
export DISPATCH_ID="test123"
}
teardown() {
@@ -12,8 +13,7 @@ teardown() {
@test "dispatch succeeds: POST 201, poll running x3 then success → exit 0" {
mock_set_sequence '[
{"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}}
@@ -26,7 +26,7 @@ teardown() {
@test "dispatch: poll returns failure conclusion → exit 1" {
mock_set_sequence '[
{"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"completed","conclusion":"failure"}}
]'
@@ -38,7 +38,7 @@ teardown() {
@test "dispatch: poll returns cancelled conclusion → exit 1" {
mock_set_sequence '[
{"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"completed","conclusion":"cancelled"}}
]'
@@ -47,18 +47,18 @@ teardown() {
[ "$status" -eq 1 ]
}
@test "timeout: poll never completes, exceeds timeout_minutes → exit 124" {
@test "timeout: no matching run found, exceeds timeout_minutes → exit 124" {
mock_set_sequence '[
{"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}}
{"code":200,"body":{"workflow_runs":[]}},
{"code":200,"body":{"workflow_runs":[]}},
{"code":200,"body":{"workflow_runs":[]}},
{"code":200,"body":{"workflow_runs":[]}},
{"code":200,"body":{"workflow_runs":[]}},
{"code":200,"body":{"workflow_runs":[]}},
{"code":200,"body":{"workflow_runs":[]}},
{"code":200,"body":{"workflow_runs":[]}},
{"code":200,"body":{"workflow_runs":[]}}
]'
mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123" "0.001"
@@ -77,7 +77,7 @@ teardown() {
@test "POST dispatch is called with correct URL and payload" {
mock_set_sequence '[
{"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}}
]'
mock_start
@@ -91,6 +91,7 @@ teardown() {
[[ "$body" == *'"ref":"main"'* ]]
[[ "$body" == *'"inputs"'* ]]
[[ "$body" == *'"version":"1.2.3"'* ]]
[[ "$body" == *'"dispatch_id":"test123"'* ]]
}
@test "missing gitea_api_url argument → exit 1 with error message" {
@@ -120,15 +121,15 @@ teardown() {
[ "$status" -eq 1 ]
}
@test "dispatch: no workflow run found after dispatch → exit 1" {
@test "dispatch: no workflow run found after dispatch → exit 124 (timeout)" {
mock_set_sequence '[
{"code":201},
{"code":200,"body":{"workflow_runs":[]}}
]'
mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{}' "http://localhost:18080" "test-token-abc123"
[ "$status" -eq 1 ]
[[ "$output" == *"ERROR"* ]]
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{}' "http://localhost:18080" "test-token-abc123" "0.001"
[ "$status" -eq 124 ]
[[ "$output" == *"ERROR"* || "$output" == *"Timeout"* ]]
}
@test "missing inputs_json argument → exit 1" {
+42
View File
@@ -0,0 +1,42 @@
Feature: GitOps update
As a GitOps repository
I want to update version references and report results to the caller
So that the deployment chain is traceable from source to GitOps commit
Background:
Given a project repository exists in Gitea
And a commit has been pushed to the repository
@mock
Scenario: Not enough env vars — script fails, no status set
Given insufficient environment variables are provided for the GitOps update
When the GitOps update script runs
Then the script exits with error
@mock
Scenario: GitOps job fails — no status set (SHA not yet known)
Given the GitOps repository clone will fail
When the GitOps update script runs
Then the script exits with error
@mock
Scenario: Everything succeeds — GitOps repo gets success status with link to caller
Given a valid GitOps update dispatch
When the GitOps update script runs
Then the script exits successfully
And the GitOps repo commit shows a success status with a link to the caller commit
@mock
Scenario: GitOps push fails — GitOps repo gets failure status
Given the GitOps repo push will fail after the version is committed
When the GitOps update script runs
Then the script exits with error
And the GitOps repo commit shows a failure status linking to the caller commit
@mock
Scenario: No changes — GitOps repo gets "no change" status
Given the version file already has the target version
When the GitOps update script runs
Then the script exits successfully
And the GitOps repo commit shows a "no change" status
And no Git commit or push was performed
@@ -27,7 +27,7 @@ function bashQuiet(cmd) {
}
function runReportStatus(args) {
return bash(`export GITEA_API_URL="http://localhost:18080" GITEA_TOKEN="test-token-abc123" GIT_PAGES_URL="https://reports.example.com" GITHUB_REPOSITORY="test-owner/test-repo" GITHUB_SHA="abc123def456789012345678901234567890abcd" GITHUB_RUN_ID="42"; bash "${REPORT_SCRIPT}" ${args}`);
return bash(`export GITEA_API_URL="http://localhost:18080" GITEA_TOKEN="test-token-abc123" GIT_PAGES_URL="https://reports.example.com" GITHUB_REPOSITORY="test-owner/test-repo" GITHUB_REF_NAME="main" GITHUB_SHA="abc123def456789012345678901234567890abcd" GITHUB_RUN_ID="42"; bash "${REPORT_SCRIPT}" ${args}`);
}
function getMockBody() {
@@ -57,7 +57,7 @@ When('a build step completes successfully and reports its results', function ()
Then('the commit shows a success status with a clickable link to the results', function () {
const body = getMockBody();
if (!body.includes('"state":"success"')) throw new Error('Expected success status');
if (!body.includes('"target_url":"https://reports.example.com/test-owner/test-repo/reports/abc123de/cucumber/"')) throw new Error('Expected URL');
if (!body.includes('"target_url":"https://reports.example.com/test-owner/test-repo/main/abc123de/cucumber/"')) throw new Error('Expected URL');
});
When('a build step fails', function () {
@@ -6,7 +6,7 @@ const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh');
Before({ tags: '@mock' }, function () {
const out = execSync(`bash -c 'source "${MOCK_SCRIPT}" && mock_start && sleep 0.3 && curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health/check'`, {
const out = execSync(`bash -c 'source "${MOCK_SCRIPT}" && mock_start && sleep 1 && curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health/check'`, {
cwd: PROJECT_ROOT,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
@@ -0,0 +1,172 @@
const { spawnSync, execSync } = require('child_process');
const { Before, After, Given, When, Then } = require('@cucumber/cucumber');
const path = require('path');
const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh');
const GITOPS_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'gitops-update.sh');
const MOCK_HELPERS = path.join(PROJECT_ROOT, 'tests', 'helpers');
const REQ_FILE = '/tmp/gitops-mock-requests.log';
const BASE_ENV = {
INPUT_FILE: 'dev/Chart.yaml',
YQ_TPL: '(.version) = "{{VERSION}}"',
VERSION: '0.2.3',
SOURCE_REPO: 'niko/app',
SOURCE_COMMIT: 'abc123def456',
GITOPS_REPO: 'niko/app-gitops',
GITEA_API_URL: 'http://localhost:18080',
GITEA_TOKEN: 'test-token',
};
Before({ tags: '@mock' }, function () {
process.env.PATH = `${MOCK_HELPERS}:${process.env.PATH}`;
try { execSync('rm -f /tmp/gitops-mock-requests.log', { stdio: 'ignore' }); } catch (_) {}
// Restart mock with known request file path
const result = spawnSync('bash', ['-c', `
source "${MOCK_SCRIPT}"
mock_stop 2>/dev/null
MOCK_REQUEST_FILE="${REQ_FILE}"
mock_start
sleep 0.5
curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health
`], {
cwd: PROJECT_ROOT, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
});
const code = result.stdout.trim();
if (!code.startsWith('2') && !code.startsWith('4')) {
throw new Error(`GitOps mock restart failed: ${result.stderr.substring(0,200)}`);
}
});
After({ tags: '@mock' }, function () {
spawnSync('bash', ['-c', `source "${MOCK_SCRIPT}" && mock_stop 2>/dev/null`], { stdio: 'ignore' });
try { execSync('rm -f /tmp/gitops-mock-requests.log /tmp/gitops-git-calls.log', { stdio: 'ignore' }); } catch (_) {}
});
function bash(cmd) {
const result = spawnSync('bash', ['-c', cmd], {
cwd: PROJECT_ROOT,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
return { status: result.status, stdout: result.stdout || '', stderr: result.stderr || '' };
}
function getFirstBody() {
return bash(`grep -A1 '^POST ' "${REQ_FILE}" 2>/dev/null | head -2 | tail -1 || echo ""`).stdout.trim();
}
function getFirstPath() {
return bash(`grep '^POST ' "${REQ_FILE}" 2>/dev/null | head -1 | awk '{print $2}' || echo ""`).stdout.trim();
}
function getLastBody() {
return bash(`grep -A1 '^POST ' "${REQ_FILE}" 2>/dev/null | grep -v '^POST ' | tail -1 || echo ""`).stdout.trim();
}
function getLastPath() {
return bash(`grep '^POST ' "${REQ_FILE}" 2>/dev/null | tail -1 | awk '{print $2}' || echo ""`).stdout.trim();
}
function requestCount() {
return parseInt(bash(`grep -c '^POST ' "${REQ_FILE}" 2>/dev/null || echo 0`).stdout.trim(), 10) || 0;
}
function gitCalls() {
const callsFile = process.env.GIT_CALLS_FILE || '/dev/null';
const out = bash(`cat "${callsFile}" 2>/dev/null || echo ""`).stdout;
return out.split('\n').filter(l => l.length > 0);
}
function runScript(envOverrides) {
const env = { ...BASE_ENV, ...envOverrides };
const scriptPath = `/tmp/gitops-run-${Date.now()}.sh`;
const exports = Object.entries(env)
.map(([k, v]) => `export ${k}="${v.replace(/"/g, '\\"')}"`)
.join('\n');
require('fs').writeFileSync(scriptPath, `${exports}\nexport PATH="${MOCK_HELPERS}:$PATH"\nset -euo pipefail\nbash "${GITOPS_SCRIPT}"\nsync\n`, 'utf8');
try {
return bash(`bash "${scriptPath}"`);
} finally {
require('fs').unlinkSync(scriptPath);
}
}
Given('insufficient environment variables are provided for the GitOps update', function () {
this.envOverrides = { INPUT_FILE: '' };
});
Given('the GitOps repository clone will fail', function () {
this.envOverrides = { GIT_MOCK_FAIL: '1' };
});
Given('a valid GitOps update dispatch', function () {
this.envOverrides = {};
});
Given('the GitOps repo push will fail after the version is committed', function () {
this.envOverrides = { GIT_MOCK_FAIL_PUSH: '1' };
});
Given('the version file already has the target version', function () {
this.envOverrides = {
GIT_MOCK_DIFF_NO_CHANGES: '1',
GIT_CALLS_FILE: '/tmp/gitops-git-calls.log',
};
});
When('the GitOps update script runs', function () {
this.result = runScript(this.envOverrides || {});
});
Then('the script exits with error', function () {
if (this.result.status === 0) throw new Error(`Expected non-zero exit, got 0. stderr: ${this.result.stderr.substring(0,200)}`);
});
Then('the script exits successfully', function () {
if (this.result.status !== 0) throw new Error(`Expected exit 0, got ${this.result.status}: ${this.result.stderr.substring(0,200)}`);
});
Then('the GitOps repo commit shows a success status with a link to the caller commit', function () {
const count = requestCount();
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
const body = getFirstBody();
if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`);
if (!body.includes('"context":"app ')) throw new Error(`Expected context "app unknown", body: ${body.substring(0,200)}`);
if (!body.includes('"description":"Install to dev 0.2.3"')) throw new Error(`Expected description, body: ${body.substring(0,200)}`);
if (!body.includes('niko/app/commit/abc123def456')) throw new Error(`Expected link to caller commit, body: ${body.substring(0,200)}`);
const pathStr = getFirstPath();
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
});
Then('the GitOps repo commit shows a failure status linking to the caller commit', function () {
const count = requestCount();
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
const body = getFirstBody();
if (!body.includes('"state":"failure"')) throw new Error(`Expected failure state, body: ${body.substring(0,200)}`);
if (!body.includes('"context":"app ')) throw new Error(`Expected context "app unknown", body: ${body.substring(0,200)}`);
if (!body.includes('"description":"Install to dev 0.2.3"')) throw new Error(`Expected description, body: ${body.substring(0,200)}`);
if (!body.includes('niko/app/commit/abc123def456')) throw new Error(`Expected link to caller commit, body: ${body.substring(0,200)}`);
const pathStr = getFirstPath();
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
});
Then('the GitOps repo commit shows a "no change" status', function () {
const count = requestCount();
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
const body = getFirstBody();
if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`);
if (!body.includes('"description":"Install to dev 0.2.3 \u2014 no change"')) {
throw new Error(`Expected "no change" description, body: ${body.substring(0,200)}`);
}
const pathStr = getFirstPath();
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
});
Then('no Git commit or push was performed', function () {
const calls = gitCalls();
if (calls.some(l => l.includes(' commit ') || l.includes(' push '))) {
throw new Error(`Expected no commit or push, got: ${calls.join(', ')}`);
}
});
@@ -15,7 +15,7 @@ function bash(cmd) {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
return { status: 0, stdout: out };
return { status: 0, stdout: out, stderr: '' };
} catch (e) {
return { status: e.status, stdout: e.stdout || '', stderr: e.stderr || '' };
}
@@ -54,7 +54,7 @@ function setupMock(seqJson) {
}
function runDispatch(args) {
return bash(`export DISPATCH_POLL_INTERVAL="0.1"; bash "${DISPATCH_SCRIPT}" ${args}`);
return bash(`export DISPATCH_ID="test123"; export DISPATCH_POLL_INTERVAL="0.1"; bash "${DISPATCH_SCRIPT}" ${args}`);
}
Given('a deployment has completed in the target environment', function () {
@@ -66,7 +66,7 @@ Given('the test project repository exists with test definitions', function () {
When('a test workflow is dispatched to a test project', function () {
setupMock(JSON.stringify([
{ code: 201 },
{ code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } },
{ code: 200, body: { workflow_runs: [{ id: 1, display_title: 'Workflow (test123)', run_number: 42, status: 'running' }] } },
{ code: 200, body: { id: 1, status: 'completed', conclusion: 'success' } },
]));
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"');
@@ -84,7 +84,7 @@ Then('the pipeline continues only after receiving a success result', function ()
When('a test workflow is dispatched and the tests fail', function () {
setupMock(JSON.stringify([
{ code: 201 },
{ code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } },
{ code: 200, body: { workflow_runs: [{ id: 1, display_title: 'Workflow (test123)', run_number: 42, status: 'running' }] } },
{ code: 200, body: { id: 1, status: 'completed', conclusion: 'failure' } },
]));
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"');
@@ -98,15 +98,19 @@ Then('the calling pipeline reports failure', function () {
When('a test workflow is dispatched but does not finish within the allowed time', function () {
setupMock(JSON.stringify([
{ code: 201 },
{ code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } },
{ code: 200, body: { id: 1, status: 'running' } },
{ code: 200, body: { id: 1, status: 'running' } },
{ code: 200, body: { id: 1, status: 'running' } },
{ code: 200, body: { workflow_runs: [] } },
{ code: 200, body: { workflow_runs: [] } },
{ code: 200, body: { workflow_runs: [] } },
{ code: 200, body: { workflow_runs: [] } },
{ code: 200, body: { workflow_runs: [] } },
]));
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123" "0.001"');
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123" "0.05"');
this.dispatchResult = r.status;
this.dispatchStderr = r.stderr;
});
Then('the calling pipeline reports a timeout error', function () {
if (this.dispatchResult !== 124) throw new Error(`Expected timeout exit 124, got ${this.dispatchResult}`);
if (this.dispatchResult !== 124) {
throw new Error(`Expected timeout exit 124, got ${this.dispatchResult}. stderr: ${(this.dispatchStderr || '').substring(0,300)}`);
}
});

Some files were not shown because too many files have changed in this diff Show More