Compare commits

...

11 Commits

Author SHA1 Message Date
moilanik 038e5405b6 secret ohje päivittynyt
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 17s
CI Feature / Bats tests (push) Failing after 1m23s
CI Feature / Cucumber tests (push) Failing after 1m23s
CI Feature / Report Summary (push) Successful in 3s
2026-06-28 05:54:31 +03:00
moilanik 179f6a1b43 publish -> link -> read testattu "e2e"
port forward, local publish ja luettu tämän repon linkin luomisen mallisella url julkaistu raportti
2026-06-28 05:27:42 +03:00
moilanik 5e2c50d67e v4 2026-06-28 04:40:58 +03:00
moilanik c354afc8d7 v3 2026-06-28 04:27:38 +03:00
moilanik d01866fcfb v2 2026-06-28 04:02:23 +03:00
moilanik 7d83ea42f1 v1 2026-06-28 04:02:17 +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
49 changed files with 1535 additions and 1337 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ on:
jobs: jobs:
build-push: build-push:
runs-on: ubuntu-latest runs-on: docker
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+2 -2
View File
@@ -32,7 +32,7 @@ concurrency:
jobs: jobs:
build-push: build-push:
runs-on: ubuntu-latest runs-on: docker
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -91,7 +91,7 @@ jobs:
fi fi
tag-commit: tag-commit:
runs-on: ubuntu-latest runs-on: docker
needs: [build-push] needs: [build-push]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+44 -25
View File
@@ -17,12 +17,15 @@ on:
GITOPS_REPO: GITOPS_REPO:
required: true required: true
type: string type: string
GITOPS_EXTRA_CMD:
required: false
type: string
secrets: secrets:
GITOPS_DISPATCH_TOKEN: GITOPS_DISPATCH_TOKEN:
required: true required: true
outputs: outputs:
summary: summary:
description: 'Pipe-format: component|version|status|commit_sha|repo' description: "Pipe-format: component|version|status|commit_sha|repo"
value: ${{ jobs.dispatch.outputs.summary }} value: ${{ jobs.dispatch.outputs.summary }}
env: env:
@@ -34,6 +37,7 @@ env:
GITOPS_SOURCE_COMMIT: ${{ github.sha }} GITOPS_SOURCE_COMMIT: ${{ github.sha }}
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }} GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GITOPS_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }} GITOPS_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
GITOPS_EXTRA_CMD: ${{ inputs.GITOPS_EXTRA_CMD || '' }}
GITOPS_WORKFLOW: gitops-service.yaml GITOPS_WORKFLOW: gitops-service.yaml
GITOPS_DISPATCH_TIMEOUT: 30 GITOPS_DISPATCH_TIMEOUT: 30
@@ -49,25 +53,39 @@ jobs:
ID=$(date +%s | md5sum | head -c 8) ID=$(date +%s | md5sum | head -c 8)
echo "dispatch_id=$ID" >> "$GITHUB_OUTPUT" 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 - name: Dispatch to GitOps repo
env: env:
GITEA_TOKEN: ${{ secrets.GITOPS_DISPATCH_TOKEN }} GITEA_TOKEN: ${{ secrets.GITOPS_DISPATCH_TOKEN }}
run: | run: |
INPUTS=$(jq -nc \ INPUTS=$(jq -nc \
--arg dispatch_id "${{ steps.gen.outputs.dispatch_id }}" \ --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 file "$GITOPS_FILE" \
--arg yq_tpl "$GITOPS_YQ_TPL" \ --arg yq_tpl "$GITOPS_YQ_TPL" \
--arg version "$GITOPS_VERSION" \ --arg version "$GITOPS_VERSION" \
--arg source_repo "$GITOPS_SOURCE_REPO" \ --arg source_repo "$GITOPS_SOURCE_REPO" \
--arg source_commit "$GITOPS_SOURCE_COMMIT" \ --arg source_commit "$GITOPS_SOURCE_COMMIT" \
--arg git_tag_prefix "${GITOPS_TAG_PREFIX:-}" \ --arg git_tag_prefix "${GITOPS_TAG_PREFIX:-}" \
'{dispatch_id: $dispatch_id, file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_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 \ curl -s -X POST \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/actions/workflows/${GITOPS_WORKFLOW}/dispatches" \ "${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/actions/workflows/${GITOPS_WORKFLOW}/dispatches" \
-H "Authorization: token $GITEA_TOKEN" \ -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$(jq -nc --arg ref "main" --argjson inputs "$INPUTS" '{ref: "main", inputs: $inputs}')" -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 - name: Poll for completion
id: poll id: poll
env: env:
@@ -131,32 +149,12 @@ jobs:
--arg msg "$SEARCH_MSG" \ --arg msg "$SEARCH_MSG" \
'[.[] | select(.commit.message | contains($msg))] | .[0].sha // empty') '[.[] | select(.commit.message | contains($msg))] | .[0].sha // empty')
# 3. If not found → no change → use HEAD # 3. If not found → fail
if [ -z "$GITOPS_COMMIT" ]; then if [ -z "$GITOPS_COMMIT" ]; then
GITOPS_COMMIT=$(echo "$COMMITS" | jq -r '.[0].sha // empty') echo "ERROR: no matching GitOps commit found for version ${GITOPS_VERSION}" >&2
exit 1
fi fi
# 4. Set commit status on caller's own 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
STATUS_URL="${GITEA_API_URL}/api/v1/repos/${GITOPS_SOURCE_REPO}/statuses/${GITOPS_SOURCE_COMMIT}"
STATUS_BODY=$(jq -nc \
--arg state "success" \
--arg description "$DESCRIPTION" \
--arg context "$CONTEXT" \
--arg target_url "${GITEA_API_URL}/${GITOPS_REPO}/commit/${GITOPS_COMMIT}" \
'{state: $state, target_url: $target_url, description: $description, context: $context}')
curl -s -X POST "$STATUS_URL" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "$STATUS_BODY" || true
echo "GITOPS_COMMIT=$GITOPS_COMMIT" >> "$GITHUB_OUTPUT" echo "GITOPS_COMMIT=$GITOPS_COMMIT" >> "$GITHUB_OUTPUT"
echo "$GITOPS_COMMIT" > /tmp/gitops-commit echo "$GITOPS_COMMIT" > /tmp/gitops-commit
exit 0 exit 0
@@ -181,3 +179,24 @@ jobs:
fi fi
COMPONENT="${GITOPS_TAG_PREFIX:-${GITOPS_FILE}}" COMPONENT="${GITOPS_TAG_PREFIX:-${GITOPS_FILE}}"
echo "GITOPS_SUMMARY=${COMPONENT}|${GITOPS_VERSION}|${STATUS}|${GITOPS_SHA}|${GITOPS_REPO}" >> "$GITHUB_OUTPUT" 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}"
+13
View File
@@ -8,6 +8,12 @@ on:
version: version:
required: true required: true
type: string type: string
chart_path:
required: true
type: string
extra_dependency_paths:
required: false
type: string
secrets: secrets:
GITEA_TOKEN: GITEA_TOKEN:
required: true required: true
@@ -47,6 +53,13 @@ jobs:
repository: niko/gitea-ci-library repository: niko/gitea-ci-library
path: .ci 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 - name: Package Helm chart
run: | run: |
CHART_DIR=$(dirname "${CHART_FILE}") CHART_DIR=$(dirname "${CHART_FILE}")
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
shell: bash shell: bash
run: | run: |
SHA8="${GITHUB_SHA:0:8}" SHA8="${GITHUB_SHA:0:8}"
BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}" BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}"
{ {
echo "## Test Reports" echo "## Test Reports"
+59 -12
View File
@@ -151,58 +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 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. 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 ```bash
helm repo add gitea https://dl.gitea.com/charts helm repo add gitea https://dl.gitea.com/charts
helm repo update helm repo update
# 1. Yleisrunneri (DinD idle)
helm upgrade --install act-runner gitea/actions \ helm upgrade --install act-runner gitea/actions \
--set enabled=true \ --set enabled=true \
--set giteaRootURL="$GITEA_URL" \ --set giteaRootURL="$GITEA_URL" \
--set existingSecret=act-runner-token \ --set existingSecret=act-runner-token \
--set existingSecretKey=token \ --set existingSecretKey=token \
--set statefulset.replicas=3 \ --set statefulset.replicas=2 \
--set statefulset.runner.tag=1.0.8 \ --set statefulset.runner.tag=1.0.8 \
--set statefulset.dind.tag=29.5.2-dind \ --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: --set-string 'statefulset.runner.config=log:
level: info level: info
cache: cache:
enabled: false enabled: true
container: container:
require_docker: true require_docker: false' \
docker_timeout: 300s' \
--namespace "$GITEA_ACTIONS_NAMESPACE" \ --namespace "$GITEA_ACTIONS_NAMESPACE" \
--create-namespace --create-namespace
# 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. `path escapes from parent` -bugi korjattiin Docker 29.5.2:ssa. Tämän teko aikana default on 29.5.1 — juuri tämän alle jäävä versio.
Oletus-lokitaso on `debug` — suositeltu `info`. Näkee jobien aloitukset ja valmistumiset ilman konttikerrosten purkua (Downloading/Extracting-spämmiä). `debug` on tarpeen vain vianselvityksessä. Oletus-lokitaso on `debug` — suositeltu `info`. Näkee jobien aloitukset ja valmistumiset ilman konttikerrosten purkua (Downloading/Extracting-spämmiä). `debug` on tarpeen vain vianselvityksessä.
`cache.enabled: true` nappaa image-cachen käyttöön — ilman sitä jokainen ajo lataa konttikuvat uudestaan.
#### Docker (DinD) #### Docker (DinD)
Helm chart deployaa DinD:n init-sidecarina (`docker:dind` samassa podissa). > **Huomio:** Gitea 1.26.x ei tue vielä label-pohjaista runner-valintaa.
`require_docker: true` kytkee jobit siihen — erillistä DinD-asennusta ei tarvita. > `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 Helm chart deployaa DinD:n init-sidecarina (`docker:dind` samassa podissa).
`path escapes from parent` -virheen job-kontin käynnistyksessä. 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ä. Maven/npm-ajot käyttävät vain workflow'n `container:`-imagea; DinD tarvitaan vasta Docker-buildissä.
### 3. Varmista ### 5. Varmista
```bash ```bash
kubectl get pods -n gitea-actions kubectl get pods -n gitea-actions
# → act-runner-runner-0 Running # → 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) # → 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) Site Admin → Actions → Runners (tai Org → Settings → Actions → Runners)
# → act-runner-runner-0 Active ubuntu-latest # → 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. Tämän jälkeen `.gitea/workflows/ci.yml` triggeröityy automaattisesti pushista.
@@ -279,4 +323,7 @@ Tarkka asennus: [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md)
| `existingSecret` | Kubernetes secretin nimi, jossa token | | `existingSecret` | Kubernetes secretin nimi, jossa token |
| `existingSecretKey` | Avain secretin sisällä | | `existingSecretKey` | Avain secretin sisällä |
| `statefulset.dind.tag` | DinD-image tag (`29.5.2-dind` minimi) | | `statefulset.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 | | `statefulset.runner.labels` | Mukautetut labelit |
+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 > Päivitetty: 2026-06-28 — git-pages korvattu Nginx + upload-sidecar -arkkitehtuurilla.
> 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.
--- ## 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, ## Uusi arkkitehtuuri
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.
Tämä kirjasto on se mitä kopioidaan. Se tarjoaa valmiit, testatut, - **Nginx** palvelee staattisia tiedostoja suoraan PV:ltä
dokumentoidut rakennuspalikat joista jokainen tiimi kokoaa oman putkensa. - **Upload-sidecar** (busybox httpd + CGI) ottaa vastaan PUT + tar.gz ja purkaa PV:lle
Palikat ovat Gitea Actionsin `uses:`-direktiivillä kutsuttavia reusable - **URL = FS-polku** — `/{owner}/{repo}/{branch}/{sha8}/{suite}/`
workflow'ta — ei asennusta, ei runtime-riippuvuutta, ei versiopäivityksiä - **Branch raakana URL:ssa** — `feature/x` FS:ssä = `feature/x/`
projekteihin. - **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 | Python HTTP-serveri ottaa vastaan PUT-pyynnöt ja purkaa tar.gz:n oikeaan paikkaan. Ei monimutkaista API:a, ei tietokantaa.
|---|---|
| `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 |
Mikään workflow ei kutsu toista provider-workflowta. Consumer ### 3. Julkaisu ja luku eri porteilla
— siis mikropalvelun oma pipeline-tiedosto — on ainoa paikka joka
tietää mitä palikoita tarvitaan ja missä järjestyksessä.
**Miksi:** Tämä on sama periaate kuin Unix-putkissa tai mikropalveluissa: Eri portit mahdollistavat eri Traefik-säännöt: julkaisu vaatii BasicAuthin, luku on julkinen.
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.
### 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 ## Rajapinnat
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.
Kirjasto käyttää näitä kaikkia. Ei omaa tilakonetta, ei custom | Suunta | Protokolla | Auth | Kuvaus |
action -runtimea, ei ulkoista orkestraattoria. |--------|------------|------|--------|
| Julkaisija → Traefik | HTTPS PUT | BasicAuth `publish` | tar.gz → upload-sidecar |
**Esimerkki:** Tool-jobit eivät kutsu commit-status API:a lainkaan. | Selain → Traefik | HTTPS GET/HEAD | — | HTML-raportti nginxiltä |
Gitean oma job-status riittää — `success`/`failure`/`running` näkyy | Retention → Gitea | HTTPS GET | PAT | branch-tarkistus |
automaattisesti. API:a käytetään vain kun tarvitaan **custom-linkki** | Traefik → nginx | HTTP :8080 | — | sisäverkko |
(testiraporttiin tai Docker registryyn), jota natiivistaatus ei tarjoa. | Traefik → upload | HTTP :8081 | — | sisäverkko (BasicAuth tarkistettu) |
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 |
+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 "${GIT_PAGES_PUBLISH_TOKEN:-}" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${GITHUB_REPOSITORY:-}" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; }
[ -n "${GITHUB_SHA:-}" ] || { echo "ERROR: GITHUB_SHA is not set" >&2; exit 1; }
[ -n "${GITHUB_REF_NAME:-}" ] || { echo "ERROR: GITHUB_REF_NAME is not set" >&2; exit 1; }
OWNER="${GITHUB_REPOSITORY%%/*}"
REPO="${GITHUB_REPOSITORY##*/}"
SHA8=$(echo "$GITHUB_SHA" | cut -c1-8)
BRANCH="${GITHUB_REF_NAME}"
SUITE="${SUITE_PATH%/}"
PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}"
[ -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}:${GIT_PAGES_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
+6 -4
View File
@@ -103,7 +103,8 @@ joten `actions/checkout` toimii ilman node-asennuksia.
|-----------|------------|--------| |-----------|------------|--------|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä | | `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
| `version` | Kyllä | Version string (check-version output) | | `version` | Kyllä | Version string (check-version output) |
| `chart_path` | Ei | Polku Chart.yaml-hakemistoon, oletus `.` | | `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:** **`env_json`-avaimet:**
@@ -124,9 +125,10 @@ build-push (helm package → helm push OCI) → tag-commit (git-tagin luonti)
**Steppien kuvaus `build-push`-jobissa:** **Steppien kuvaus `build-push`-jobissa:**
1. **Node.js-asennus**`apk add --no-cache nodejs` (vaaditaan `actions/checkout`-actionia varten) 1. **Node.js-asennus**`apk add --no-cache nodejs` (vaaditaan `actions/checkout`-actionia varten)
2. **Checkout** — sovellusrepo ja gitea-ci-library `.ci/`-polkuun 2. **Checkout** — sovellusrepo ja gitea-ci-library `.ci/`-polkuun
3. **Package**`helm package` versiolla `$VERSION` 3. **Resolve extra subchart dependencies**`helm dependency update` jokaiselle `extra_dependency_paths`-polulle (vain jos input on annettu)
4. **Push OCI**`helm push` registryyn autentikoinnilla 4. **Package**`helm dependency update` + `helm package` versiolla `$VERSION`
5. **Report status** — commit-status + UI-linkki 5. **Push OCI**`helm push` registryyn autentikoinnilla
6. **Report status** — commit-status + UI-linkki
**Kompromissi:** Kontti `alpine/helm` ei sisällä node.js:ää, mutta **Kompromissi:** Kontti `alpine/helm` ei sisällä node.js:ää, mutta
`actions/checkout@v4` on JavaScript-action ja vaatii sen. Siksi nodejs `actions/checkout@v4` on JavaScript-action ja vaatii sen. Siksi nodejs
-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.
-265
View File
@@ -1,265 +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: full site rebuild ==="
echo "Rebuilding site (${#TO_DELETE[@]} report(s) to delete)..."
ARCHIVE_FILE=$(mktemp)
SITE_DIR=$(mktemp -d)
NEW_TAR=$(mktemp)
cleanup_phase4() {
rm -f "$ARCHIVE_FILE" "$NEW_TAR"
rm -rf "$SITE_DIR"
}
trap cleanup_phase4 EXIT
# Try archive.tar first
echo "Downloading archive.tar..."
HTTP_CODE=$(curl_with_host -o "$ARCHIVE_FILE" -w "%{http_code}" -sS "${PAGES_URL}/.git-pages/archive.tar")
if [ "$HTTP_CODE" = "200" ] && tar -tf "$ARCHIVE_FILE" >/dev/null 2>&1; then
echo "Extracting archive..."
tar -xf "$ARCHIVE_FILE" -C "$SITE_DIR"
for dir in "${TO_DELETE[@]}"; do
if [ -d "$SITE_DIR/$dir" ]; then
echo " Removing: $dir"
rm -rf "$SITE_DIR/$dir"
fi
done
else
echo "archive.tar failed (HTTP ${HTTP_CODE}) - falling back to manifest-based rebuild"
ALL_PATHS=$(echo "$MANIFEST" | jq -r '.contents | keys[]' 2>/dev/null || true)
if [ -z "$ALL_PATHS" ]; then
echo "ERROR: no files in manifest - cannot rebuild" >&2
exit 1
fi
EXCLUDE_GREP=""
for dir in "${TO_DELETE[@]}"; do
EXCLUDE_GREP="${EXCLUDE_GREP}${EXCLUDE_GREP:+|}^${dir}/"
done
if [ -n "$EXCLUDE_GREP" ]; then
KEEP_PATHS=$(echo "$ALL_PATHS" | grep -v -E "$EXCLUDE_GREP" || true)
else
KEEP_PATHS="$ALL_PATHS"
fi
if [ -z "$KEEP_PATHS" ]; then
echo "No files to keep - site will be empty"
mkdir -p "$SITE_DIR/__placeholder__"
echo "placeholder" > "$SITE_DIR/__placeholder__/index.html"
else
FILE_COUNT=$(echo "$KEEP_PATHS" | wc -l | tr -d ' ')
echo "Downloading ${FILE_COUNT} file(s)..."
while IFS= read -r path; do
[ -z "$path" ] && continue
dir=$(dirname "$SITE_DIR/$path")
mkdir -p "$dir"
curl_with_host -o "$SITE_DIR/$path" -sS "${PAGES_URL}/${path}" || {
echo " WARN: failed to download ${path}"
}
done <<< "$KEEP_PATHS"
fi
fi
if [ -z "$(ls -A "$SITE_DIR" 2>/dev/null)" ]; then
echo "Site is empty - creating placeholder"
mkdir -p "$SITE_DIR/__placeholder__"
echo "placeholder" > "$SITE_DIR/__placeholder__/index.html"
fi
tar -cf "$NEW_TAR" -C "$SITE_DIR" .
echo "PUT: replacing site contents..."
HTTP_CODE=$(curl_with_host -X PUT "${PAGES_URL}/" \
-H "Content-Type: application/x-tar" \
--data-binary @"${NEW_TAR}" \
-w "%{http_code}" \
-o /dev/null)
echo "HTTP ${HTTP_CODE}"
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "204" ]; then
echo "Site rebuild completed."
else
echo "ERROR: PUT 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."
-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 }}
-58
View File
@@ -1,58 +0,0 @@
{{- if .Values.initJob.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "git-pages.fullname" . }}-init
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
annotations:
"helm.sh/hook": post-install
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
spec:
backoffLimit: 5
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "git-pages.name" . }}-init
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
restartPolicy: Never
containers:
- name: init
image: "{{ .Values.initJob.image.repository }}:{{ .Values.initJob.image.tag }}"
imagePullPolicy: {{ .Values.initJob.image.pullPolicy }}
command:
- bash
- -c
- |
set -euo pipefail
apt-get update -qq && apt-get install -y -qq curl tar >/dev/null
echo "Init: waiting for git-pages..."
until curl -sf \
-H "Host: {{ .Values.ingress.host }}" \
-o /dev/null "http://git-pages:3000/.git-pages/health"
do sleep 2; done
echo "Init: checking if site already exists..."
MANIFEST=$(curl -sf \
-H "Host: {{ .Values.ingress.host }}" \
"http://git-pages:3000/.git-pages/manifest.json" 2>/dev/null || echo "")
if echo "$MANIFEST" | grep -q '"contents"'; then
echo "Init: site already initialized, skipping"
exit 0
fi
echo "Init: creating placeholder site..."
WORK=$(mktemp -d)
mkdir -p "$WORK/__init__"
echo "initialized" > "$WORK/__init__/index.html"
tar cf /tmp/init.tar -C "$WORK" __init__
curl -sf -X PUT "http://git-pages:3000/" \
-H "Host: {{ .Values.ingress.host }}" \
-H "Content-Type: application/x-tar" \
--data-binary @/tmp/init.tar -o /dev/null
echo "Init: done"
env:
- name: PAGES_INSECURE
value: "1"
{{- end }}
+5
View File
@@ -0,0 +1,5 @@
apiVersion: v2
name: git-pages
description: Nginx + upload-sidecar for CI HTML reports (static serving, Traefik BasicAuth publish)
type: application
version: 0.2.0
+52 -24
View File
@@ -1,4 +1,4 @@
# git-pages # gitea-reports
Jaettu **Gitea CI -raporttien** tallennus- ja lukupaikka: HTML-raportit (esim. Cucumber) Jaettu **Gitea CI -raporttien** tallennus- ja lukupaikka: HTML-raportit (esim. Cucumber)
commit-kohtaisiin polkuihin, selaimella avattavina linkkeinä Gitean commitin CI-job 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 ```bash
# Muokkaa oma values-tiedosto (tai käytä dev-values.yaml) # 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. # Muuta: ingress.host, certificate.issuerRef.name, jne.
``` ```
### 3. Helm-asennus ### 3. Helm-asennus
```bash ```bash
NS=git-pages NS=gitea-reports
VALUES=my-values.yaml VALUES=gitea-reports/dev-values.yaml
helm upgrade --install git-pages ./git-pages \ helm upgrade --install gitea-reports ./gitea-reports \
-n "$NS" --create-namespace \ -n "$NS" --create-namespace \
-f "$VALUES" -f "$VALUES"
helm template gitea-reports ./gitea-reports -n "$NS" -f "$VALUES"
``` ```
Helm ajaa asennuksen jälkeen init-jobin, joka PUTtaa paikanpitäjäsivun Nginx ja upload-sidecar käynnistyvät suoraan ilman init-vaihetta.
git-pagesiin. Tämä luo tarvittavan `.index`-tiedoston — sen jälkeen PV on tyhjä ennen ensimmäistä CI-julkaisua.
Gitea Actions -scriptit voivat käyttää suoraan PATCHia ilman
PUT-fallbackia.
--- ---
## Vie publish-token Gitea Actions-secretiin (per repo) ## 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 ```bash
NS=git-pages NS=gitea-reports
REPO_OWNER="niko" REPO_OWNER="niko"
REPO_NAME="gitea-ci-library" REPO_NAME="gitea-ci-library"
# 1. Lue plaintext-token erillisestä secretistä # 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 # 2. Kopioi leikepöydälle
echo -n "$TOKEN" | pbcopy # macOS echo -n "$TOKEN" | pbcopy # macOS
@@ -113,14 +113,14 @@ Esimerkki dev-ympäristöstä: [dev-values.yaml](dev-values.yaml).
Repojuuresta (sama `NS` kuin [docs/secrets.md](docs/secrets.md)): Repojuuresta (sama `NS` kuin [docs/secrets.md](docs/secrets.md)):
```bash ```bash
NS=git-pages NS=gitea-reports
VALUES=git-pages/dev-values.yaml VALUES=gitea-reports/dev-values.yaml
helm upgrade --install git-pages ./git-pages \ helm upgrade --install gitea-reports ./gitea-reports \
-n "$NS" --create-namespace \ -n "$NS" --create-namespace \
-f "$VALUES" -f "$VALUES"
helm template git-pages ./git-pages -f "$VALUES" helm template gitea-reports ./gitea-reports -f "$VALUES"
``` ```
--- ---
@@ -131,21 +131,49 @@ Julkaisu DNS-osoitteeseen BasicAuthilla:
```bash ```bash
# Esimerkki: julkaise raportti # Esimerkki: julkaise raportti
curl -X PATCH https://ci-reports.helm-dev.keskikuja.site/owner/repo/commit/sha8/ \ curl -X PUT \
-H "Authorization: Basic $(echo -n "publish:$GIT_PAGES_PUBLISH_TOKEN" | base64)" \ -u publish:$GIT_PAGES_PUBLISH_TOKEN \
-H "Content-Type: application/x-tar" \ -H "Content-Type: application/tar+gz" \
--data-binary @raportti.tar --data-binary @report.tar.gz \
https://ci-reports.helm-dev.keskikuja.site/{owner}/{repo}/{branch}/{sha8}/{suite}/
``` ```
**Vaaditut asetukset:** **Vaaditut asetukset:**
| Missä | Arvo | | Missä | Arvo |
|-------|------| |-------|------|
| Gitea Actions secret | `GIT_PAGES_PUBLISH_TOKEN` (sama kuin K8s `git-pages-publish-token` `token`-avain) | | Gitea Actions secret | `GIT_PAGES_PUBLISH_TOKEN` (sama kuin K8s `gitea-reports-publish-token` `token`-avain) |
| Scriptissä | `curl` käyttää BasicAuth-headeria yllä | | Scriptissä | `bash scripts/publish-gitea-reports.sh <suite>` |
**K8s secretit (Traefik-yhteensopivuus):** **K8s secretit (Traefik-yhteensopivuus):**
- `git-pages-publish-auth` = htpasswd (Traefik BasicAuth middleware) - `gitea-reports-publish-auth` = htpasswd (Traefik BasicAuth middleware)
- `git-pages-publish-token` = plaintext (luetaan Giteaan viedessä) - `gitea-reports-publish-token` = plaintext (luetaan Giteaan viedessä)
Tarkemmat secret-ohjeet: [docs/secrets.md](docs/secrets.md). 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. # 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: ingress:
host: ci-reports.helm-dev.keskikuja.site host: ci-reports.helm-dev.keskikuja.site
@@ -19,11 +19,11 @@ retention:
rules: rules:
branches: branches:
default: default:
maxAgeDays: 90 minAgeDays: 3
keepMin: 5 keepMin: 5
main: main:
maxAgeDays: 365 minAgeDays: 3
keepMin: 20 keepMin: 10
master: master:
maxAgeDays: 365 minAgeDays: 3
keepMin: 20 keepMin: 10
+82
View File
@@ -0,0 +1,82 @@
# Architecture — Nginx + upload-sidecar
> git-pages on korvattu. Uusi arkkitehtuuri: Nginx palvelee staattisia tiedostoja PV:ltä, upload-sidecar (busybox httpd + CGI) ottaa vastaan tar.gz-PUTit.
## Yleiskuvaus
Yksi apex-host, monta Gitea-repoa. URL = FS-polku: `/{owner}/{repo}/{branch}/{sha8}/{suite}/`.
Ei Host-header-kikkaa, ei slugia, ei rewritea.
## Komponentit
| Komponentti | Rooli |
|-------------|-------|
| **nginx** | Palvelee staattisia tiedostoja /app/data -rootista (:8080) |
| **upload** (Python HTTP server) | Vastaanottaa PUT /path + tar.gz body, purkaa PV:lle (:8081) |
| **PVC** | Raporttisisältö suoraan FS-muodossa |
| **Service** | ClusterIP :8080 (read), :8081 (write) |
| **Traefik IngressRoute** | PATCH/PUT → upload:8081 (BasicAuth), GET/HEAD → nginx:8080 |
| **Traefik Middleware** | `git-pages-publish-auth` (BasicAuth), HTTPS-redirect |
| **cert-manager Certificate** | TLS |
## URL ja sisältö
```
https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/main/f4baa286/cucumber/index.html
└── selvä URL ──┘ └─────────── Gitea-yhteensopiva polku ──────────────────────────┘
```
Levyllä:
```
/app/data/{owner}/{repo}/{branch}/{sha8}/
.meta
index.html
...
```
## Järjestelmäkaavio
```mermaid
flowchart TB
subgraph ext["Ulkoiset"]
PUB["Julkaisija\n(CI)"]
BR["Selain"]
GITEA["Gitea API\n(branch-lista)"]
end
subgraph edge["Reuna"]
TRAEFIK["Traefik\nIngressRoute + Middleware"]
CM["cert-manager\nTLS"]
end
subgraph cluster["Pod"]
NGINX["nginx\n(kontti)"]
UPLOAD["upload sidecar\n(Python HTTP server)"]
PVC["PVC /app/data"]
end
PUB -->|"PUT + BasicAuth\ntar.gz"| TRAEFIK
BR -->|"GET/HEAD"| TRAEFIK
TRAEFIK -->|"PUT/PATCH"| UPLOAD
TRAEFIK -->|"GET/HEAD"| NGINX
CM --> TRAEFIK
UPLOAD -->|"extract to PV"| PVC
NGINX --> PVC
```
## Julkaisu
1. Julkaisija paketoi tiedostot tar.gz:ksi
2. `PUT https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`
3. Traefik tarkistaa BasicAuth → välittää upload-sidecarille
4. CGI purkaa tar.gz:n suoraan PV:lle: `/app/data/{owner}/{repo}/{branch}/{sha8}/{suite}/`
## Luku
1. Selain avaa URL:n (GET/HEAD)
2. Traefik välittää nginxille
3. Nginx palauttaa suoraan PV:ltä
## Retention
CronJob lukee PV:tä suoraan (find, ls, stat), tarkistaa branchin olemassaolon Giteasta, poistaa vanhat commit-kansiot.
@@ -40,7 +40,7 @@ echo "$GIT_PAGES_PUBLISH_TOKEN"
### Vaihe 4: Luo K8s secrets ### Vaihe 4: Luo K8s secrets
```bash ```bash
NS=git-pages NS=gitea-reports
# 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin) # 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin)
kubectl create secret generic git-pages-publish-auth \ kubectl create secret generic git-pages-publish-auth \
@@ -62,6 +62,31 @@ 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 git-pages-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 `GIT_PAGES_PUBLISH_TOKEN` secret, ja terminaalista kopioitu arvo `values` kenttään.
Description voi olla esim:
> Julkaisuun liittyvä salaisuus, jonka ci-reports auth tarkistaa, että saako julkaista raporttia.
**Gitea UI:ssa:** New Secret → Name: `GIT_PAGES_PUBLISH_TOKEN` → Value: **liitä token** → Save
Toista jokaiselle repolle, joka julkaisee raportteja.
---
### Seuraava: Helm-asennus ### Seuraava: Helm-asennus
Palaa takaisin [README.md](../README.md#käyttöönotto) ja jatka kohdasta "Instanssin values-tiedosto". Palaa takaisin [README.md](../README.md#käyttöönotto) ja jatka kohdasta "Instanssin values-tiedosto".
@@ -110,20 +135,23 @@ sequenceDiagram
participant Traefik as Traefik participant Traefik as Traefik
participant K8sAuth as K8s Secret<br/>git-pages-publish-auth participant K8sAuth as K8s Secret<br/>git-pages-publish-auth
participant K8sToken as K8s Secret<br/>git-pages-publish-token participant K8sToken as K8s Secret<br/>git-pages-publish-token
participant GP as git-pages participant Upload as upload-sidecar
participant PV as PVC /app/data
Note over Actions: 1. Lue plaintext-token Note over Actions: 1. Lue plaintext-token
Actions->>K8sToken: lue token-avain Actions->>K8sToken: lue token-avain
K8sToken-->>Actions: plaintext token K8sToken-->>Actions: plaintext token
Note over Actions: 2. Lähettää raportin Note over Actions: 2. Lähettää raportin
Actions->>Traefik: PUT / + BasicAuth<br/>publish:TOKEN + repo-url Actions->>Traefik: PUT /{polku} + BasicAuth<br/>publish:TOKEN + tar.gz
Traefik->>K8sAuth: lue users (htpasswd) Traefik->>K8sAuth: lue users (htpasswd)
K8sAuth-->>Traefik: publish:$apr1$... K8sAuth-->>Traefik: publish:$apr1$...
alt Token match alt Token match
Traefik->>GP: välitä Traefik->>Upload: PUT /{path}
GP-->>Traefik: 200 OK Upload->>PV: extract tar.gz
Traefik-->>Actions: 200 OK PV-->>Upload: 201 Created
Upload-->>Traefik: 201 Created
Traefik-->>Actions: 201 Created
else Token ei match else Token ei match
Traefik-->>Actions: 401 Unauthorized Traefik-->>Actions: 401 Unauthorized
end end
@@ -141,11 +169,14 @@ sequenceDiagram
sequenceDiagram sequenceDiagram
participant Browser as Selain participant Browser as Selain
participant Traefik as Traefik participant Traefik as Traefik
participant GP as git-pages participant Nginx as nginx
participant PV as PVC /app/data
Browser->>Traefik: GET /OWNER/REPO/commit/SHA/raportti/index.html Browser->>Traefik: GET /OWNER/REPO/branch/SHA/suite/index.html
Traefik->>GP: välitä (ei authia) Traefik->>Nginx: välitä (ei authia)
GP-->>Traefik: HTML Nginx->>PV: lue tiedosto
PV-->>Nginx: HTML
Nginx-->>Traefik: HTML
Traefik-->>Browser: HTML Traefik-->>Browser: HTML
``` ```
@@ -157,26 +188,25 @@ GET/HEAD-reitillä ei ole Middlewarea. Luku on julkinen, jos URL tunnetaan.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Sidecar as Retention Sidecar participant CronJob as Retention CronJob
participant K8sSecret as K8s Secret<br/>git-pages-retention-gitea participant K8sSecret as K8s Secret<br/>git-pages-retention-gitea
participant GiteaAPI as Gitea API participant GiteaAPI as Gitea API
participant GP as git-pages (localhost:3000) participant PV as PVC /app/data
Note over Sidecar: 1. Lue PAT Note over CronJob: 1. Lue PAT
Sidecar->>K8sSecret: lue token CronJob->>K8sSecret: lue token
K8sSecret-->>Sidecar: Gitea PAT K8sSecret-->>CronJob: Gitea PAT
Note over Sidecar: 2. Lue manifest Note over CronJob: 2. Lue PV suoraan
Sidecar->>GP: GET .git-pages/manifest.json CronJob->>PV: ls /app/data/{owner}/{repo}/{branch}/
GP-->>Sidecar: sisällysluettelo PV-->>CronJob: commit-kansiot
Note over Sidecar: 3. Kysy branch Note over CronJob: 3. Kysy branch
Sidecar->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH CronJob->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH
GiteaAPI-->>Sidecar: 200 / 404 GiteaAPI-->>CronJob: 200 / 404
Note over Sidecar: 4. Luo whiteout-tar + PATCH Note over CronJob: 4. Poista vanhat
Sidecar->>GP: PATCH / (whiteout) CronJob->>PV: rm -rf vanhat commit-kansiot
GP-->>Sidecar: 200 OK
``` ```
**Huomio:** Retention-PAT:in omistajalla on oltava lukuoikeus KAIKKIIN repoihin, **Huomio:** Retention-PAT:in omistajalla on oltava lukuoikeus KAIKKIIN repoihin,
@@ -195,7 +225,7 @@ joista raportteja on PVC:llä.
GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)" GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)"
# 2. Päivitä K8s secrets (molemmat) # 2. Päivitä K8s secrets (molemmat)
NS=git-pages NS=gitea-reports
kubectl delete secret git-pages-publish-auth -n "$NS" kubectl delete secret git-pages-publish-auth -n "$NS"
kubectl delete secret git-pages-publish-token -n "$NS" kubectl delete secret git-pages-publish-token -n "$NS"
@@ -216,7 +246,7 @@ Jos repoja on monta, voit käyttää Gitea API:ta (vaatii admin-tokenin):
```bash ```bash
ADMIN_TOKEN="<gitea-admin-token>" ADMIN_TOKEN="<gitea-admin-token>"
NS=git-pages NS=gitea-reports
# Lue plaintext-token erillisestä secretistä # 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 git-pages-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
@@ -66,7 +66,7 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f
| `values.yaml` | Chart-vakiot | Image, resurssit, Traefik entrypointit | | `values.yaml` | Chart-vakiot | Image, resurssit, Traefik entrypointit |
| `{env}-values.yaml` | Instanssi | Host, issuer, PVC koko/storageClass | | `{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`
--- ---
+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."
@@ -1,16 +1,19 @@
git-pages installed. Nginx + upload-sidecar installed.
See docs/secrets.md for secret prerequisites (K8s + Gitea Actions). See docs/secrets.md for secret prerequisites (K8s + Gitea Actions).
Install: Install:
helm upgrade --install git-pages ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml helm upgrade --install git-pages ./git-pages -n {{ .Release.Namespace }} --create-namespace -f dev-values.yaml
Host: https://{{ .Values.ingress.host }} Host: https://{{ .Values.ingress.host }}
Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/reports/{sha8}/index.html Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/{branch}/{sha8}/{suite}/
Publish (CI): Publish (CI):
PATCH https://{{ .Values.ingress.host }}/ curl -X PUT \
Authorization: Basic publish:<GIT_PAGES_PUBLISH_TOKEN> -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 }} ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml Upgrade: helm upgrade {{ .Release.Name }} ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml
Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }}
+42
View File
@@ -0,0 +1,42 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "git-pages.fullname" . }}-config
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
data:
default.conf: |
client_max_body_size {{ .Values.nginx.bodySize }};
server {
listen {{ .Values.nginx.port }};
root /app/data;
autoindex on;
client_max_body_size {{ .Values.nginx.bodySize }};
location / {
try_files $uri $uri/ $uri/index.html =404;
}
}
upload-server.py: |
#!/usr/bin/env python3
import http.server, os, sys, tarfile, io
PORT = int(os.environ.get("UPLOAD_PORT", "8081"))
DATA_ROOT = "/app/data"
class Handler(http.server.BaseHTTPRequestHandler):
def do_PUT(self):
dest = DATA_ROOT + self.path.rstrip("/")
os.makedirs(dest, exist_ok=True)
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
with tarfile.open(fileobj=io.BytesIO(body), mode="r:gz") as tar:
tar.extractall(dest)
self.send_response(201)
self.end_headers()
def log_message(self, fmt, *args):
sys.stderr.write("%s - %s\n" % (self.client_address[0], fmt % args))
http.server.HTTPServer(("", PORT), Handler).serve_forever()
+75
View File
@@ -0,0 +1,75 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "git-pages.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "git-pages.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "git-pages.componentLabels" . | nindent 8 }}
spec:
securityContext:
fsGroup: 1000
containers:
- name: nginx
image: "{{ .Values.nginx.image }}:{{ .Values.nginx.tag }}"
imagePullPolicy: IfNotPresent
ports:
- name: http-read
containerPort: {{ .Values.nginx.port }}
protocol: TCP
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
resources:
{{- toYaml .Values.nginx.resources | nindent 12 }}
- name: upload
image: "{{ .Values.upload.image }}:{{ .Values.upload.tag }}"
imagePullPolicy: IfNotPresent
command:
- python3
- /upload/upload-server.py
ports:
- name: http-write
containerPort: {{ .Values.upload.port }}
protocol: TCP
volumeMounts:
- name: upload-script
mountPath: /upload
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
resources:
{{- toYaml .Values.upload.resources | nindent 12 }}
volumes:
- name: nginx-conf
configMap:
name: {{ include "git-pages.fullname" . }}-config
items:
- key: default.conf
path: default.conf
- name: upload-script
configMap:
name: {{ include "git-pages.fullname" . }}-config
defaultMode: 0755
items:
- key: upload-server.py
path: upload-server.py
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "git-pages.fullname" . }}-data
{{- end }}
@@ -17,12 +17,12 @@ spec:
- name: {{ include "git-pages.fullname" . }}-publish-auth - name: {{ include "git-pages.fullname" . }}-publish-auth
services: services:
- name: {{ include "git-pages.fullname" . }} - name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }} port: {{ .Values.upload.port }}
- match: Host(`{{ .Values.ingress.host }}`) && (Method(`GET`) || Method(`HEAD`)) - match: Host(`{{ .Values.ingress.host }}`) && (Method(`GET`) || Method(`HEAD`))
kind: Rule kind: Rule
services: services:
- name: {{ include "git-pages.fullname" . }} - name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }} port: {{ .Values.nginx.port }}
tls: tls:
secretName: git-pages-tls secretName: git-pages-tls
--- ---
@@ -44,5 +44,5 @@ spec:
- name: {{ include "git-pages.fullname" . }}-https-redirect - name: {{ include "git-pages.fullname" . }}-https-redirect
services: services:
- name: {{ include "git-pages.fullname" . }} - name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }} port: {{ .Values.nginx.port }}
{{- end }} {{- end }}
@@ -8,6 +8,8 @@ metadata:
data: data:
retention.json: | retention.json: |
{{- .Values.retention.rules | toJson | nindent 4 }} {{- .Values.retention.rules | toJson | nindent 4 }}
retention-lib.sh: |
{{- .Files.Get "files/retention-lib.sh" | nindent 4 }}
retention-cleanup.sh: | retention-cleanup.sh: |
{{- .Files.Get "files/retention-cleanup.sh" | nindent 4 }} {{- .Files.Get "files/retention-cleanup.sh" | nindent 4 }}
retention-run.sh: | retention-run.sh: |
@@ -34,15 +34,9 @@ spec:
set -euo pipefail set -euo pipefail
apt-get update -qq apt-get update -qq
apt-get install -y --no-install-recommends curl jq >/dev/null 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 /scripts/retention-run.sh
env: env:
- name: NAMESPACE
value: {{ .Release.Namespace | quote }}
- name: DEPLOYMENT
value: {{ include "git-pages.fullname" . | quote }}
- name: INSTANCE
value: {{ .Release.Name | quote }}
- name: DATA_ROOT - name: DATA_ROOT
value: /app/data value: /app/data
- name: RETENTION_CONFIG - name: RETENTION_CONFIG
@@ -9,7 +9,11 @@ spec:
selector: selector:
{{- include "git-pages.selectorLabels" . | nindent 4 }} {{- include "git-pages.selectorLabels" . | nindent 4 }}
ports: ports:
- name: http - name: http-read
port: {{ .Values.service.port }} port: {{ .Values.nginx.port }}
targetPort: http targetPort: http-read
protocol: TCP
- name: http-write
port: {{ .Values.upload.port }}
targetPort: http-write
protocol: TCP 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. # Constants — shared across all instances. Do not put per-env values here.
# Per instance: use {env}-values.yaml (e.g. dev-values.yaml): # 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: "" nameOverride: ""
fullnameOverride: "" fullnameOverride: ""
image: nginx:
repository: codeberg.org/git-pages/git-pages image: nginx
tag: "0.9.1" tag: alpine
pullPolicy: IfNotPresent 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: service:
type: ClusterIP type: ClusterIP
port: 3000
persistence: persistence:
enabled: true enabled: true
accessMode: ReadWriteOnce accessMode: ReadWriteOnce
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
ingress: ingress:
enabled: true enabled: true
entryPoints: entryPoints:
@@ -37,15 +46,6 @@ ingress:
certificate: certificate:
enabled: true 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). # Optional Helm-managed secret — prefer manual create (see docs/secrets.md).
publishAuth: publishAuth:
create: false create: false
@@ -53,7 +53,7 @@ publishAuth:
retention: retention:
enabled: false enabled: false
mode: sidecar mode: cronjob
schedule: "0 3 * * *" schedule: "0 3 * * *"
image: image:
repository: debian repository: debian
@@ -63,5 +63,5 @@ retention:
rules: rules:
branches: branches:
default: default:
maxAgeDays: 90 minAgeDays: 7
keepMin: 5 keepMin: 5
+1 -1
View File
@@ -98,7 +98,7 @@ if [ "$TOTAL" -eq 1 ]; then
SINGLE_ENTRY="${first_name}/index.html" SINGLE_ENTRY="${first_name}/index.html"
fi fi
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${SINGLE_ENTRY}" URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}/${SINGLE_ENTRY}"
sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "" "$URL" sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "" "$URL"
else else
generate_index generate_index
+4 -1
View File
@@ -22,7 +22,10 @@ INPUTS=$(jq -nc \
--arg source_repo "$GITOPS_SOURCE_REPO" \ --arg source_repo "$GITOPS_SOURCE_REPO" \
--arg source_commit "$GITOPS_SOURCE_COMMIT" \ --arg source_commit "$GITOPS_SOURCE_COMMIT" \
--arg git_tag_prefix "${GITOPS_TAG_PREFIX:-}" \ --arg git_tag_prefix "${GITOPS_TAG_PREFIX:-}" \
'{file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_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)" DIR="$(cd "$(dirname "$0")" && pwd)"
echo "gitops-dispatch: dispatching to $GITOPS_REPO/$GITOPS_WORKFLOW..." echo "gitops-dispatch: dispatching to $GITOPS_REPO/$GITOPS_WORKFLOW..."
+8 -3
View File
@@ -84,7 +84,12 @@ _gitops_update() {
cd "${CLONE_DIR}" || _gitops_fail "Failed to enter clone directory" cd "${CLONE_DIR}" || _gitops_fail "Failed to enter clone directory"
yq eval -i "${YQ_EXPR}" "${INPUT_FILE}" || _gitops_fail "Failed to update ${INPUT_FILE}" yq eval -i "${YQ_EXPR}" "${INPUT_FILE}" || _gitops_fail "Failed to update ${INPUT_FILE}"
git add "${INPUT_FILE}" || _gitops_fail "Failed to stage ${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 if git diff --cached --quiet; then
echo "No changes — ${INPUT_FILE} already at ${VERSION}" echo "No changes — ${INPUT_FILE} already at ${VERSION}"
@@ -93,8 +98,8 @@ _gitops_update() {
exit 0 exit 0
fi fi
git -c user.name="gitea-ci-bot" \ git -c user.name="${GIT_USER_NAME:-gitea-ci-bot}" \
-c user.email="ci@keskikuja.site" \ -c user.email="${GIT_USER_EMAIL:-ci@keskikuja.site}" \
commit -m "[skip ci] gitops: update version to ${VERSION}" || _gitops_fail "Failed to commit" commit -m "[skip ci] gitops: update version to ${VERSION}" || _gitops_fail "Failed to commit"
GITOPS_SHA="$(git rev-parse HEAD)" GITOPS_SHA="$(git rev-parse HEAD)"
git push || _gitops_fail "Failed to push" git push || _gitops_fail "Failed to push"
+34 -40
View File
@@ -4,54 +4,48 @@ set -eu
SUITE_PATH="${1:-}" SUITE_PATH="${1:-}"
[ -n "$SUITE_PATH" ] || { echo "ERROR: suite_path argument required" >&2; exit 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_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 "${GIT_PAGES_PUBLISH_TOKEN:-}" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${GITHUB_REPOSITORY:-}" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; } [ -n "${GITHUB_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_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%%/*}" OWNER="${GITHUB_REPOSITORY%%/*}"
REPO="${GITHUB_REPOSITORY##*/}" REPO="${GITHUB_REPOSITORY##*/}"
SHA8=$(echo "$GITHUB_SHA" | cut -c1-8) SHA8=$(echo "$GITHUB_SHA" | cut -c1-8)
BRANCH="${GITHUB_REF_NAME}"
SUITE="${SUITE_PATH%/}"
PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}" 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}/"
WORK=$(mktemp -d) WORK=$(mktemp -d)
TAR=$(mktemp) TAR=$(mktemp)
trap 'rm -rf "$WORK" "$TAR"' EXIT trap 'rm -rf "$WORK" "$TAR"' EXIT
RELPATH="${REPORT_DIR#reports/${SHA8}/}" # Kopioi raporttitiedostot
if [ "$RELPATH" != "$REPORT_DIR" ] && [ -n "$RELPATH" ]; then mkdir -p "$WORK/$SUITE"
TARGET="$WORK/${OWNER}/${REPO}/reports/${SHA8}/${RELPATH}" cp -a "$SUITE/." "$WORK/$SUITE/"
else
TARGET="$WORK/${OWNER}/${REPO}/reports/${SHA8}"
fi
mkdir -p "$TARGET"
cp -a "$REPORT_DIR/." "$TARGET/"
if [ ! -f "$TARGET/index.html" ]; then # Generoi index.html (sama logiikka kuin nykyään)
cd "$WORK/$SUITE"
if [ ! -f "index.html" ]; then
ITEM_LIST="" ITEM_LIST=""
ITEM_COUNT=0 ITEM_COUNT=0
for f in "$TARGET"/*; do for f in *; do
[ -f "$f" ] || continue [ -f "$f" ] || continue
base=$(basename "$f") [ "$f" = "index.html" ] && continue
[ "$base" = "index.html" ] && continue ITEM_LIST="${ITEM_LIST}file:${f}
ITEM_LIST="${ITEM_LIST}file:${base}
" "
ITEM_COUNT=$((ITEM_COUNT + 1)) ITEM_COUNT=$((ITEM_COUNT + 1))
done done
for d in "$TARGET"/*/; do for d in */; do
[ -d "$d" ] || continue [ -d "$d" ] || continue
base=$(basename "$d") d="${d%/}"
[ -f "$d/index.html" ] || continue [ -f "$d/index.html" ] || continue
ITEM_LIST="${ITEM_LIST}dir:${base} ITEM_LIST="${ITEM_LIST}dir:${d}
" "
ITEM_COUNT=$((ITEM_COUNT + 1)) ITEM_COUNT=$((ITEM_COUNT + 1))
done done
@@ -82,36 +76,36 @@ if [ ! -f "$TARGET/index.html" ]; then
done done
echo '</ul></body></html>' echo '</ul></body></html>'
} > "$TARGET/index.html" } > "index.html"
fi fi
fi fi
cat > "$TARGET/.meta" <<EOF # .meta tiedosto retentionia varten
{"branch":"${GITHUB_REF_NAME:-}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"} cat > ".meta" <<EOF
{"branch":"${BRANCH}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF EOF
find "$WORK/$OWNER" \( -type f -o -type l \) -print | sed "s|^${WORK}/||" | tar -cf "$TAR" -C "$WORK" -T -
publish() { cd "$WORK"
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}"
}
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}:${GIT_PAGES_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 case "$HTTP_CODE" in
200|201|204) ;; 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 cat /tmp/git-pages-publish-response.txt >&2
exit 1 exit 1
;; ;;
esac esac
echo "$REPORT_BASE" echo "${PUBLISH_URL}"
+1 -1
View File
@@ -18,7 +18,7 @@ if [ -n "$CUSTOM_URL" ]; then
elif [ -n "$SUITE" ]; then elif [ -n "$SUITE" ]; then
SUITE="${SUITE%/}/" SUITE="${SUITE%/}/"
SHA8_CUT=$(echo "$GITHUB_SHA" | cut -c1-8) SHA8_CUT=$(echo "$GITHUB_SHA" | cut -c1-8)
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8_CUT}/${SUITE}" URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8_CUT}/${SUITE}"
else else
URL="${GITEA_API_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" URL="${GITEA_API_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
fi fi
+21 -4
View File
@@ -368,9 +368,18 @@ Pakkaa ja pushee Helm-chartin OCI-registryyn. Käyttää `alpine/helm`-konttia.
```yaml ```yaml
HELM_REGISTRY: gitea.app.keskikuja.site/niko HELM_REGISTRY: gitea.app.keskikuja.site/niko
VERSION_FILE: platform-helm/Chart.yaml # chart-hakemisto + versionlähde 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ä:** **Käyttö reitittimessä:**
```yaml ```yaml
@@ -382,11 +391,19 @@ helm-build-push:
with: with:
env_json: ${{ needs.load-config.outputs.env_json }} env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }} version: ${{ needs.check-version.outputs.version }}
chart_path: platform-helm
# extra_dependency_paths: subchart-a,subchart-b # tarvittaessa
``` ```
Chart-hakemisto johdetaan `VERSION_FILE`-polusta: `dirname "${VERSION_FILE}"`. `chart_path` on eksplisiittinen polku chart-hakemistoon (esim. `platform-helm`).
Jos `VERSION_FILE` on `Chart.yaml`, konteksti on juuri. Jos `platform-helm/Chart.yaml`, `VERSION_FILE` määrää version lähteen (`Chart.yaml:n` `version`-kenttä) —
konteksti on `platform-helm/`. 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 **Yksittäisten Helm-UI-linkkien raportointi:** `HELM_UI_URL` on
tarkoitettu yleiselle registry UI:lle — provider muodostaa linkin tarkoitettu yleiselle registry UI:lle — provider muodostaa linkin
+139 -298
View File
@@ -1,10 +1,9 @@
--- ---
name: gitops-update name: gitops-update
description: | description: |
Setting up GitOps version updates: GitOps-repo workflow template, code Getting GitOps configuration updates working for a consumer project —
repo dispatch, secret requirements, and two-repo commit-status pattern. GitOps repo setup, consumer pipeline wiring, secrets, and commit-status
Activates when the user needs to wire up artifact builds to GitOps output.
configuration updates.
activation-gate: | activation-gate: |
User mentions GitOps update, gitops-update, dispatch to another repo, User mentions GitOps update, gitops-update, dispatch to another repo,
two-repo version bump, cross-repo deployment, or wiring build output to two-repo version bump, cross-repo deployment, or wiring build output to
@@ -13,47 +12,26 @@ category: ci
impact: high impact: high
--- ---
# GitOps Update — Provider-palvelu # GitOps Update — consumer setup
`scripts/gitops-update.sh` ja `scripts/dispatch-workflow.sh` muodostavat ## What you need
GitOps-päivityspalvelun. Artifact buildataan code repossa, minkä jälkeen
code repo dispatchaa GitOps-repoon, joka päivittää konfiguraatiotiedoston
ja pushaa muutoksen.
## Arkkitehtuuri - **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
Kaksi erillistä repoa, eristetyt oikeudet: Two repos, isolated access. The consumer never writes to GitOps directly;
it dispatches a workflow that clones, updates, commits, and pushes.
``` ---
Code repo GitOps repo
(build & push artifact) (konfiguraatiot)
build & push onnistuu (v0.2.3) ## 1. GitOps-repo setup
│ dispatch ci-main.yml
│ {file, yq_tpl, version, source_repo, source_commit}
└────────────────────────────────────→┐
dispatch-workflow.sh pollaa ←─────────┘
code repo asettaa │ git clone, yq update,
oman commit-statusnsa │ git commit + push
dispatchin exit-koodilla │ status GitOps-repoon
```
**Token-periaate:** Vain GitOps-repoon kirjoitetaan. Code repo asettaa Create `.gitea/workflows/gitops-service.yaml`:
oman commit-statusnsa dispatch-kutsun exit-koodin perusteella omalla
auto-tokenillaan. GitOps-repon auto-token ei tarvitse oikeuksia code
repoon.
## GitOps-repon workflow (ci-main.yml)
GitOps-repoon luodaan `.gitea/workflows/ci-main.yml`:
```yaml ```yaml
name: GitOps Update name: GitOps Update
run-name: "GitOps Service (${{ inputs.dispatch_id || 'manual' }})" run-name: "GitOps (${{ inputs.dispatch_id || 'manual' }})"
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
@@ -78,6 +56,15 @@ on:
git_tag_prefix: git_tag_prefix:
required: false required: false
type: string type: string
extra_cmd:
required: false
type: string
author_name:
required: false
type: string
author_email:
required: false
type: string
env: env:
INPUT_FILE: ${{ inputs.file }} INPUT_FILE: ${{ inputs.file }}
@@ -86,27 +73,26 @@ env:
SOURCE_REPO: ${{ inputs.source_repo }} SOURCE_REPO: ${{ inputs.source_repo }}
SOURCE_COMMIT: ${{ inputs.source_commit }} SOURCE_COMMIT: ${{ inputs.source_commit }}
GITOPS_REPO: ${{ github.repository }} GITOPS_REPO: ${{ github.repository }}
GITOPS_BRANCH: ${{ github.ref_name }}
GITEA_API_URL: ${{ gitea.server_url }} GITEA_API_URL: ${{ gitea.server_url }}
GIT_TAG_PREFIX: ${{ inputs.git_tag_prefix || '' }} 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: jobs:
update: update:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
repository: niko/gitea-ci-library repository: niko/gitea-ci-library
path: .ci path: .ci
- name: Install yq - name: Install yq
run: | run: |
wget -qO /usr/local/bin/yq \ wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq chmod +x /usr/local/bin/yq
- name: Run GitOps update - name: Run GitOps update
env: env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
@@ -114,297 +100,152 @@ jobs:
bash .ci/scripts/gitops-update.sh bash .ci/scripts/gitops-update.sh
``` ```
**Huomiot:** > **⚠️ yq ladataan lennossa.** Tämä on väliaikainen kompromissi. Myöhemmin
- `GITEA_TOKEN` on Gitean auto-token — scopeutuu GitOps-repoon, riittää > julkaistaan Docker Hubiin custom CI-kontti, jossa nodejs + git + yq
cloneen, committiin, pushiin ja commit-statusiin GitOps-repossa > valmiina. Sama patterni kuin `ci-bats` ja `ci-cucumber`.
- `run-name` ja `dispatch_id` mahdollistavat dispatchaavan skriptin tunnistaa > Ks. `skills/ci-container-build/SKILL.md`.
tämän workflow-runin yksiselitteisesti `display_title`-kentästä, vaikka
samassa repossa olisi samanaikaisia ajoja
- yq ladataan lennossa (kompromissi, ks. "Tuleva CI-kontti")
### Tulossa: custom CI-kontti 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
Nykyinen job lataa yq:n lennossa. Myöhemmin rakennetaan oma kontti ---
(`ci-gitops`), jossa on nodejs + git + yq valmiina. Sama patterni kuin
`ci-bats` ja `ci-cucumber`. Ks. `skills/ci-container-build/SKILL.md`.
## Code-repon dispatch-step ## 2. Consumer-repo setup
Code repo dispatchaa GitOps-repon workflown artifact buildin onnistuttua: ### 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 ```yaml
gitops-update: gitops-update:
needs: [helm-build-push] needs: [build-push]
if: success() if: success()
runs-on: ubuntu-latest uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@v1
steps: secrets: inherit
- uses: actions/checkout@v4 with:
env_json: ${{ needs.load-config.outputs.env_json }}
- uses: actions/checkout@v4 version: ${{ needs.version.outputs.version }}
with: GITOPS_FILE: dev/Chart.yaml
repository: niko/gitea-ci-library GITOPS_YQ_TPL: '.version = "{{VERSION}}"'
path: .ci GITOPS_REPO: niko/your-gitops-repo
- name: Dispatch GitOps update
id: dispatch
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
INPUTS=$(jq -nc \
--arg file "dev/Chart.yaml" \
--arg yq_tpl '(.dependencies[] | select(.name == "agent-platform-helm") | .version) = "{{VERSION}}"' \
--arg version "${{ needs.check-version.outputs.version }}" \
--arg source_repo "${{ github.repository }}" \
--arg source_commit "${{ github.sha }}" \
'{file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit}')
OUTPUT=$(bash .ci/scripts/dispatch-workflow.sh \
"niko/agent-platform-gitops" \
"ci-main.yml" \
"main" \
"$INPUTS" \
"${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }}" \
"${{ secrets.GITEA_TOKEN }}" \
"30")
echo "$OUTPUT"
GITOPS_COMMIT=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2)
echo "gitops_commit=$GITOPS_COMMIT" >> "$GITHUB_OUTPUT"
``` ```
### Multi-artifact pipeline (kontti + helm) This single job handles: dispatch → poll → find commit SHA → set commit-status on your commit → produce `GITOPS_SUMMARY` output.
Yksi main-haaran build tuottaa usein sekä Docker-imagen että Helm-chartin. To run extra commands (e.g. `helm dependency update`) after the version bump and before the commit:
Kumpikin artefakti dispatchaa oman GitOps-päivityksensä rinnakkain:
```yaml ```yaml
gitops-helm: gitops-update:
needs: [helm-build-push] needs: [load-config, check-version, helm-build-push]
if: success() if: success()
runs-on: ubuntu-latest uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@v1
steps: secrets: inherit
- uses: actions/checkout@v4 with:
- uses: actions/checkout@v4 env_json: ${{ needs.load-config.outputs.env_json }}
with: version: ${{ needs.check-version.outputs.version }}
repository: niko/gitea-ci-library GITOPS_FILE: Chart.yaml
path: .ci GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "agent-platform-helm") | .version) = "{{VERSION}}"'
- name: Update helm version GITOPS_REPO: niko/agent-platform-gitops
id: helm GITOPS_EXTRA_CMD: helm dependency update
run: |
INPUTS=$(jq -nc \
--arg file "dev/Chart.yaml" \
--arg yq_tpl '(.dependencies[] | select(.name == "git-pages") | .version) = "{{VERSION}}"' \
--arg version "${{ needs.check-version.outputs.version }}" \
--arg source_repo "${{ github.repository }}" \
--arg source_commit "${{ github.sha }}" \
--arg git_tag_prefix "helm" \
'{dispatch_id: "", file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_tag_prefix}')
OUTPUT=$(bash .ci/scripts/dispatch-workflow.sh \
"niko/gitea-ci-gitops-tests" "gitops-service.yaml" "main" \
"$INPUTS" "${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }}" \
"${{ secrets.GITOPS_DISPATCH_TOKEN }}" "30")
echo "$OUTPUT"
echo "helm_commit=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2)" >> "$GITHUB_OUTPUT"
gitops-docker:
needs: [docker-build-push]
if: success()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Update docker tag
id: docker
run: |
INPUTS=$(jq -nc \
--arg file "dev/values.yaml" \
--arg yq_tpl '.service.tag = "{{VERSION}}"' \
--arg version "${{ needs.check-version.outputs.version }}" \
--arg source_repo "${{ github.repository }}" \
--arg source_commit "${{ github.sha }}" \
--arg git_tag_prefix "docker" \
'{dispatch_id: "", file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_tag_prefix}')
OUTPUT=$(bash .ci/scripts/dispatch-workflow.sh \
"niko/gitea-ci-gitops-tests" "gitops-service.yaml" "main" \
"$INPUTS" "${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }}" \
"${{ secrets.GITOPS_DISPATCH_TOKEN }}" "30")
echo "$OUTPUT"
echo "docker_commit=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2)" >> "$GITHUB_OUTPUT"
``` ```
Kaksi dispatchia, kaksi eri tiedostoa, kaksi eri `GIT_TAG_PREFIX`-arvoa. 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.
Kummallakin on oma commit-status-linja ja oma summary-rivi.
`dispatch-workflow.sh` hoitaa rinnakkaisuuden `display_title`-matchauksella.
**GITEA_TOKEN dispatch-vaiheessa:** Tarvitaan manuaalinen token, 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.
jolla on **write-oikeus GitOps-repoon** (esim. org-tason token).
Code-repon auto-token ei oikeuta dispatchaamaan toiseen repoon.
Token luodaan Giteassa: `Settings → Applications → Generate Token`
ja asetetaan code-repoon Actions Secretiksi.
### Commit-status dispatchin perusteella ### 2.3 Parameters
`dispatch-workflow.sh` tulostaa `GITOPS_COMMIT=<sha>` stdoutiin onnistuneen | Input | Required | Description |
GitOps-päivityksen jälkeen. Code repo parsii sen ja asettaa commit-statusin |---|---|---|---|
linkillä GitOps-committiin: | `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 ```yaml
- name: Set commit-status with GitOps link report-summary:
if: always() needs: [load-config, gitops-update]
env: if: always()
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} uses: niko/gitea-ci-library/.gitea/workflows/report-summary.yml@main
GITEA_API_URL: ${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }} with:
GITOPS_COMMIT: ${{ steps.dispatch.outputs.gitops_commit }} env_json: ${{ needs.load-config.outputs.env_json }}
VERSION: ${{ needs.check-version.outputs.version }} suites: bats cucumber
run: | gitops: ${{ needs.gitops-update.outputs.summary }}
GITOPS_URL="${GITEA_API_URL}/niko/agent-platform-gitops/commit/${GITOPS_COMMIT}"
CTX="gitops/$(basename ${{ github.repository }})"
DESC="Deploy to dev ${VERSION}"
if [ -n "$GITOPS_COMMIT" ]; then
bash .ci/scripts/report-status.sh success "$DESC" "$CTX" "" "$GITOPS_URL"
else
bash .ci/scripts/report-status.sh success "$DESC" "$CTX"
fi
``` ```
`dispatch-workflow.sh` palauttaa: ---
- exit 0 = GitOps-päivitys onnistui (+ `GITOPS_COMMIT=<sha>`)
- exit 1 = GitOps-päivitys failasi
- exit 124 = aikakatkaisu (360 min oletus)
### Loppuraportti (report-summary) ## 3. Token summary
Code-repon viimeinen job (`report-summary`) lisää GitOps-päivityksestä | Token | Where | Scope | Purpose |
rivin GITHUB_STEP_SUMMARYyn: |---|---|---|---|
| `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 |
```yaml ---
- name: GitOps summary
if: always()
env:
GITEA_API_URL: ${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }}
GITOPS_COMMIT: ${{ steps.dispatch.outputs.gitops_commit }}
VERSION: ${{ needs.check-version.outputs.version }}
run: |
if [ -n "$GITOPS_COMMIT" ]; then
LINK="${GITEA_API_URL}/niko/agent-platform-gitops/commit/${GITOPS_COMMIT}"
else
LINK="#"
fi
cat >> "$GITHUB_STEP_SUMMARY" << 'GITOPS'
## GitOps updates ## 4. What happens at runtime
| Component | Version | Status | Commit | 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
| agent-platform-helm | __VERSION__ | __STATUS__ | [link](__LINK__) | 3. Consumer polls the GitOps repo's runs until the workflow completes
GITOPS 4. Consumer lists recent commits and finds the matching one by commit message `"gitops: update version to X.Y.Z"`
sed -i "s|__VERSION__|${VERSION}|; s|__STATUS__|${{ job.status }}|; s|__LINK__|${LINK}|" \ 5. Consumer sets commit-status `gitops/{repo}[/{prefix}]` on its own commit with a link to the exact GitOps commit
"$GITHUB_STEP_SUMMARY" 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/
``` ```
## Secretit ja tokenit Each component gets its own commit-status context:
| Secret | Missä | Scope | Kuvaus | | Prefix | Context |
|--------|-------|-------|--------| |---|---|
| `GITEA_TOKEN` (auto) | Code repo | Vain code repo | Asettaa commit-statusin dispatchin jälkeen | | (empty) | `gitops/agent-platform` |
| `GITEA_TOKEN` (auto) | GitOps repo | Vain GitOps repo | Klooni, push, commit-status GitOps-repossa | | `docker/` | `gitops/agent-platform/docker` |
| `GITOPS_DISPATCH_TOKEN` (manuaalinen) | Code repo | Write GitOps-repoon | Dispatchaa GitOps-repon workflow | | `helm/` | `gitops/agent-platform/helm` |
**Tokenin luonti:** This prevents status overwrites between parallel dispatch jobs.
1. Gitea → `Settings``Applications``Generate Token`
2. Valitse repo-oikeudet: valitse GitOps-repo, anna write-oikeudet
3. Token asetetaan code-repoon: `{repo} → Settings → Actions Secrets`
4. Salaisuuden nimi: esim. `GITOPS_DISPATCH_TOKEN`
## Provider-skriptit ---
### `scripts/gitops-update.sh` ## 6. What you do NOT need to know
Ajaan GitOps-repon workflow'ssa. Päivittää konfiguraatiotiedoston yq:llä, - How `gitops-update.sh` works internally
committaa ja pushaa. Asettaa commit-statuksen vain GitOps-repoon. - How the polling finds the run
- How the commit SHA is extracted
- Race condition handling
- CI container plans
**Input-ympäristömuuttujat:** All of that is handled by `gitops-dispatch.yml`. You just call it.
| 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ä | Code-repo slug (esim. `org/app`) |
| `SOURCE_COMMIT` | Kyllä | Code-repon 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 | Branch (oletus `main`) |
| `GIT_TAG_PREFIX` | Ei | Komponentin tag-prefix status-nimeämiseen (esim. `agent-platform-helm`) |
| `GITOPS_CLONE_URL` | Ei | Yliajaa clone-URL (esim. eri protokolla) |
| `GITOPS_TARGET_DIR` | Ei | Yliajaa clone-kohdehakemisto |
**Commit-status muoto:**
GitOps-repoon asetetaan commit-status:
| Kenttä | Formaatti | Esimerkki |
|--------|-----------|-----------|
| Context | `{repo}/{GIT_TAG_PREFIX} {RUN_ID}` tai `{repo} {RUN_ID}` | `gitea-ci-library/agent-platform-helm 473` |
| Description | `Install to {env} {version}` | `Install to dev 0.2.0` |
| Target URL | Linkki code-repon committiin | `/niko/gitea-ci-library/commit/abc123` |
Jos tiedosto on jo halutussa versiossa (ei muutoksia), status saa descriptionin `Install to {env} {version} — no change`. Commit-pushia ei tehdä, GitOps-repo pysyy muuttumattomana.
- `{env}` parsitaan `INPUT_FILE`:stä (`dev/Chart.yaml``dev`)
- `{repo}` parsitaan `SOURCE_REPO`:sta (`niko/gitea-ci-library``gitea-ci-library`)
- `{GIT_TAG_PREFIX}` tulee env-varista (sama kuin `gitea-env.conf`:ssa)
### `scripts/dispatch-workflow.sh`
Dispatchaa workflow_dispatchin kohderepoon ja pollaa valmistumista.
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 dispatch-attribuutioita.
**Argumentit:**
| # | Pakollinen | Kuvaus |
|---|------------|--------|
| 1 | Kyllä | Kohderepo (esim. `niko/agent-platform-gitops`) |
| 2 | Kyllä | Workflow-tiedosto (esim. `ci-main.yml`) |
| 3 | Kyllä | Branch/ref |
| 4 | Kyllä | Inputs JSON |
| 5 | Kyllä | Gitea API URL |
| 6 | Kyllä | Gitea token |
| 7 | Ei | Aikakatkaisu minuutteina (oletus 360) |
Kutsujan ei tarvitse välittää `dispatch_id`:tä — skripti generoi sen
itse ja lisää inputteihin ennen dispatchia.
## [skip ci]
Commit-viestissä on `[skip ci]`, joka estää GitActions-runneria
triggeröimästä uutta CI-ajoa GitOps-repoon pushista. Näin vältetään
ääretön trigger-loop.
## Race condition
`dispatch-workflow.sh` tunnistaa jokaisen dispatchatun runin uniikilla
`dispatch_id`-tunnisteella `display_title`-kentästä. Vaikka useampi
artifakti dispatchaisi samaan aikaan ja useita workflow-runeja olisi
käynnissä rinnakkain, jokainen skripti löytää oikean runinsa.
## Sääntöjä
1. **Token ei kirjoita code repoon.** GitOps-repon workflow ei tarvitse
oikeuksia code repoon. Kaikki status-kutsut kohdistuvat vain
GitOps-repoon. Code repo asettaa oman statusnsa itse.
2. **Ei provider-workflowta.** GitOps-päivitys ei ole reusable workflow.
GitOps-repo ajaa `scripts/gitops-update.sh`:n suoraan.
3. **Vain `workflow_dispatch`.** GitOps-repon workflow:ta ei triggeröidä
pushista — se laukeaa vain dispatch-kutsusta.
4. **Dispatch ei palauta tarkkaa SHA:**ta. Code repo ei tiedä GitOps-
commitin SHA:ta ennen dispatch-valmistumista. Status asetetaan
dispatchin exit-koodin perusteella, ei GitOps-commitin tiedoilla.
5. **`dispatch_id` on pakollinen kohde-workflow'ssa** — ilman sitä
`dispatch-workflow.sh` ei löydä oikeaa runia moniajo-tilanteessa.
6. **`[skip ci]` commit-viestissä.** Pakollinen trigger-loopin estoon.