Compare commits

..

11 Commits

Author SHA1 Message Date
niko f3abd42f17 Fix/git pages gitea reports 1 (#49)
CI Main / Config load (push) Successful in 23s
CI Gitea Reports Main / Config load (push) Successful in 23s
CI Gitea Reports Main / Latest version (push) Successful in 20s
CI Main / Latest versio (push) Successful in 21s
ci-helm-build-push Helm push 0.2.1
CI Gitea Reports Main / Build & Push Helm chart (push) Successful in 41s
unit-tests Bats test report
acc-tests Cucumber test report
CI Gitea Reports Main / Report Summary (push) Successful in 3s
ci-docker-build-push Docker push 0.2.36
CI Main / Build & Push Docker (push) Successful in 32s
CI Main / Report Summary (push) Successful in 3s
CI Main / Move provider version tag (push) Successful in 10s
gitops/gitea-ci-library/gitea-reports GitOps: gitea-reports 0.2.1
CI Gitea Reports Main / GitOps (push) Successful in 33s
CI Main / Cucumber tests (push) Successful in 1m25s
CI Main / Bats tests (push) Successful in 1m26s
gitops/gitea-ci-library GitOps: 0.2.36
CI Main / GitOps (push) Successful in 32s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #49
2026-06-28 09:25:41 +03:00
niko bd6ed5c2c2 Feature/git pages -> oma nginx perusteinen raporointipalvelu (#48)
CI Gitea Reports Main / Config load (push) Successful in 17s
CI Main / Config load (push) Successful in 20s
CI Gitea Reports Main / Latest version (push) Successful in 17s
CI Main / Latest versio (push) Successful in 19s
ci-helm-build-push Helm push 0.2.0
CI Gitea Reports Main / Build & Push Helm chart (push) Successful in 33s
gitops/gitea-ci-library/gitea-reports GitOps: gitea-reports 0.2.0
CI Gitea Reports Main / GitOps (push) Successful in 33s
CI Gitea Reports Main / Report Summary (push) Successful in 3s
CI Main / Bats tests (push) Failing after 1m16s
CI Main / Cucumber tests (push) Failing after 1m22s
CI Main / Build & Push Docker (push) Has been skipped
CI Main / GitOps (push) Has been skipped
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 3s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #48
2026-06-28 09:10:10 +03:00
niko dd9cdf70b6 Featuer/gitops extra command (#47)
CI Main / Config load (push) Successful in 1m48s
CI Main / Latest versio (push) Successful in 1m38s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 2m15s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 2m46s
ci-docker-build-push Docker push 0.2.35
CI Main / Build & Push Docker (push) Successful in 40s
gitops/gitea-ci-library GitOps: 0.2.35
CI Main / GitOps (push) Successful in 1m34s
CI Main / Report Summary (push) Successful in 3s
CI Main / Move provider version tag (push) Successful in 11s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #47
2026-06-27 05:20:09 +03:00
niko 848ba723e4 Fix/git pages rentetin create tests (#46)
CI Main / Config load (push) Successful in 3m12s
CI Git-Pages Main / Config load (push) Successful in 3m4s
CI Main / Latest versio (push) Successful in 30s
CI Git-Pages Main / Latest version (push) Successful in 27s
ci-helm-build-push Helm push 0.1.9
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m56s
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 1m19s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 3m47s
ci-docker-build-push Docker push 0.2.34
CI Main / Build & Push Docker (push) Successful in 1m14s
gitops/gitea-ci-library/git-pages GitOps: git-pages 0.1.9
CI Git-Pages Main / GitOps (push) Successful in 3m37s
gitops/gitea-ci-library GitOps: 0.2.34
CI Main / GitOps (push) Successful in 40s
CI Main / Report Summary (push) Successful in 4s
CI Main / Move provider version tag (push) Successful in 12s
CI Git-Pages Main / Report Summary (push) Successful in 4s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #46
2026-06-26 08:07:02 +03:00
niko 1978a995a8 Update README.md (#45)
CI Main / Config load (push) Successful in 23s
CI Main / Latest versio (push) Successful in 21s
CI Main / Bats tests (push) Successful in 1m34s
CI Main / Cucumber tests (push) Successful in 1m33s
ci-docker-build-push Docker push 0.2.33
CI Main / Build & Push Docker (push) Successful in 50s
gitops/gitea-ci-library GitOps: 0.2.33
CI Main / GitOps (push) Successful in 47s
CI Main / Report Summary (push) Successful in 7s
CI Main / Move provider version tag (push) Successful in 14s
CI Feature / Load example-gitea-env.conf to pipeline env (push) Successful in 1m5s
unit-tests Bats test report
CI Feature / Bats tests (push) Successful in 2m9s
acc-tests Cucumber test report
CI Feature / Cucumber tests (push) Successful in 2m53s
CI Feature / Report Summary (push) Successful in 7s
Reviewed-on: #45
2026-06-25 09:19:01 +03:00
niko d6343438a3 Feture/gitops7 (#44)
unit-tests Bats test report
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m44s
ci-docker-build-push Docker push 0.2.32
gitops/gitea-ci-library GitOps: 0.2.32
CI Main / Config load (push) Successful in 26s
CI Main / Latest versio (push) Successful in 21s
CI Main / Build & Push Docker (push) Has been skipped
CI Main / GitOps (push) Has been skipped
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 5s
CI Main / Bats tests (push) Has been skipped
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #44
2026-06-23 12:45:42 +03:00
niko ed2703b7d7 commit status kutsujalle gitops repoon (#43)
CI Main / Config load (push) Successful in 19s
CI Main / Latest versio (push) Successful in 19s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m33s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m45s
ci-docker-build-push Docker push 0.2.31
CI Main / Build & Push Docker (push) Successful in 44s
gitops/gitea-ci-library GitOps: 0.2.31
CI Main / GitOps (push) Successful in 35s
CI Main / Report Summary (push) Successful in 7s
CI Main / Move provider version tag (push) Successful in 13s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #43
2026-06-22 14:24:25 +03:00
niko dc4b331ea1 kutsujalle gitops commit (#42)
CI Main / Config load (push) Successful in 26s
CI Git-Pages Main / Latest version (push) Successful in 22s
CI Git-Pages Main / Config load (push) Successful in 25s
CI Main / Latest versio (push) Successful in 21s
ci-helm-build-push Helm push 0.1.8
CI Main / Bats tests (push) Successful in 1m38s
CI Git-Pages Main / GitOps (push) Successful in 1m24s
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 46s
unit-tests Bats test report
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m59s
CI Git-Pages Main / Report Summary (push) Successful in 9s
ci-docker-build-push Docker push 0.2.30
CI Main / Build & Push Docker (push) Successful in 50s
CI Main / Move provider version tag (push) Successful in 14s
CI Main / GitOps (push) Successful in 41s
CI Main / Report Summary (push) Successful in 6s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #42
2026-06-22 13:57:14 +03:00
niko c06015cd9f poc logiikka takaisin (#41)
CI Main / Config load (push) Successful in 19s
CI Main / Latest versio (push) Successful in 17s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m30s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m43s
ci-docker-build-push Docker push 0.2.29
CI Main / Build & Push Docker (push) Successful in 44s
CI Main / GitOps (push) Successful in 39s
CI Main / Report Summary (push) Successful in 6s
CI Main / Move provider version tag (push) Successful in 15s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #41
2026-06-22 13:24:09 +03:00
niko 4c73433eab Update scripts/gitops-dispatch.sh (#40)
CI Main / Config load (push) Successful in 21s
CI Main / Latest versio (push) Successful in 19s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m23s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m43s
ci-docker-build-push Docker push 0.2.28
CI Main / Build & Push Docker (push) Successful in 33s
CI Main / GitOps (push) Failing after 18s
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 7s
Reviewed-on: #40
2026-06-22 11:19:56 +03:00
niko b9de1da855 testiä (#39)
ci-helm-build-push Helm push 0.1.7
CI Git-Pages Main / Build & Push Helm chart (push) Successful in 51s
CI Git-Pages Main / Report Summary (push) Successful in 6s
unit-tests Bats test report
CI Main / Bats tests (push) Successful in 1m40s
acc-tests Cucumber test report
CI Main / Cucumber tests (push) Successful in 1m48s
CI Git-Pages Main / Config load (push) Successful in 22s
CI Main / Config load (push) Successful in 24s
CI Main / Latest versio (push) Successful in 25s
CI Git-Pages Main / Latest version (push) Successful in 24s
CI Git-Pages Main / GitOps (push) Failing after 20s
ci-docker-build-push Docker push 0.2.27
CI Main / Build & Push Docker (push) Successful in 41s
CI Main / GitOps (push) Failing after 18s
CI Main / Move provider version tag (push) Has been skipped
CI Main / Report Summary (push) Successful in 6s
Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #39
2026-06-22 11:14:50 +03:00
72 changed files with 2136 additions and 1941 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ on:
jobs:
build-push:
runs-on: ubuntu-latest
runs-on: docker
steps:
- uses: actions/checkout@v4
+2 -2
View File
@@ -8,7 +8,7 @@ on:
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
REPORTS_PUBLISH_TOKEN:
required: true
outputs:
env_json:
@@ -19,7 +19,7 @@ on:
env:
CI_CONF_FILE: ${{ inputs.config_path }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }}
jobs:
parse-config:
+2 -2
View File
@@ -32,7 +32,7 @@ concurrency:
jobs:
build-push:
runs-on: ubuntu-latest
runs-on: docker
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
@@ -91,7 +91,7 @@ jobs:
fi
tag-commit:
runs-on: ubuntu-latest
runs-on: docker
needs: [build-push]
steps:
- uses: actions/checkout@v4
+2 -2
View File
@@ -12,14 +12,14 @@ on:
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
REPORTS_PUBLISH_TOKEN:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }}
jobs:
bats:
+2 -2
View File
@@ -12,14 +12,14 @@ on:
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
REPORTS_PUBLISH_TOKEN:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
REPORTS_PUBLISH_TOKEN: ${{ secrets.REPORTS_PUBLISH_TOKEN }}
jobs:
cucumber:
@@ -1,11 +1,11 @@
name: CI Git-Pages Main
name: CI Gitea Reports Main
on:
push:
branches:
- main
paths:
- git-pages/**
- .gitea/workflows/git-pages.*
- gitea-reports/**
- ".gitea/workflows/gitea-reports.*"
workflow_dispatch:
jobs:
@@ -14,7 +14,7 @@ jobs:
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: .gitea/workflows/git-pages.gitea-env.conf
config_path: .gitea/workflows/gitea-reports.gitea-env.conf
check-version:
name: Latest version
@@ -33,7 +33,7 @@ jobs:
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
chart_path: git-pages
chart_path: gitea-reports
chart-gitops:
name: GitOps
@@ -44,12 +44,12 @@ jobs:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
GITOPS_FILE: dev/Chart.yaml
GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "git-pages") | .version) = "{{VERSION}}"'
GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "gitea-reports") | .version) = "{{VERSION}}"'
GITOPS_REPO: niko/gitea-ci-gitops-tests
report-summary:
name: Report Summary
needs: [load-config, helm-push, chart-gitops]
needs: [load-config, check-version, helm-push, chart-gitops]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
@@ -1,6 +1,5 @@
GITEA_API_URL=https://gitea.app.keskikuja.site
HELM_REGISTRY=gitea.app.keskikuja.site/niko
HELM_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container
GIT_TAG_PREFIX=git-pages/
VERSION_FILE=git-pages/Chart.yaml
GIT_TAG_PREFIX=gitea-reports/
VERSION_FILE=gitea-reports/Chart.yaml
+153 -9
View File
@@ -17,12 +17,15 @@ on:
GITOPS_REPO:
required: true
type: string
GITOPS_EXTRA_CMD:
required: false
type: string
secrets:
GITOPS_DISPATCH_TOKEN:
required: true
outputs:
summary:
description: 'Pipe-format: component|version|status|commit_sha|repo'
description: "Pipe-format: component|version|status|commit_sha|repo"
value: ${{ jobs.dispatch.outputs.summary }}
env:
@@ -34,25 +37,166 @@ env:
GITOPS_SOURCE_COMMIT: ${{ github.sha }}
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GITOPS_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
GITOPS_EXTRA_CMD: ${{ inputs.GITOPS_EXTRA_CMD || '' }}
GITOPS_WORKFLOW: gitops-service.yaml
GITOPS_DISPATCH_TIMEOUT: 30
jobs:
dispatch:
runs-on: ubuntu-latest
outputs:
summary: ${{ steps.run.outputs.GITOPS_SUMMARY }}
summary: ${{ steps.summary.outputs.GITOPS_SUMMARY }}
steps:
- uses: actions/checkout@v4
- name: Generate dispatch_id
id: gen
run: |
ID=$(date +%s | md5sum | head -c 8)
echo "dispatch_id=$ID" >> "$GITHUB_OUTPUT"
- name: Resolve commit author
id: author
run: |
echo "name=$(git log -1 --format='%an')" >> "$GITHUB_OUTPUT"
echo "email=$(git log -1 --format='%ae')" >> "$GITHUB_OUTPUT"
- name: Dispatch to GitOps repo
env:
GITEA_TOKEN: ${{ secrets.GITOPS_DISPATCH_TOKEN }}
run: |
INPUTS=$(jq -nc \
--arg dispatch_id "${{ steps.gen.outputs.dispatch_id }}" \
--arg author_name "${{ steps.author.outputs.name }}" \
--arg author_email "${{ steps.author.outputs.email }}" \
--arg file "$GITOPS_FILE" \
--arg yq_tpl "$GITOPS_YQ_TPL" \
--arg version "$GITOPS_VERSION" \
--arg source_repo "$GITOPS_SOURCE_REPO" \
--arg source_commit "$GITOPS_SOURCE_COMMIT" \
--arg git_tag_prefix "${GITOPS_TAG_PREFIX:-}" \
--arg extra_cmd "${GITOPS_EXTRA_CMD:-}" \
'{dispatch_id: $dispatch_id, author_name: $author_name, author_email: $author_email, file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_tag_prefix, extra_cmd: $extra_cmd}')
curl -s -X POST \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/actions/workflows/${GITOPS_WORKFLOW}/dispatches" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -nc --arg ref "main" --argjson inputs "$INPUTS" '{ref: "main", inputs: $inputs}')"
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Run gitops dispatch
id: run
- name: Poll for completion
id: poll
env:
GITEA_TOKEN: ${{ secrets.GITOPS_DISPATCH_TOKEN }}
run: |
OUTPUT=$(bash .ci/scripts/gitops-dispatch.sh)
echo "$OUTPUT"
SUMMARY=$(awk -F= '/^GITOPS_SUMMARY=/ {print $2}' <<<"$OUTPUT")
echo "GITOPS_SUMMARY=$SUMMARY" >> "$GITHUB_OUTPUT"
ID="${{ steps.gen.outputs.dispatch_id }}"
TIMEOUT_MINUTES="${GITOPS_DISPATCH_TIMEOUT:-30}"
POLL_INTERVAL=10
START_TIME=$(date +%s)
TIMEOUT_SECONDS=$((TIMEOUT_MINUTES * 60))
echo "Polling for run with dispatch_id=$ID"
while [ -z "$RUN_ID" ]; do
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
if [ "$ELAPSED" -ge "$TIMEOUT_SECONDS" ]; then
echo "ERROR: Timeout waiting for run to appear" >&2
exit 124
fi
RUNS_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/actions/runs?event=workflow_dispatch&limit=10" \
-H "Authorization: token $GITEA_TOKEN")
RUN_ID=$(echo "$RUNS_RESP" | jq -r --arg id "$ID" \
'[.workflow_runs[] | select(.display_title | contains($id))] | .[0].id // empty')
[ -z "$RUN_ID" ] && sleep "$POLL_INTERVAL"
done
echo "Run found: id=$RUN_ID"
while true; do
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
if [ "$ELAPSED" -ge "$TIMEOUT_SECONDS" ]; then
echo "ERROR: Timeout waiting for completion" >&2
exit 124
fi
RUN_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/actions/runs/${RUN_ID}" \
-H "Authorization: token $GITEA_TOKEN")
STATUS=$(echo "$RUN_RESP" | jq -r '.status // "running"')
CONCLUSION=$(echo "$RUN_RESP" | jq -r '.conclusion // ""')
echo " status=$STATUS conclusion=$CONCLUSION"
if [ "$STATUS" = "completed" ]; then
if [ "$CONCLUSION" = "success" ]; then
echo "GitOps workflow completed successfully"
# 1. List recent commits from GitOps repo
COMMITS=$(curl -s --connect-timeout 5 --max-time 10 \
"${GITEA_API_URL}/api/v1/repos/${GITOPS_REPO}/commits?sha=main&limit=10" \
-H "Authorization: token $GITEA_TOKEN")
# 2. Find commit by message: "gitops: update version to X.Y.Z"
SEARCH_MSG="gitops: update version to ${GITOPS_VERSION}"
GITOPS_COMMIT=$(echo "$COMMITS" | jq -r \
--arg msg "$SEARCH_MSG" \
'[.[] | select(.commit.message | contains($msg))] | .[0].sha // empty')
# 3. If not found → fail
if [ -z "$GITOPS_COMMIT" ]; then
echo "ERROR: no matching GitOps commit found for version ${GITOPS_VERSION}" >&2
exit 1
fi
echo "GITOPS_COMMIT=$GITOPS_COMMIT" >> "$GITHUB_OUTPUT"
echo "$GITOPS_COMMIT" > /tmp/gitops-commit
exit 0
else
echo "ERROR: GitOps workflow failed with conclusion=$CONCLUSION" >&2
exit 1
fi
fi
sleep "$POLL_INTERVAL"
done
- name: GitOps summary
id: summary
if: always()
run: |
STATUS="failure"
GITOPS_SHA=""
if [ -f /tmp/gitops-commit ]; then
STATUS="success"
GITOPS_SHA=$(cat /tmp/gitops-commit)
fi
COMPONENT="${GITOPS_TAG_PREFIX:-${GITOPS_FILE}}"
echo "GITOPS_SUMMARY=${COMPONENT}|${GITOPS_VERSION}|${STATUS}|${GITOPS_SHA}|${GITOPS_REPO}" >> "$GITHUB_OUTPUT"
- name: Set commit status
if: success()
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
GITOPS_SHA=$(cat /tmp/gitops-commit)
PREFIX="${GITOPS_TAG_PREFIX%/}"
if [ -n "$PREFIX" ]; then
CONTEXT="gitops/$(basename "${GITOPS_SOURCE_REPO}")/${PREFIX}"
DESCRIPTION="GitOps: ${PREFIX} ${GITOPS_VERSION}"
else
CONTEXT="gitops/$(basename "${GITOPS_SOURCE_REPO}")"
DESCRIPTION="GitOps: ${GITOPS_VERSION}"
fi
ROOT_REPO="${GITOPS_SOURCE_REPO}" ROOT_COMMIT="${GITOPS_SOURCE_COMMIT}" \
bash .ci/scripts/report-status.sh success \
"$DESCRIPTION" "$CONTEXT" "" \
"${GITEA_API_URL}/${GITOPS_REPO}/commit/${GITOPS_SHA}"
+13
View File
@@ -8,6 +8,12 @@ on:
version:
required: true
type: string
chart_path:
required: true
type: string
extra_dependency_paths:
required: false
type: string
secrets:
GITEA_TOKEN:
required: true
@@ -47,6 +53,13 @@ jobs:
repository: niko/gitea-ci-library
path: .ci
- name: Resolve extra subchart dependencies
if: inputs.extra_dependency_paths != ''
run: |
for path in $(echo "${{ inputs.extra_dependency_paths }}" | tr ',' '\n'); do
helm dependency update "${path}"
done
- name: Package Helm chart
run: |
CHART_DIR=$(dirname "${CHART_FILE}")
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
shell: bash
run: |
SHA8="${GITHUB_SHA:0:8}"
BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}"
BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}"
{
echo "## Test Reports"
+63 -16
View File
@@ -141,7 +141,7 @@ kubectl create secret generic act-runner-token \
### 4. Helm install / upgrade
Menee samalla komennolla.
Menee samalla komennolla.
> Muista asettaa variables ennen ajoa.
@@ -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
mikäli pod jatkaa ajamista.
Klusterissa on kaksi StatefulSetiä, joilla on eri labelit:
- **`act-runner`** — yleisrunneri (label `ubuntu-latest`). DinD-sidecar on olemassa mutta idle — `require_docker: false` estää runneria käyttämästä sitä.
- **`act-runner-docker`** — Docker-buildien runneri (label `docker`). DinD on aktiivinen, `require_docker: true`.
Vain `docker-build-push.yml` ja `ci-container-build-push.yml` käyttävät `docker`-labelia.
Kaikki muut workflowt (testit, lintit, helm-publish, gitops-dispatch) ajetaan `ubuntu-latest`-runnerilla.
Steppien suoritus ei mene Docker Daemonin läpi, joten konekielisiä kontteja luodaan suoraan K8s-runtimella.
```bash
helm repo add gitea https://dl.gitea.com/charts
helm repo update
# 1. Yleisrunneri (DinD idle)
helm upgrade --install act-runner gitea/actions \
--set enabled=true \
--set giteaRootURL="$GITEA_URL" \
--set existingSecret=act-runner-token \
--set existingSecretKey=token \
--set statefulset.replicas=3 \
--set statefulset.replicas=2 \
--set statefulset.runner.tag=1.0.8 \
--set statefulset.dind.tag=29.5.2-dind \
--set statefulset.dind.resources.requests.memory=250Mi \
--set statefulset.dind.resources.limits.memory=750Mi \
--set-string 'statefulset.runner.config=log:
level: info
cache:
enabled: false
enabled: true
container:
require_docker: true
docker_timeout: 300s' \
require_docker: false' \
--namespace "$GITEA_ACTIONS_NAMESPACE" \
--create-namespace
```
path escapes from parent -bugi korjattiin Docker 29.5.2:ssa. Tämän teko aikana default on 29.5.1 — juuri tämän alle jäävä versio.
# 2. Docker-runner (DinD)
helm upgrade --install act-runner-docker gitea/actions \
--set enabled=true \
--set giteaRootURL="$GITEA_URL" \
--set existingSecret=act-runner-token \
--set existingSecretKey=token \
--set statefulset.replicas=1 \
--set statefulset.runner.tag=1.0.8 \
--set statefulset.dind.tag=29.5.2-dind \
--set statefulset.dind.resources.requests.memory=250Mi \
--set statefulset.dind.resources.limits.memory=750Mi \
--set-string 'statefulset.runner.config=log:
level: info
cache:
enabled: true
container:
require_docker: true
docker_timeout: 300s
runner:
labels:
- "docker:docker://catthehacker/ubuntu:act-latest"' \
--namespace "$GITEA_ACTIONS_NAMESPACE"
```
`path escapes from parent` -bugi korjattiin Docker 29.5.2:ssa. Tämän teko aikana default on 29.5.1 — juuri tämän alle jäävä versio.
Oletus-lokitaso on `debug` — suositeltu `info`. Näkee jobien aloitukset ja valmistumiset ilman konttikerrosten purkua (Downloading/Extracting-spämmiä). `debug` on tarpeen vain vianselvityksessä.
`cache.enabled: true` nappaa image-cachen käyttöön — ilman sitä jokainen ajo lataa konttikuvat uudestaan.
#### Docker (DinD)
Helm chart deployaa DinD:n init-sidecarina (`docker:dind` samassa podissa).
`require_docker: true` kytkee jobit siihen — erillistä DinD-asennusta ei tarvita.
> **Huomio:** Gitea 1.26.x ei tue vielä label-pohjaista runner-valintaa.
> `runs-on: ubuntu-latest`-jobi saattaa päätyä `docker`-labeliselle runnerille.
> Bugi on tunnettu Gitean FAQ:ssa — korjaus tulossa myöhemmässä versiossa.
> Katso: [docs.gitea.com/usage/actions/faq](https://docs.gitea.com/usage/actions/faq)
**DinD-tag pinottu:** `29.5.2-dind` (ei chart-oletusta). Docker 29.5.1 aiheuttaa act-runnerissa
`path escapes from parent` -virheen job-kontin käynnistyksessä.
Helm chart deployaa DinD:n init-sidecarina (`docker:dind` samassa podissa).
Molemmissa StatefulSetissä on DinD-sidecar, mutta:
- **`act-runner`**: `require_docker: false` → runner ei käytä DinD:tä lainkaan, steppien suoritus menee suoraan K8s-runtimella
- **`act-runner-docker`**: `require_docker: true` → runner luo steppikontit DinD:n kautta (tarvitaan `docker build` -komentoja varten)
**DinD-tag pinottu:** `29.5.2-dind` molemmissa (ei chart-oletusta `29.5.1-dind`). Docker 29.5.1 aiheuttaa act-runnerissa
`path escapes from parent` -virheen job-kontin käynnistyksessä sekä `mkdirat var/run: file exists` -virheen tiedostojen kopioinnissa.
Maven/npm-ajot käyttävät vain workflow'n `container:`-imagea; DinD tarvitaan vasta Docker-buildissä.
### 3. Varmista
### 5. Varmista
```bash
kubectl get pods -n gitea-actions
# → act-runner-runner-0 Running
# → act-runner-docker-runner-0 Running
kubectl exec -n gitea-actions act-runner-runner-0 -c dind -- docker version
kubectl exec -n gitea-actions act-runner-docker-runner-0 -c dind -- docker version
# → Server Version: 29.5.2 (tai uudempi)
```
Gitean puolella runner ilmestyy Active-tilaan pienellä viiveellä:
Gitean puolella runnerit ilmestyvät Active-tilaan pienellä viiveellä:
```
Site Admin → Actions → Runners (tai Org → Settings → Actions → Runners)
# → act-runner-runner-0 Active ubuntu-latest
# → act-runner-docker-runner-0 Active docker
```
Tämän jälkeen `.gitea/workflows/ci.yml` triggeröityy automaattisesti pushista.
@@ -220,7 +264,7 @@ Consumer-repossa on oltava seuraavat asetukset:
| Secret | Kuvaus |
|--------|--------|
| `GIT_PAGES_PUBLISH_TOKEN` | Git-pages-palvelimen BasicAuth-token. Nimi on lukittu — tämä tarkka nimi vaaditaan. |
| `REPORTS_PUBLISH_TOKEN` | Git-pages-palvelimen BasicAuth-token. Nimi on lukittu — tämä tarkka nimi vaaditaan. |
`GITEA_TOKEN` on Gitean automaattisesti jokaiselle workflow-runille generoima token (`secrets.GITEA_TOKEN`). Se on scopeutettu **siihen repoon**, jossa workflow ajaa — ei toimi toiseen repoon dispatchaukseen eikä toisen repon commit-statusin asettamiseen. Ei tarvitse erikseen luoda.
@@ -256,7 +300,7 @@ GIT_PAGES_URL=https://ci-reports.example.com
Jokaisen jobin alussa `ci-validate.sh` tarkistaa:
- `.gitea/workflows/gitea-env.conf` on olemassa ja sen arvot ovat validit
- `GITEA_TOKEN` ja `GIT_PAGES_PUBLISH_TOKEN` on asetettu
- `GITEA_TOKEN` ja `REPORTS_PUBLISH_TOKEN` on asetettu
Jos validointi epäonnistuu, job keskeytyy exit-koodilla 1 ja Gitean commit-status näyttää epäonnistumisen linkkinä lokiin.
@@ -279,4 +323,7 @@ Tarkka asennus: [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md)
| `existingSecret` | Kubernetes secretin nimi, jossa token |
| `existingSecretKey` | Avain secretin sisällä |
| `statefulset.dind.tag` | DinD-image tag (`29.5.2-dind` minimi) |
| `statefulset.dind.resources.requests.memory` | DinD muistirequest (suositus `250Mi`) |
| `statefulset.dind.resources.limits.memory` | DinD muistilimitti (suositus `750Mi`) |
| `statefulset.runner.labels` | Mukautetut labelit |
+1 -1
View File
@@ -26,7 +26,7 @@ Consumer:
- Omistaa pipeline-logiikan (mitä testejä ajetaan, missä järjestyksessä)
- Luo raportit omiin polkuihinsa (`reports/{sha8}/{step}/`)
- Luo `.meta`-tiedostot per step
- Määrittelee Gitea-secretit (`GIT_PAGES_PUBLISH_TOKEN`, `GITEA_TOKEN`)
- Määrittelee Gitea-secretit (`REPORTS_PUBLISH_TOKEN`, `GITEA_TOKEN`)
### Provider → Consumer (mitä provider tarjoaa)
+1 -1
View File
@@ -96,7 +96,7 @@ Avainkomponentit:
- **workflow `env:`** — ainoa tapa jakaa arvot kaikkiin jobeihin.
`fromJson(inputs.env_json).KEY` purkaa yksittäiset arvot ilman toistoa.
- **Per-job `env:`** — sisältää vain secretit (`GITEA_TOKEN`,
`GIT_PAGES_PUBLISH_TOKEN`), ei config-arvoja.
`REPORTS_PUBLISH_TOKEN`), ei config-arvoja.
## 5. Pipeline Provides All Dependencies
+2 -2
View File
@@ -69,7 +69,7 @@ org-tason token.
| Secret | Pakollinen | Käyttäjä |
|---|---|---|
| `GITEA_TOKEN` | Kyllä | `report-status.sh`, `check-version.yml`, `docker-build-push.yml`, `gitops-update.sh` (GitOps-repossa) |
| `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | `publish-git-pages.sh`, `config-provider.yml` (validointi) |
| `REPORTS_PUBLISH_TOKEN` | Kyllä | `publish-git-pages.sh`, `config-provider.yml` (validointi) |
| `DOCKER_USERNAME` | Ei | `docker-build-push.yml` (oletus: `github.actor`, ei pakollinen kaikissa registryissä) |
| `DOCKER_PASSWORD` | Kyllä | `docker-build-push.yml` |
@@ -88,7 +88,7 @@ JSON-muotoisen `env_json`:n.
- `.conf`-tiedosto on olemassa
- Jokaisella `KEY=VALUE`-rivillä on arvo (ei tyhjää)
- URL-tyyppiset avaimet alkavat `http://` tai `https://`
- Pakolliset secretit (`GITEA_TOKEN`, `GIT_PAGES_PUBLISH_TOKEN`) on asetettu
- Pakolliset secretit (`GITEA_TOKEN`, `REPORTS_PUBLISH_TOKEN`) on asetettu
Kutsu:
```yaml
+28 -211
View File
@@ -1,227 +1,44 @@
# Design Rationale — Gitea Actions CI -kirjasto
# Design Rationale — git-pages → Nginx + PV
> Miksi kirjasto on rakennettu näin. Arvot, periaatteet ja reunaehdot, joiden
> varaan arkkitehtuuri nojaa.
>
> Tämä dokumentti on **normatiivinen** — arkkitehtuurin on noudatettava näitä
> periaatteita. Jos ehdotettu muutos on ristiriidassa rationalen kanssa,
> rationalen on muututtava ensin.
> Päivitetty: 2026-06-28 — git-pages korvattu Nginx + upload-sidecar -arkkitehtuurilla.
---
## Miksi git-pages poistettiin
## Miksi tämä projekti on olemassa
Codeberg git-pages storage v2 pakottaa kaikki tiedostot yhteen `.index`-protobufiin per site. Raja on 1MB kovakoodattu. Monorepo täytti tämän — uusia raportteja ei voinut julkaista.
Mikropalveluarkkitehtuurissa jokainen palvelu tarvitsee CI-putken: testit,
laatutarkistukset, buildin, kontituksen ja julkaisun. Ilman jaettua
kirjastoa jokainen tiimi kopioi saman YAML-boilerplaten, tekee omat
virheensä ja ylläpitää omaa versiotaan. Ajan myötä putket ajautuvat erilleen
— toisessa on `shell: bash`, toisessa ei; toinen käyttää `set -o pipefail`,
toinen kadottaa exit-koodin `tee`:hen.
## Uusi arkkitehtuuri
Tämä kirjasto on se mitä kopioidaan. Se tarjoaa valmiit, testatut,
dokumentoidut rakennuspalikat joista jokainen tiimi kokoaa oman putkensa.
Palikat ovat Gitea Actionsin `uses:`-direktiivillä kutsuttavia reusable
workflow'ta — ei asennusta, ei runtime-riippuvuutta, ei versiopäivityksiä
projekteihin.
- **Nginx** palvelee staattisia tiedostoja suoraan PV:ltä
- **Upload-sidecar** (busybox httpd + CGI) ottaa vastaan PUT + tar.gz ja purkaa PV:lle
- **URL = FS-polku** — `/{owner}/{repo}/{branch}/{sha8}/{suite}/`
- **Branch raakana URL:ssa** — `feature/x` FS:ssä = `feature/x/`
- **Linkki on aina 1-suuntainen** — gitea → raportti
- **GITEA_API_URL poistuu** publish-skriptin pakollisista env-varista
---
## Ratkaisun periaatteet
## Suunnitteluperiaatteet
### 1. Ei välikerrosta — tiedostot suoraan PV:llä
### 1. Palikka-arkkitehtuuri: pieniä, vaihdettavia, yhden vastuun workflow'ta
git-pagesin `.index`-protobuf poistuu. Tiedostot ovat suoraan PV:llä kansiorakenteessa. Nginx palvelee niitä suoraan.
Jokainen provider-workflow tekee yhden asian:
### 2. Yksinkertainen upload-sidecar
| Workflow | Vastuu |
|---|---|
| `config-provider.yml` | Lataa ja validoi konfiguraatio |
| `check-version.yml` | Tarkistaa onko commit buildattu, laskee version |
| `docker-build-push.yml` | Buildaa, puskea ja tagittaa kontin |
Python HTTP-serveri ottaa vastaan PUT-pyynnöt ja purkaa tar.gz:n oikeaan paikkaan. Ei monimutkaista API:a, ei tietokantaa.
Mikään workflow ei kutsu toista provider-workflowta. Consumer
— siis mikropalvelun oma pipeline-tiedosto — on ainoa paikka joka
tietää mitä palikoita tarvitaan ja missä järjestyksessä.
### 3. Julkaisu ja luku eri porteilla
**Miksi:** Tämä on sama periaate kuin Unix-putkissa tai mikropalveluissa:
pieniä, itsenäisiä komponentteja jotka tekevät yhden asian hyvin.
Consumer voi vaihtaa yhden palikan toiseen — esimerkiksi Docker-buildin
tilalle Maven-paketoinnin — ilman että muut palikat muuttuvat.
Ratkaisu ei ole se että kaikki ajetaan, vaan se että jokainen tiimi
valitsee mitä tarvitsee. Monoliittinen "kaikki yhdessä" -workflow
pakottaisi jokaisen tiimin ajamaan tarpeettomia vaiheita.
Eri portit mahdollistavat eri Traefik-säännöt: julkaisu vaatii BasicAuthin, luku on julkinen.
### 2. Gitea ensin — hyödynnä alustaa, älä taistele sitä vastaan
### 4. Retention lukee PV:tä suoraan
Gitea Actions tarjoaa kolme asiaa ilmaiseksi:
Ei HTTP API:a retentionia varten. Skripti listaa kansiot, tarkistaa branchit Giteasta, poistaa vanhat.
1. **Jobien visuaalinen status** — jokainen jobi näkyy automaattisesti
commit-näkymässä checkmarkilla, spinnerillä tai ristillä.
2. **Cross-job riippuvuudet**`needs` hoitaa virheiden propagointin:
jos edeltävä jobi feilaa, riippuvat jobit skipataan.
3. **Reusable workflow -jakelu**`uses: org/repo/.gitea/workflows/file.yml@v1`
on natiivisti versioitu, skopattu ja välimuistitettu.
## Rajapinnat
Kirjasto käyttää näitä kaikkia. Ei omaa tilakonetta, ei custom
action -runtimea, ei ulkoista orkestraattoria.
**Esimerkki:** Tool-jobit eivät kutsu commit-status API:a lainkaan.
Gitean oma job-status riittää — `success`/`failure`/`running` näkyy
automaattisesti. API:a käytetään vain kun tarvitaan **custom-linkki**
(testiraporttiin tai Docker registryyn), jota natiivistaatus ei tarjoa.
Tämä linjaus on dokumentoitu ADR 0004 ja 0007:ssä.
### 3. Status näkyy siellä missä työ tehdään — Git-commitissa
Kehittäjä työskentelee Gitissä. `git log`, `git blame`, PR-näkymä —
nämä ovat päivittäiset työkalut. CI-statuksen kuuluu näkyä siellä,
ei erillisessä dashboardissa.
Gitea Actionsin natiivi job-status tekee tämän automaattisesti:
jokainen commit näyttää välittömästi mitkä jobit on ajettu ja millä
tuloksella. Testiraportteihin pääsee yhdellä klikkauksella commitin
status-kuvakkeesta — koska `report-status.sh` asettaa `target_url`:n
osoittamaan suoraan HTML-raporttiin git-pagesissa.
Tämä ei ole kosmeettinen yksityiskohta. Se on devops-käytännön
ydin: palautesilmukka on lyhin mahdollinen. Commit → build → status
näkyy samassa näkymässä jossa kehittäjä jo on.
### 4. Exit-koodi on ainoa totuus
CI-putken jokaisen `run`-stepin onnistuminen määräytyy **vain ja
ainoastaan** exit-koodin perusteella. Ei tiedoston olemassaolon, ei
stdout-tulosteen, ei arvauksen. `0` = ok, kaikki muu = ei ok.
Tämä kuulostaa itsestään selvältä, mutta YAML-pipelineissa se rikkoutuu
helposti. Pipe (`|`) `tee`:hen syö exit-koodin. Tiedoston olemassaolon
tarkistus (`[ -f results.xml ]`) ei kerro testien läpimenosta.
**Käytännössä:** Jokainen `run`-steppi ottaa exit-koodin talteen
`$?`-muuttujaan ennen kuin mikään muu komento ehtii muuttaa sitä,
ja stepin viimeinen rivi on `exit ${EXIT}`. Pipeä ei käytetä
työvaiheen viimeisenä komentona. Ks. ADR 0008.
### 5. Pienin mahdollinen pinta-ala
Jokainen ylimääräinen riippuvuus on ylimääräinen vikaantumispiste.
Kirjaston ainoat riippuvuudet:
- Gitea Actions (alusta)
- `bash`, `curl`, `jq` (ubuntu-latest runnerissa valmiina)
- Docker (runnerissa valmiina)
- git-pages (raporttien hostaus, erillinen palvelu)
Ei Pythonia, ei Node.js:ää ajonaikaisesti (testit omissa konteissaan).
Ei tietokantaa. Ei ulkoista tilanhallintaa. Kirjasto on joukko
YAML-tiedostoja ja shell-skriptejä — samat työkalut jotka jokainen
devops-ihminen jo osaa.
### 6. Konfiguraatio repoon, salaisuudet Giteaan
Projektikohtainen konfiguraatio (`.gitea/workflows/gitea-env.conf`)
asuu mikropalvelun omassa repossa. Kehittäjä omistaa sen — hän tietää
mikä on Docker-imagen nimi, mihin registryyn puskea, mikä on
testiympäristön URL.
Salaisuudet (tokenit, salasanat) elävät Gitean secrets-mekanismissa,
eivät repon tiedostoissa. `secrets: inherit` välittää ne providerin
workflow'hun ilman että consumerin tarvitsee tietää mitä salaisuuksia
mikäkin provider tarvitsee.
Poikkeus: infra-tason asetukset (`GIT_PAGES_URL`, `GITEA_API_URL`)
ovat Gitean organization secrets/variables -mekanismissa. Ne eivät
ole repokohtaisia.
### 7. Consumer omistaa orkestroinnin, provider tarjoaa palikat
Tämä on kirjaston tärkein arkkitehtuurinen päätös (ADR 0005).
Provider (`gitea-ci-library`) ei tiedä mitä testejä ajetaan, missä
järjestyksessä, tai millä branchilla. Se tarjoaa kolme reusable
workflow'ta ja joukon skriptejä.
Consumer (mikropalvelun `example-feature.yml` / `example-main.yml`)
päättää:
- Mitkä palikat kutsutaan
- Missä järjestyksessä (`needs`)
- Millä branch-ehdoilla (`if`)
- Mitkä testikontit käytetään (input-parametrit)
Tämä on tarkoituksellinen vallanjako. Provider ei voi tietää jokaisen
tiimin tarpeita — eikä sen pidäkään. Consumer ei voi muuttaa providerin
sisäistä toteutusta — eikä sen pidäkään. Rajapinta on `workflow_call` ja
se on molemmille osapuolille selvä.
### 8. Branch-kohtainen reititys, ei yhtä kaikille
Eri brancheilla on eri tavoite:
- **Feature-haara:** Onko koodi laadukasta? → testit, validointi
- **Main-haara:** Onko tästä versiosta jo artifakti? Jos ei →
testit + build + push + tag. Jos on → ei tehdä mitään (tai
jatketaan klusteritesteihin).
Tämä logiikka elää consumerin pipeline-tiedostossa, ei providerissa.
Se on puhdasta `if`-ehtoa ja `needs`-ketjutusta — ei skriptausta,
ei monimutkaisia ehtoja providerin sisällä.
### 9. Raportit erillisellä palvelulla, linkit commitissa
Gitea Actionsin artifact-järjestelmä on binääriarkisto — ZIP-lataus,
ei HTML-selailtavuutta. Testiraportit (Cucumber HTML, Bats-coverage)
on voitava avata selaimessa yhdellä klikkauksella.
Ratkaisu: git-pages Helm-chartti, joka tarjoaa staattista
tiedostohostingia HTTP:llä. `publish-git-pages.sh` vie raportit
sinne; `report-status.sh` linkittää commit-statuksen suoraan
raporttiin. Retention hoitaa git-pagesin sidecar automaattisesti.
Tulevaisuudessa `GITHUB_STEP_SUMMARY` (Gitea 1.27+) tarjoaa
vaihtoehtoisen kanavan: jobin Summary-välilehdelle renderöityvä
Markdown-taulukko kaikista raporttilinkeistä.
### 10. Vain Gitea — ei monialustatukea ilman tarvetta
Yhden alustan tukeminen kunnolla on vaikeampaa kuin kolmen tukeminen
huonosti. Gitea Actionsin `uses:`-mekanismi, `needs`-semantiikka,
`secrets: inherit`, `gitea`-konteksti — nämä ovat alustakohtaisia
ominaisuuksia joita abstraktiokerros vain haittaisi.
Jos toinen alusta tulee ajankohtaiseksi, sille kirjoitetaan oma
toteutus. Siihen asti yksi alusta riittää. Ennenaikainen yleistys
on devopsissa yhtä haitallista kuin ohjelmistosuunnittelussa.
---
## Arkkitehtuuriset rajoitteet
### Mitä kirjasto EI tee
- **Ei ulkoista orkestraattoria.** Pipeline-ohjaus on Gitea Actionsin
`needs`-ketjuissa ja consumerin `if`-ehdoissa.
- **Ei custom actioneita.** Reusable workflow on kevyempi, versioitu
ja jaeltu Gitean oman mekanismin kautta.
- **Ei asennusta projekteihin.** Consumer viittaa `uses:`-direktiivillä
suoraan tämän repon workflow-tiedostoihin. Ei npm-pakettia, ei
git-submodulea, ei kopioitavia tiedostoja.
- **Ei runtime-riippuvuuksia.** Provider-skriptit käyttävät vain
työkaluja jotka ovat Gitea Actionsin `ubuntu-latest` runnerissa
valmiina: `bash`, `curl`, `jq`.
- **Ei monorepo-konfiguraatiota.** Jokainen mikropalvelu omistaa
oman pipeline-tiedostonsa ja konfiguraationsa.
---
## Mitä tietoisesti hylättiin
| Hylätty | Syy |
|---|---|
| Monoliittinen "kaikki yhdessä" -workflow | Pakottaa kaikille samat vaiheet. Palikka-arkkitehtuuri antaa jokaiselle tiimille vain mitä se tarvitsee |
| Oma orkestraattoripalvelin | Ylimääräinen ylläpidettävä. Gitean `needs` ja `if` riittävät |
| Docker-pohjaiset custom actionit | Tuovat riippuvuuden Docker-rekisteriin. Reusable workflow on natiivimpi |
| Commit-status API jokaiselle vaiheelle | Duplikointia — Gitea näyttää job-statuksen automaattisesti. API vain custom-linkeille |
| `tee`-putki debug-näkyvyyteen | Syö exit-koodin. stdout ohjataan tiedostoon `>` ilman pipeä |
| Multi-Git-platform-tuki | Ennenaikaista optimointia ilman tarvetta |
| Gitea Packages raporttien hostingiin | Ei HTML-selailtavuutta — vain binäärilataus |
| Gitea Pages + reports-branch | Race condition rinnakkaisten pushien kanssa |
| `repository_dispatch` ketjutukseen | Lisää konfiguraatiota vastaanottaviin repoihin. Suora API-kutsu eksplisiittisempi |
| Suunta | Protokolla | Auth | Kuvaus |
|--------|------------|------|--------|
| Julkaisija → Traefik | HTTPS PUT | BasicAuth `publish` | tar.gz → upload-sidecar |
| Selain → Traefik | HTTPS GET/HEAD | — | HTML-raportti nginxiltä |
| Retention → Gitea | HTTPS GET | PAT | branch-tarkistus |
| Traefik → nginx | HTTP :8080 | — | sisäverkko |
| Traefik → upload | HTTP :8081 | — | sisäverkko (BasicAuth tarkistettu) |
+551
View File
@@ -0,0 +1,551 @@
# git-pages → Nginx + PV -arkkitehtuuri
> Päivitetty: 2026-06-28
## Ongelma
git-pages storage v2 pakottaa kaikki tiedostot yhteen `.index`-protobufiin per site.
Raja on 1MB kovakoodattu. Yksi monorepo täytti tämän, eikä uusia raportteja voi
julkaista ennen kuin ongelma on ratkaistu.
## Ratkaisu
Poista git-pages kokonaan. Korvaa Nginx:llä (static serving) + kevyellä upload-sidecarilla (tar.gz HTTP PUT → extract). Tiedostot suoraan PV:llä ilman `.index`-välikerrosta.
## Lukitut päätökset
1. **git-pages poistuu** — sovellus ei ole mukana luku- eikä kirjoitusketjussa
2. **Nginx palvelee suoraan PV:ltä**`root /data`, `autoindex on`, `try_files $uri $uri/ $uri/index.html =404`
3. **Upload-sidecar** — busybox httpd + CGI, ottaa vastaan `PUT /path` + tar.gz body ja purkaa PV:lle
4. **URL = FS-polku** — ei Host-header-kikkaa, ei slugia, ei rewritea
5. **Traefik** — PATCH/PUT → upload-sidecar:8081 (BasicAuth), GET/HEAD → nginx:8080 (ForwardAuth myöh.)
6. **CI-julkaisu**`curl -X PUT --data-binary @report.tar.gz https://{host}/{owner}/{repo}/{branch_raw}/{sha8}/{suite}/`
7. **Branch raakana URL:ssa**`feature/x` URL:ssa = `feature/x/` FS:ssä. Ei slugitusta
8. **Linkki on aina 1-suuntainen** — gitea → raportti. Raportista ei linkkiä giteaan
9. **GITEA_API_URL poistuu** publish-skriptin pakollisista env-varista (retention käyttää omaansa)
## Ympäristömuuttujat
### GITHUB_REF_NAME
Gitea Actions runnerin automaattisesti asettama muuttuja, joka on branchin/tagin nimi ilman `refs/heads/`-etuliitettä.
| Tilanne | Arvo |
|---------|------|
| branch `main` | `main` |
| branch `feature/branch` | `feature/branch` |
| tag `v1.0.0` | `v1.0.0` |
Käytännössä `git rev-parse --abbrev-ref HEAD`. Branchin `/` on sallittu URL-polussa, joten `GITHUB_REF_NAME` voidaan käyttää sellaisenaan rakenteessa `${GITHUB_REF_NAME}/${SHA8}/${SUITE}`. Rinnakkaisissa buildeissa jokainen runner ajaa oman branchinsa kontekstissa → URL pysyy uniikkina.
```
CI → curl -X PUT → Traefik (BasicAuth middleware)
→ upload-sidecar:8081 (busybox httpd + CGI)
→ tar -xzf - -C /data/{owner}/{repo}/{branch}/{sha8}/{suite}/
→ PV /app/data/
Browser → GET → Traefik
→ nginx:8080 (root /app/data)
→ /data/{owner}/{repo}/{branch}/{sha8}/{suite}/index.html
```
## Tiedostomuutokset
### Poistuu (4 tiedostoa)
| Tiedosto | Miksi |
|----------|-------|
| `templates/init-job.yaml` | Ei enää git-pages API:a alustettavaksi |
| `templates/configmap.yaml` (nykyinen) | Korvataan nginx-conf + CGI |
| `templates/deployment.yaml` (nykyinen) | Korvataan uudella |
| `git-pages-image` (values.yaml) | Ei enää git-pages-konttia |
### Muuttuu (8 tiedostoa)
| # | Tiedosto | Muutos |
|---|----------|--------|
| 1 | `Chart.yaml` | description, poista appVersion |
| 2 | `values.yaml` | Poista git-pages-keyt, lisää nginx/sidecar |
| 3 | `dev-values.yaml` | Matchaa uusi values-rakenne |
| 4 | `templates/configmap.yaml` | `default.conf` (nginx) + `upload.cgi` |
| 5 | `templates/deployment.yaml` | 2 konttia: nginx:alpine (8080) + busybox httpd (8081) |
| 6 | `templates/ingressroute.yaml` | Backend: PATCH/PUT → upload:8081, GET/HEAD → nginx:8080 |
| 7 | `templates/service.yaml` | 2 porttia: http-read(8080), http-write(8081) |
| 8 | `templates/NOTES.txt` | Uusi URL + esimerkit |
### Pysyy ennallaan (8 tiedostoa)
| Tiedosto | Huomio |
|----------|--------|
| `templates/certificate.yaml` | TLS hostille |
| `templates/middleware.yaml` | BasicAuth + HTTPS redirect |
| `templates/publish-auth-secret.yaml` | Sama BasicAuth-secret |
| `templates/pvc.yaml` | Sama PVC |
| `templates/retention-configmap.yaml` | Päivitä retention-scriptit |
| `templates/retention-cronjob.yaml` | Pieni muutos (ei git-pages API) |
| `templates/retention-rbac.yaml` | Ennallaan |
| `_helpers.tpl` | Vain label-helpers |
### Chartin ulkopuolella
| Tiedosto | Muutos |
|----------|--------|
| `scripts/publish-git-pages.sh` | Uusi: `curl -X PUT` tar.gz upload-sidecariin |
| `scripts/report-status.sh` | URL: `reports/``${GITHUB_REF_NAME}/` |
| `scripts/ci-report.sh` | URL (rivi 101): `reports/``${GITHUB_REF_NAME}/` |
| `git-pages/docs/architecture.md` | Uusi arkkitehtuuri |
| `docs/design-rationale.md` | Päivitys |
| `files/retention-*.sh` | Lue PV:tä suoraan, ei git-pages API:a |
## Upload CGI
Busybox httpd välittää CGI-skriptille tiedot ympäristömuuttujissa (`REQUEST_METHOD`, `PATH_INFO`, `CONTENT_LENGTH`). Skripti on tiedosto `/cgi/upload.cgi``.cgi`-pääte laukaisee CGI-execution.
```bash
#!/bin/sh
# REQUEST_METHOD, PATH_INFO, etc. asettaa busybox httpd automaattisesti
if [ "$REQUEST_METHOD" = "PUT" ]; then
dest="/app/data${PATH_INFO%/}"
mkdir -p "$dest"
cat - | tar -xzf - -C "$dest"
echo "Status: 201 Created"
echo ""
fi
```
**PUT `https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`** → CGI purkaa:
- `PATH_INFO` = `/{owner}/{repo}/{branch}/{sha8}/{suite}/`
- `dest` = `/app/data/{owner}/{repo}/{branch}/{sha8}/{suite}`
- Tarin tiedostot puretaan tähän
**Huomio:** Skripti käyttää tar.gz-pakkausta. Jos tar on raaka (ilman gzip:ia), vaihda `-xzf``-xf`.
### CGI-kutsu
Busybox httpd käynnistetään:
```bash
busybox httpd -f -p 8081 -h /cgi
```
`-h /cgi` on docroot. Skriptitiedoston pääte `.cgi` (esim. `/cgi/upload.cgi`) käynnistää CGI-execution automaattisesti.
## Nginx-konfiguraatio
```nginx
client_max_body_size 100M; # nousevat raportit; oletus 1M on liian pieni
server {
listen 8080;
root /app/data;
autoindex on;
client_max_body_size 100M;
location / {
try_files $uri $uri/ $uri/index.html =404;
}
}
```
Alpine-nginx lukee configit `/etc/nginx/http.d/default.conf`. ConfigMap mountataan tähän polkuun.
## Deployment (kontit)
```yaml
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 8080
name: http-read
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/http.d
- name: data
mountPath: /app/data
- name: upload
image: alpine:latest
command:
- busybox
- httpd
- -f
- -p
- "8081"
- -h
- /cgi
ports:
- containerPort: 8081
name: http-write
volumeMounts:
- name: cgi-scripts
mountPath: /cgi
readOnly: true
- name: data
mountPath: /app/data
```
## values.yaml-rakenne
```yaml
# git-pages → Nginx + upload-sidecar
nameOverride: ""
fullnameOverride: ""
nginx:
image: nginx
tag: alpine
port: 8080
bodySize: 100M
upload:
image: alpine
tag: latest
port: 8081
service:
type: ClusterIP
persistence:
enabled: true
accessMode: ReadWriteOnce
storageClass: ""
size: 5Gi
ingress:
enabled: true
host: ci-reports.helm-dev.keskikuja.site
entryPoints:
websecure: websecure
web: web
certificate:
enabled: true
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
publishAuth:
create: false
htpasswdUsers: ""
retention:
enabled: false
mode: cronjob
schedule: "0 3 * * *"
image:
repository: debian
tag: bookworm-slim
giteaApiUrl: ""
rules:
branches:
default:
minAgeDays: 7
keepMin: 5
```
## CI-julkaisu (publish-skripti)
### Muutokset nykyiseen
| Kohta | Nykyinen | Uusi |
|-------|----------|------|
| `GITEA_API_URL` | pakollinen env-var | **poistettu** (retention käyttää omaansa) |
| Tar-prefiksi | `{owner}/{repo}/reports/{sha8}/{suite}` | ei prefiksiä — `{suite}` |
| Paketti | `tar -cf` | `tar -czf` (gzip) |
| Content-Type | `application/x-tar` | `application/tar+gz` |
| Headerit | `Atomic: no`, `Create-Parents: yes` | poistettu |
| Kohde | `PATCH {GIT_PAGES_URL}/` | `PUT {GIT_PAGES_URL}/{owner}/{repo}/{branch}/{sha8}/{suite}/` |
| Branch | vain `.meta` | URL:ssa + `.meta` |
### Uusi skripti (ydin)
```bash
#!/usr/bin/env sh
set -eu
SUITE_PATH="${1:-}"
[ -n "$SUITE_PATH" ] || { echo "ERROR: suite_path required" >&2; exit 1; }
[ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; }
[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${GITHUB_REPOSITORY:-}" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; }
[ -n "${GITHUB_SHA:-}" ] || { echo "ERROR: GITHUB_SHA is not set" >&2; exit 1; }
[ -n "${GITHUB_REF_NAME:-}" ] || { echo "ERROR: GITHUB_REF_NAME is not set" >&2; exit 1; }
OWNER="${GITHUB_REPOSITORY%%/*}"
REPO="${GITHUB_REPOSITORY##*/}"
SHA8=$(echo "$GITHUB_SHA" | cut -c1-8)
BRANCH="${GITHUB_REF_NAME}"
SUITE="${SUITE_PATH%/}"
PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}"
[ -d "$SUITE" ] || { echo "ERROR: not a directory: $SUITE" >&2; exit 1; }
WORK=$(mktemp -d)
TAR=$(mktemp)
trap 'rm -rf "$WORK" "$TAR"' EXIT
# Kopioi raporttitiedostot
mkdir -p "$WORK/$SUITE"
cp -a "$SUITE/." "$WORK/$SUITE/"
# Generoi index.html (sama logiikka kuin nykyään)
cd "$WORK/$SUITE"
if [ ! -f "index.html" ]; then
# identtinen item-listaus kuin nykyisessä skriptissä
fi
# .meta tiedosto retentionia varten
cat > ".meta" <<EOF
{"branch":"${BRANCH}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF
cd "$WORK"
# Pakkaa: pelkät tiedostot ilman $SUITE-etuliitettä
# CGI purkaa destinationiin /data/{owner}/{repo}/{branch}/{sha8}/{suite}/
# ilman --strip-components, joten tarin menee suoraan oikeaan paikkaan
tar czf "$TAR" -C "$WORK/$SUITE" .
# PUT upload-sidecariin
PUBLISH_URL="${GIT_PAGES_URL}/${OWNER}/${REPO}/${BRANCH}/${SHA8}/${SUITE}/"
HTTP_CODE=$(curl -sS -X PUT "$PUBLISH_URL" \
-u "${PAGES_USER}:${REPORTS_PUBLISH_TOKEN}" \
-H "Content-Type: application/tar+gz" \
--data-binary @"$TAR" \
-o /tmp/git-pages-publish-response.txt \
-w "%{http_code}")
case "$HTTP_CODE" in
200|201|204) ;;
*)
echo "ERROR: publish HTTP ${HTTP_CODE}" >&2
cat /tmp/git-pages-publish-response.txt >&2
exit 1
;;
esac
echo "${PUBLISH_URL}"
```
## Gitea commit -linkki (report-status.sh)
Gitean commit-näkymään tuleva raporttilinkki muodostetaan `scripts/report-status.sh`:ssa.
### Muutos
| Kohta | Nykyinen (rivi 21) | Uusi |
|-------|-------------------|------|
| URL | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}` |
`reports/``{GITHUB_REF_NAME}/`. Branchin raaka nimi URL-polussa on sallittu. Muu skripti (Gitea API -kutsu, token, json-body) pysyy identtisenä.
Tarkista: `GITHUB_REF_NAME` on oltava asetettu — Gitea Actions asettaa sen automaattisesti. Jos sitä tarvitaan tarkistuksena, lisätään `report-status.sh`:n env-var-tarkistuksiin.
## Gitea Actions step summary (report-summary.yml)
`.gitea/workflows/report-summary.yml` luo GITHUB_STEP_SUMMARY -taulukon raporttilinkeillä.
### Muutos
| Kohta | Nykyinen (rivi 28) | Uusi |
|-------|-------------------|------|
| BASE | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}` |
Sama muuri: `reports/``${GITHUB_REF_NAME}/`. Loppuosa identtinen.
## CI-report (ci-report.sh)
`scripts/ci-report.sh` rakentaa URL:n suoraan single-entry-tapauksessa (rivi 101).
### Muutos
| Kohta | Nykyinen (rivi 101) | Uusi |
|-------|-------------------|------|
| URL | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${SINGLE_ENTRY}` | `${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}/${SINGLE_ENTRY}` |
`reports/``${GITHUB_REF_NAME}/`. Lisäksi `GITHUB_REF_NAME` pitää lisätä env-var-tarkistuksiin, jos sitä ei ole.
## Retention
Retention siirtyy lukemaan PV:tä suoraan (find, ls, stat) git-pages API:n sijaan. Nykyinen malli luki `.git-pages/manifest.json` HTTP:lla — uusi lukee FS:ää.
### Data
```
/app/data/{owner}/{repo}/{branch}/{sha8}/
.meta ← {"branch":"...","sha":"...","published_at":"..."}
index.html
style.css
...
```
### Logiikka commit-tasolla
Retention on commit-kohtainen. Yksi commit = yksi kansio `/{sha8}/`, jonka alla voi olla useita suiteja (`cucumber/`, `bats/`, jne.).
```
/app/data/{owner}/{repo}/{branch}/
abc12345/ ← yksi commit
cucumber/
index.html
.meta
bats/
index.html
.meta
def67890/ ← toinen commit
cucumber/
index.html
.meta
```
Toiminta aktiiviselle branchille (löytyy Giteasta):
1. Listaa commit-kansiot (`abc12345/`, `def67890/`, ...) uusin ensin (viimeksi muokattu)
2. Ohita `keepMin` kpl — nämä säilytetään aina
3. Jos raportti on alle `minAgeDays` päivää vanha → skip (liian tuore poistettavaksi)
4. Muut poistetaan (`rm -rf {sha8}/`), jolloin kaikki commitin suite-tulokset poistuvat kerralla
Poistuneelle branchille (404 Giteasta): poista koko `{branch}/`-kansio.
### Parametrit
```yaml
retention:
rules:
branches:
default:
keepMin: 5
minAgeDays: 7
main:
keepMin: 20
minAgeDays: 14
```
**Parametrin nimi:** `maxAgeDays``minAgeDays` (arvo sama, uusi nimi kertoo mitä se todella tekee: minimi-ikä ennen poistoa).
### Skripti (ydin)
```bash
DATA_ROOT="/app/data"
for owner_dir in "$DATA_ROOT"/*/; do
owner=$(basename "$owner_dir")
for repo_dir in "$owner_dir"*/; do
repo=$(basename "$repo_dir")
for branch_dir in "$repo_dir"*/; do
branch=$(basename "$branch_dir")
branch_urlenc=$(echo "$branch" | sed 's/\//%2F/g')
# Tarkista onko branch yhä olemassa Giteassa
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token $GITEA_TOKEN" \
"$GITEA_API_URL/api/v1/repos/$owner/$repo/branches/$branch_urlenc")
if [ "$HTTP_CODE" = "404" ]; then
# Branch poistettu — tuhoa koko branch
rm -rf "$branch_dir"
continue
fi
# Aktiivinen branch — hae säännöt
rules=$(jq -r ".branches.\"$branch\" // .branches.default" "$RETENTION_CONFIG")
keep_min=$(echo "$rules" | jq -r '.keepMin // 5')
min_age=$(echo "$rules" | jq -r '.minAgeDays // 7')
# Listaa commit-kansiot uusin ensin
commits=$(ls -1t "$branch_dir")
count=0
for sha8 in $commits; do
[ -d "$branch_dir/$sha8" ] || continue
count=$((count + 1))
# Ohita keepMin kpl
[ "$count" -le "$keep_min" ] && continue
# Tarkista ikä
age_seconds=$(( $(date +%s) - $(stat -c %Y "$branch_dir/$sha8") ))
age_days=$(( age_seconds / 86400 ))
[ "$age_days" -lt "$min_age" ] && continue
# Poista koko commit
rm -rf "$branch_dir/$sha8"
done
done
done
done
```
## Deploy-järjestys (vaiheistus)
### Vaiheet
| # | Vaihe | Mitä | Riippuvuus |
|---|-------|------|-----------|
| 1 | **Poista vanha PVC** | `kubectl delete pvc git-pages-data -n gitea-reports` (tai helm uninstall + uusi asennus) | Vanha git-pages-data .index-muodossa — tuhotaan |
| 2 | **Deployaa uusi chart** | `helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml` | Uusi PVC luodaan tyhjänä. Vanhoja raportteja ei ole. |
| 3 | **Varmenna Nginx** | `curl localhost:8080/` → 404/autoindex | Nginx palvelee |
| 4 | **Varmenna upload** | `curl -X PUT -T test.tar.gz localhost:8081/test/` → 201 | CGI toimii |
| 5 | **Päivitä provider-skriptit** | `scripts/publish-git-pages.sh`, `scripts/report-status.sh`, `scripts/ci-report.sh` | Uusi URL-muoto |
| 6 | **Päivitä provider-workflow** | `.gitea/workflows/report-summary.yml` | BASE-muuttuja |
| 7 | **Varmenna CI-julkaisu** | `bash publish-git-pages.sh <test-suite>` → 201 + URL | Koko ketju |
### Consumer-workflowt EI päivitetä
`.gitea/workflows/example-*` ja `.ci/scripts/*` (consumerien kopioimat skriptit) eivät muutu.
### report-summary.yml
On provider-workflow (ei `example-`-prefiksiä). Saa päivittyä.
## Rollback-suunnitelma
### Jos chart epäonnistuu
```bash
helm rollback git-pages <revision>
git revert HEAD # tai git reset --hard ennen script-muutoksia
```
- Vanha PVC on poistettu → rollback palauttaa vanhan git-pages-deploymentin mutta ilman dataa
- Vanhaa `.index`-dataa ei palaudu — se tuhottiin vaiheessa 1
- Jos uutta dataa on ehtinyt kertyä, se on uudella PVC:llä → vanha deployment ei löydä sitä
### Suojaus
Pidä vanha PVC hengessä (nimeä uudelleen, älä poista) kunnes rollback-ikkuna on umpeutunut:
```bash
# Ennen vaihetta 1: uudelleennimeä vanha PVC
kubectl delete pvc git-pages-data -n gitea-reports --wait=false
kubectl patch pvc git-pages-data -n gitea-reports -p '{"metadata":{"finalizers":[]}}' --type merge # tarvittaessa
```
Jos rollback tarvitaan 24h sisällä, palauta vanha PVC backupista (esim. snapshot).
## Varmennus (check-stepit)
Jokaiselle komponentille check ennen seuraavaan vaiheeseen siirtymistä:
| Vaihe | Check |
|-------|-------|
| **helm template** | `helm template gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml` → deploymentissa 2 konttia (nginx + upload) |
| **Nginx** | `kubectl exec <pod> -- wget -qO- http://localhost:8080/` → 404 tai autoindex (ei connection refused) |
| **Upload CGI** | `echo "hello" \| tar czf /tmp/test.tar.gz -C /tmp . && curl -sS -X PUT -H "Content-Type: application/tar+gz" --data-binary @/tmp/test.tar.gz http://localhost:8081/test/hello/` → status 201. Sitten `kubectl exec <pod> -- ls /app/data/test/hello/` → tiedostot näkyvissä |
| **Retention** | `kubectl exec <pod> -- bash /scripts/retention-run.sh` → exit 0, ei virheitä |
| **Provider-skripti (end-to-end)** | 1. `kubectl port-forward pod/<pod> 8080:8080 8081:8081` 2. `bash scripts/publish-git-pages.sh <test-suite>` → output URL (`https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`) 3. `curl http://localhost:8080/{owner}/{repo}/{branch}/{sha8}/{suite}/` → 200, HTML näkyy 4. `curl -I http://localhost:8080/{owner}/{repo}/{branch}/{sha8}/{suite}/index.html` → 200 |
## Data migration
**Ei migraatiota.** Vanha PVC sisältää git-pages storage v2 -dataa (`.index` protobuf). Tämä data tuhotaan tarkoituksella:
1. CI on rikki 1MB rajoitteen takia — uusia raportteja ei voi julkaista ennen kuin git-pages on poistettu
2. Vanhat raportit ovat vanhentuneita (branchit on jo saatettu poistaa, commitit vanhoja)
3. Uudet buildit tuottavat raportit uuteen järjestelmään ilman migraatiota
**Toimenpide:**
- Poista vanha PVC ennen uuden chartin deployausta
- Uusi chart luo uuden tyhjän PVC:n
- CI ajaa uudet buildit → raportit kirjoitetaan Nginx+PV-malliin
+3 -3
View File
@@ -79,7 +79,7 @@ publish-git-pages.sh <suite>
|---|---|
| `GITEA_API_URL` | `env_json` → workflow `env:` |
| `GIT_PAGES_URL` | `env_json` → workflow `env:` |
| `GIT_PAGES_PUBLISH_TOKEN` | Gitea secret → `env:` |
| `REPORTS_PUBLISH_TOKEN` | Gitea secret → `env:` |
| `GITHUB_REPOSITORY` | Automaattinen |
| `GITHUB_SHA` | Automaattinen |
@@ -104,7 +104,7 @@ Lukee tiedoston polun `CI_CONF_FILE`-env-muuttujasta (oletus: `.gitea/workflows/
- Jokaisella `KEY=VALUE`-rivillä on arvo (ei tyhjää)
- URL-tyyppiset avaimet alkavat `http://` tai `https://`
- `GITEA_TOKEN` on asetettu
- `GIT_PAGES_PUBLISH_TOKEN` on asetettu
- `REPORTS_PUBLISH_TOKEN` on asetettu
---
@@ -155,7 +155,7 @@ dispatch-workflow.sh <target_repo> <workflow_file> <ref> <inputs_json> <gitea_ap
| `GITEA_API_URL` | `env_json` | `report-status.sh`, `ci-validate.sh` |
| `GIT_PAGES_URL` | `env_json` | `publish-git-pages.sh`, `report-status.sh` |
| `GITEA_TOKEN` | Gitea secret | `report-status.sh`, `check-version.yml`, `docker-build-push.yml` |
| `GIT_PAGES_PUBLISH_TOKEN` | Gitea secret | `publish-git-pages.sh` |
| `REPORTS_PUBLISH_TOKEN` | Gitea secret | `publish-git-pages.sh` |
| `GITHUB_REPOSITORY` | Automaattinen | Kaikki skriptit |
| `GITHUB_SHA` | Automaattinen | Kaikki skriptit |
| `GITHUB_RUN_ID` | Automaattinen | `report-status.sh` |
+7 -5
View File
@@ -32,7 +32,7 @@ Kaikki workflowt:
| Secret | Pakollinen | Kuvaus |
|--------|------------|--------|
| `GITEA_TOKEN` | Kyllä | Validointia varten |
| `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | Validointia varten |
| `REPORTS_PUBLISH_TOKEN` | Kyllä | Validointia varten |
**Outputs:**
@@ -103,7 +103,8 @@ joten `actions/checkout` toimii ilman node-asennuksia.
|-----------|------------|--------|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
| `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:**
@@ -124,9 +125,10 @@ build-push (helm package → helm push OCI) → tag-commit (git-tagin luonti)
**Steppien kuvaus `build-push`-jobissa:**
1. **Node.js-asennus**`apk add --no-cache nodejs` (vaaditaan `actions/checkout`-actionia varten)
2. **Checkout** — sovellusrepo ja gitea-ci-library `.ci/`-polkuun
3. **Package**`helm package` versiolla `$VERSION`
4. **Push OCI**`helm push` registryyn autentikoinnilla
5. **Report status** — commit-status + UI-linkki
3. **Resolve extra subchart dependencies**`helm dependency update` jokaiselle `extra_dependency_paths`-polulle (vain jos input on annettu)
4. **Package**`helm dependency update` + `helm package` versiolla `$VERSION`
5. **Push OCI**`helm push` registryyn autentikoinnilla
6. **Report status** — commit-status + UI-linkki
**Kompromissi:** Kontti `alpine/helm` ei sisällä node.js:ää, mutta
`actions/checkout@v4` on JavaScript-action ja vaatii sen. Siksi nodejs
-6
View File
@@ -1,6 +0,0 @@
apiVersion: v2
name: git-pages
description: Codeberg git-pages for CI HTML reports (apex site, Traefik BasicAuth publish)
type: application
version: 0.1.0
appVersion: "0.9.1"
-142
View File
@@ -1,142 +0,0 @@
# Architecture — git-pages
> Komponentit, datavirrat ja rajapinnat. Miksi näin on rakennettu: [design-rationale.md](design-rationale.md).
> Secretit: [secrets.md](secrets.md). Teknologiat: [tech-stack.md](tech-stack.md).
Tämä dokumentti koskee vain `git-pages/`-palvelua — ei juuren `gitea-ci-library`-kirjastoa.
---
## Yleiskuvaus
git-pages on jaettu **HTML-raporttiarkisto**: yksi apex-host, monta Gitea-repoa, commit-kohtaiset
raporttipolut. Julkaisija (esim. CI) puskaa sisällön tar-arkistona; lukija avaa raportin
selaimella commit-linkistä.
Codeberg git-pages ajaa `PAGES_INSECURE=1` — sovellus ei tee forge-authia. Julkaisu- ja
TLS-rajaukset ovat Kubernetes-kerroksessa (Traefik, cert-manager, Secretit).
---
## Komponentit
| Komponentti | Rooli |
|-------------|-------|
| **git-pages Pod** | Codeberg git-pages `0.9.1`, filesystem-storage `/app/data` |
| **retention sidecar** | Samassa podissa, HTTP API localhost:3000, siivoaa vanhat raportit |
| **PVC** | Raporttisisältö (storage v2 — `.index` + blob) |
| **Service** | ClusterIP :3000 git-pagesille |
| **Traefik IngressRoute** | Julkaisu (PATCH/PUT + BasicAuth) ja luku (GET/HEAD) eri säännöillä |
| **Traefik Middleware** | `git-pages-publish-auth` (BasicAuth), HTTPS-redirect |
| **cert-manager Certificate** | TLS → Secret `git-pages-tls` |
| Secret | Rooli |
|--------|-------|
| `git-pages-publish-auth` | htpasswd julkaisuun (Traefik) |
| `git-pages-publish-token` | plaintext token (Gitea Actions -secretiin vietäväksi) |
| `git-pages-retention-gitea` | Gitea PAT branch-tarkistukseen (sidecar)
---
## URL ja sisältö
Julkinen osoite:
```
https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/reports/f4baa286/cucumber/index.html
└────────── selvä URL ─────────┘ └───────────────── Gitea-yhteensopiva polku ─────────────────────────┘
```
Levyllä (apex index-site):
```
/app/data/site/{host}/
.index # Protobuf-manifesti (storage v2 — kaikki tiedostot tässä yhdessä tiedostossa)
```
Tiedostot eivät ole flat-FS:nä — katso `implementation-notes.md`.
Apex-juuri `/` on tyhjä — ei landing-sivua.
---
## Järjestelmäkaavio
```mermaid
flowchart TB
subgraph ext["Ulkoiset"]
PUB["Julkaisija\n(CI)"]
BR["Selain"]
GITEA["Gitea API\n(branch-lista)"]
end
subgraph edge["Reuna"]
TRAEFIK["Traefik\nIngressRoute + Middleware"]
CM["cert-manager\nTLS"]
end
subgraph cluster["git-pages Pod"]
GP["git-pages\n(kontti)"]
RT["retention sidecar\n(kontti)\nHTTP API localhost:3000"]
PVC["PVC /app/data"]
end
PUB -->|"PATCH/PUT + BasicAuth\ntar"| TRAEFIK
BR -->|"GET/HEAD"| TRAEFIK
TRAEFIK --> GP
CM --> TRAEFIK
GP --> PVC
RT -->|"reads .git-pages/manifest.json\nHTTP localhost"| GP
RT -->|"check branches"| GITEA
```
---
## Julkaisu
1. Julkaisija paketoi `{owner}/{repo}/reports/{sha8}/` tar-arkistoksi (sis. `.meta`)
2. `PATCH` tai `PUT` apex-URL:iin (`https://{host}/`) + `Content-Type: application/x-tar`
3. Traefik tarkistaa BasicAuth (`publish` + token) → välittää git-pagesille
4. git-pages kirjoittaa PVC:lle
Julkaisu kulkee aina julkisen ingressin kautta — ei suoraa ClusterIP-kirjoitusta ulkopuolelta.
---
## Luku
1. Selain avaa commit-statuslinkin (GET/HEAD)
2. Traefik välittää git-pagesille ilman julkaisu-Middlewarea
3. git-pages palauttaa HTML:n polusta
Luku-auth (OIDC) ei ole toteutettu — GET/HEAD on julkinen, jos URL tunnetaan.
Katso [design-rationale.md — Luku-auth](design-rationale.md#luku-auth).
---
## Retention
Sidecar-kontti samassa podissa, ajaa retention-cleanup.sh 24h välein:
1. Lukee `.git-pages/manifest.json` HTTP:lla localhost:3000
2. Etsii `.meta`-tiedostot, tarkistaa iän ja branchin
3. **Poistettu branch** — jos `.meta.branch` ei ole Giteassa → whiteout PATCH
4. **Aktiivinen branch**`maxAgeDays` + `keepMin` (`retention.rules`)
5. Whiteout-tar → PATCH localhost:3000 — poistaa raportit
Gitea API: `GET /api/v1/repos/{owner}/{repo}/branches/{branch}``read:repository` PAT.
Katso [secrets.md](secrets.md).
---
## Rajapinnat
| Suunta | Protokolla | Auth | Kuvaus |
|--------|------------|------|--------|
| Julkaisija → Traefik | HTTPS PATCH/PUT | BasicAuth `publish` | tar → apex site |
| Selain → Traefik | HTTPS GET/HEAD | — (tänään) | HTML-raportti |
| Sidecar → Gitea | HTTPS GET | PAT `read:repository` | branch-tarkistus per repo |
| Sidecar → git-pages | HTTP :3000 | — (PAGES_INSECURE) | manifestin luku + whiteout PATCH |
| Traefik → git-pages | HTTP :3000 | — | sisäverkko |
git-pages ei käytä Gitea forge-API:a julkaisuun eikä `pages`-branchia.
-198
View File
@@ -1,198 +0,0 @@
# Design Rationale — git-pages
> Miksi git-pages on rakennettu näin. Arvot, periaatteet ja reunaehdot.
>
> Tämä dokumentti on **normatiivinen** `git-pages/`-alikansiolle. Se ei kuvaa juuren
> `gitea-ci-library`-kirjastoa eikä sen workfloweja.
>
> Liittyvät dokumentit: [architecture.md](architecture.md), [tech-stack.md](tech-stack.md), [secrets.md](secrets.md).
---
## Miksi tämä on olemassa
### Ongelma
CI-testiajoista syntyy HTML-raportteja. Esimerkiksi Cucumber-testiraportti toimii
elävänä dokumentaationa git commitin tilasta.
Gitea ei tarjoa web-selaimella selattavaa arkistoa näille HTML-raporteille.
git-pages ratkaisee tämän ongelman.
| Vaihtoehto | Miksi ei riitä |
|---|---|
| **Gitea Actions -artifactit** | Vain ZIP-lataus — HTML ei renderöidy selaimessa |
| **Gitea `pages`-branch** | Yksi branch per repo; rinnakkaiset buildit törmäävät saman branchin pushissa |
| **Gitea Releases** | Sotkee julkaisuhistorian satojen CI-buildien raporteilla |
### Ongelma URL:ssa (hylätty malli)
Alkuvaiheen malli sitoi hostin repoon: `https://{owner}.{host}/{repo}/...`
(subdomain per owner). Julkinen linkki piti sitten “kääntää” Gitea-tyyliseksi poluksi
Traefik-rewritellä (`/{owner}/{repo}/...` → eri `Host` + lyhyempi polku).
Tämä oli ongelmallinen:
- per-owner middleware / rewrite kube-resursseina
- julkaisu-URL ja lukemis-URL eri muodossa
- wildcard-TLS tai monimutkainen cert-hallinta
- vaikea selittää kehittäjälle mistä host tulee
### Ratkaisu — `selvä_url` + Gitea-yhteensopiva polku
URL rakennetaan kahdesta erillisestä osasta, ei yhdestä sekavasta kaavasta:
| Osa | Mistä | Esimerkki |
|-----|-------|-----------|
| **Selvä URL** | Organisaation kiinteä pages-host | `https://pages.example.com` |
| **Gitea-yhteensopiva polku** | Repo (`{owner}/{repo}`) + commit | `/acme-corp/backend-api/reports/abc12345/index.html` |
**Julkinen linkki** = selvä URL + polku (yksi merkkijono commit-statusiin, ei rewritea):
`https://pages.example.com/acme-corp/backend-api/reports/abc12345/index.html`
Polku vastaa Gitea Pages -käytäntöä (`/{owner}/{repo}/...`). Host on aina sama —
ei `{owner}.pages...`-subdomainia.
**Konkreettinen esimerkki (nykyinen ympäristö):**
| Elementti | Arvo |
|-----------|------|
| **Gitea-instanssi** | `gitea.app.keskikuja.site` |
| **Repo** | `niko/gitea-ci-library` |
| **Haara** | `plan/0003-alkaa-käyttämään-itseään-commit-raportti` |
| **Commit SHA** | `14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb` |
| **Raportin nimi** | `cucumber` (esim.) |
| **Gitea commit -URL** | `https://gitea.app.keskikuja.site/niko/gitea-ci-library/commit/14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb` |
| **Raportin julkinen URL** | `https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/commit/14cf2eaeed8a4033bc37c52b0b4c29f25b253ceb/cucumber/index.html` |
Tämä varmistaa, että CI-statuslinkki on suoraan luettavissa ilman domain-rewriteä: raportin polku peilaa täsmälleen Gitean commit-polun rakennetta (`/{owner}/{repo}/commit/{sha}/{raportin-nimi}/`). Koska yksi ajo tuottaa useita raportteja, raportin nimi erottaa ne toisistaan.
Julkaisija (CI tai muu asiakas) lähettää tar-arkiston PATCH/PUT:lla. Lukija hakee
HTML:n GET:llä. Ei Gitea-git-integraatiota eikä `pages`-branchia.
**Codebergin security-malli ei sovellu tähän käyttöön** — forge-auth (Gitea PAT +
`write:repository`), DNS TXT -haaste ja muut git-pagesin sisäänrakennetut valtuutus-
mekanismit on ohitettu kokonaan (`PAGES_INSECURE=1`). Niiden sijaan Kubernetes-kerros
hoitaa rajauksen: Traefik BasicAuth julkaisuun, cert-manager TLS:ään, erillinen
publish-token ([secrets.md](secrets.md)). Sovellus palvelee sisältöä; klusteri päättää
kuka saa kirjoittaa.
---
## Suunnitteluperiaatteet
### 1. Selvä URL + Gitea-yhteensopiva polku
Julkinen osoite = kiinteä apex-host + polku `/{owner}/{repo}/reports/{sha8}/...`.
Apex-juuri `/` on tyhjä tarkoituksella — ei landing-sivua.
**Miksi:** Kehittäjä näkee Gitea-tyylisen polun; infra näkee yhden hostin. Ei Traefik-
rewritea, ei per-owner subdomaineja, ei erillistä “julkaisu-URL vs. lukemis-URL” -kaavaa.
Yksi TLS-sertifikaatti, yksi IngressRoute, yksi PVC.
### 2. Sovelluksen sisäinen security kytketty pois, Traefik hoitaa rajauksen
`git-pages`-sovelluksen koko sisäinen security-mekanismi on kytketty pois päältä (`PAGES_INSECURE=1`). Kirjoitusoikeuden validointi tapahtuu yksinomaan Kubernetes-reunalla Traefik BasicAuth -middlewaren avulla. Sovellus palvelee sisältöä sokeana; klusteri päättää, kuka saa kirjoittaa.
### 3. Julkaisu ja luku erotettu
Julkaisu (PATCH/PUT) vaatii Traefik BasicAuthin. Luku (GET/HEAD) on erillinen reitti — katso [Luku-auth](#luku-auth) alla.
**Miksi:** Koska sovellus ei validoi julkaisuoikeuksia, kirjoitusoikeus on eksplisiittisesti eriytetty Traefik Middlewaressä (`git-pages-publish-auth`).
### 4. Yksi publish-token, kaksi säilöä
Sama plaintext-token: klusterin Secretissä htpasswd-hashina, julkaisijan secret-holvissa
(esim. CI-alustan Actions-secret).
**Miksi:** Ei Gitea PAT:ia eikä `write:repository` -oikeutta. Token antaa vain
julkaisuoikeuden tähän palveluun. Yksi arvo, kaksi paikkaa — ks. [secrets.md](secrets.md).
### 5. Secretit erillisessä hallinnassa
`git-pages-publish-auth` luodaan ennen käyttöönottoa — ei osana sovelluksen konfiguraatiotiedostoja.
**Miksi:** Salaisuudet eivät kulje versionoiduissa arvoissa. Rotaatio ja SealedSecrets
pysyvät operaattorin hallussa. Ks. [secrets.md](secrets.md).
### 6. Minimaalinen parametrisointi
Instance-arvot (`host`, `issuer`, PVC) `{env}-values.yaml`:ssa. Resurssinimet,
secret-nimet ja Traefik-wire kovakoodattu templatessa.
**Miksi:** Parametrisoi vain se, mikä vaihtelee instanssien välillä (host, TLS-issuer,
levy). Vakioidut nimet ja wire pysyvät ennustettavina kaikissa asennuksissa.
---
## Puutteet
Tietoisesti avoimet asiat — eivät estä nykyistä julkaisu- ja lukumallia.
### Luku-auth
Julkaisu on suojattu (Traefik BasicAuth). **Luku ei ole:** GET/HEAD on julkinen — kuka
tuntee URL:n voi lukea raportin.
Tavoite: Traefik OIDC GET/HEAD-reitille (Gitea OAuth2 -provider). Session säilyy —
commit-statuslinkki toimii kirjautumisen jälkeen ilman uutta julkaisuoikeutta.
Ei toteutettu. Julkaisu- ja luku-reitit pysyvät erillisinä; OIDC lisätään vain lukupuolelle.
### Retention
Sidecar samassa podissa (HTTP localhost:3000), ajaa retention-cleanup.sh
24h välein:
| Sääntö | Konfiguroitavissa? | Kuvaus |
|--------|-------------------|--------|
| **Poistettu branch** | Ei — aina | Jos `.meta.branch` ei ole Giteassa enää, raportti poistetaan |
| **maxAgeDays** | Kyllä (`dev-values`) | Aktiivisen branchin raportit vanhemmat kuin N päivää |
| **keepMin** | Kyllä (`dev-values`) | Aktiivisella branchilla pidetään vähintään N uusinta |
Poistettujen branchien siivous ei tarvitse parametreja. Jäljelle jääneille
branchille säännöt tulevat `retention.rules` (`branches.default` +
`branches.{name}`).
Ei PVC-skaalausta — sidecar lukee manifestin HTTP:lla ja poistaa whiteout
PATCHilla. Ei K8s API -oikeuksia.
Secret: `git-pages-retention-gitea` — Gitea PAT branch-tarkistukseen.
Ks. [secrets.md](secrets.md).
---
## Rajat
- **Ei forge-integraatiota** — ei `pages`-branchia, ei Gitea API -hakua, ei forge-authia
- **Ei julkaisijalogiikkaa** — kuka julkaisee ja milloin on julkaisijan vastuulla
- **Ei sisäverkon ohitusjulkaisua** — julkaisu kulkee julkisen ingressin kautta (BasicAuth)
---
## Teknologiavalinnat
| Valinta | Miksi |
|---------|-------|
| **Codeberg git-pages** `0.9.1` | Natiivi apex index-site + tar-pohjainen PATCH/PUT -julkaisu |
| **Filesystem + PVC** | Yksinkertainen, yksi replica, ei erillistä objektivarastoa |
| **Traefik IngressRoute + Middleware** | Julkaisuauth erillään sovelluksesta; GET/HEAD eri säännöllä |
| **cert-manager** | TLS automaattisesti (`git-pages-tls`) |
| **Helm v3** | Toistettava asennus; instanssikohtaiset arvot erillisessä values-tiedostossa |
---
## Mitä tietoisesti hylättiin
| Hylätty | Syy |
|---------|-----|
| **deadnews/gitea-pages** | Vetää sisällön Gitea API:sta — ei sopinut CI-push-malliin |
| **Gitea `pages`-branch** | Race condition rinnakkaisissa buildeissa |
| **Per-owner subdomain** (`{owner}.pages...`) | Ongelmallinen URL; vaatii rewrite-middlewarea polun kääntämiseen |
| **Traefik path→host -rewrite** | Korvattu apex + Gitea-polulla — yksi selvä URL commit-linkissä |
| **Gitea forge-auth / PAT** | `write:repository` liian laaja oikeus vain raporttijulkaisuun |
| **DNS TXT -haaste julkaisuun** | Operatiivinen kompleksisuus ilman hyötyä BasicAuthiin verrattuna |
| **Helm-managed publish Secret** | Arvot values-tiedostoihin on kielletty; manuaalinen lähde totuudelle |
| **Image tag `v0.9.1`** | Oikea tagi on `0.9.1` (ei `v`-etuliitettä) |
-43
View File
@@ -1,43 +0,0 @@
# Implementation Notes
Teknisiä huomioita git-pages 0.9.1:n käyttäytymisestä, joita ei ole ilmeistä
dokumentaatiosta.
## Storage v2 (Protobuf manifest)
Git-pages 0.9.1 käyttää v2-arkkitehtuuria. Kaikki sisältö on pakattu
Protobuf-manifestiin (`site/{host}/.index`), ei flat-FS:nä. Tästä seuraa:
- Tiedostoja ei voi etsiä tai poistaa `find`/`rm`-komennoilla
- `.git-pages/manifest.json` listaa kaikki tiedostot (ProtoJSON)
- `.git-pages/archive.tar` antaa koko sisällön (saattaa truncata HTTP/2:ssa)
## Host-header
Git-pages valitsee sivuston Host-headerin perusteella. Ilman oikeaa Hostia
palauttaa 404.
- Ulkoiset requestit: Traefik välittää alkuperäisen Hostin automaattisesti
- Sidecar/localhost: `-H "Host: ci-reports.helm-dev.keskikuja.site"`
## PATCH ja directory-entryt
Jos PATCH-tar sisältää directory-entryn (tyyppi directory, tar typeflag '5'),
se **korvaa** koko hakemiston dokumentaation mukaan. Tar saa sisältää vain
file- ja symlink-entryjä, jotta PATCH toimii odotetusti.
## Whiteout — tiedostojen poisto
Ainoa tapa poistaa tiedostoja ilman koko sivuston PUT-korvausta:
- Tarissa character device entry (`CHRTYPE`, tar typeflag '4')
- `devmajor=0`, `devminor=0`
- PATCH:ataan sivuston juureen
## .init — ensimmäinen julkaisu
Ensimmäinen julkaisu vaatii PUTin, joka luo `.index`-tiedoston. Tämän jälkeen
PATCH riittää.
Helm-chartin post-install -job hoitaa tämän automaattisesti:
consumerien publish-scriptien ei tarvitse tuntea asennuksen tilaa.
-237
View File
@@ -1,237 +0,0 @@
# Secrets — git-pages
## Quick Start
### Vaihe 1: Secret-arkkitehtuuri
Järjestelmässä on kaksi loogista salaista arvoa. Publish-token jaetaan kahteen K8s-secretiin (Traefik-yhteensopivuus):
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` (htpasswd) | `git-pages-publish-auth` (users) | - |
| `report_publish_api_token` (plaintext) | `git-pages-publish-token` (token) | Actions Secret: `GIT_PAGES_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `git-pages-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
**Huomio:** Publish-token jaetaan kahteen secretiin, koska Traefik BasicAuth middleware vaatii single-key secretin sekä on muodossa, missä sitä ei saa takaisin. Jokainen repo mikä raportteja käyttää, tarvitsee selväkielisen arvon, joka on "ylimääräisessä" secretissä.
### Vaihe 2: Luo Gitea PAT (retention)
**Avaa Gitea browserissa:**
1. Kirjaudu Gitea-käyttäjällä, jolla on luku kaikkiin raporttirepoihin
2. **Settings****Applications****Generate New Token**
3. Token name: `CI-REPORTS_READ_FOR_RETENTION`
4. Scopes: valitse vain **`read:repository`**
5. **Generate Token****kopioi token heti** (näytetään vain kerran)
6. Tallenna token talteen (`GITEA_RETENTION_TOKEN`)
### Vaihe 3: Generoi publish-token
**Palaa terminaalille:**
```bash
GITEA_RETENTION_TOKEN="<from Gitea>"
GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)"
echo "Publish-token generoitu. Tallennetaan K8s-secretiin Vaiheessa 4."
echo "$GIT_PAGES_PUBLISH_TOKEN"
```
### Vaihe 4: Luo K8s secrets
```bash
NS=git-pages
# 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin)
kubectl create secret generic git-pages-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$GIT_PAGES_PUBLISH_TOKEN")" \
-n "$NS"
# 2. Publish-token: plaintext (luetaan README:stä Giteaan viedessä)
kubectl create secret generic git-pages-publish-token \
--from-literal=token="$GIT_PAGES_PUBLISH_TOKEN" \
-n "$NS"
# 3. Retention (käyttää Vaiheessa 2 luotua PAT:ia)
kubectl create secret generic git-pages-retention-gitea \
--from-literal=token="$GITEA_RETENTION_TOKEN" \
-n "$NS"
kubectl get secrets -n "$NS"
```
---
### Seuraava: Helm-asennus
Palaa takaisin [README.md](../README.md#käyttöönotto) ja jatka kohdasta "Instanssin values-tiedosto".
---
## Secret Arkkitehtuuri
### Loogiset salaisuudet
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` | `git-pages-publish-auth` (htpasswd) | Actions Secret: `GIT_PAGES_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `git-pages-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
### Secret Reference Architecture
```mermaid
graph TD
subgraph "Publish Flow"
P1["Actions Secret<br/>GIT_PAGES_PUBLISH_TOKEN"]
P2["K8s Secret<br/>git-pages-publish-auth"]
P1 -->|token| TRAEFIK
P2 -->|htpasswd| TRAEFIK
TRAEFIK["Traefik BasicAuth"]
end
subgraph "Retention Flow"
R1["K8s Secret<br/>git-pages-retention-gitea"]
R2["Gitea PAT<br/>CI-REPORTS_READ_FOR_RETENTION"]
R1 -->|token| SC["Sidecar"]
SC -->|API auth| GITEA["Gitea API"]
SC -->|read branches| GITEA
end
```
---
## Data Flow
### Flow 1: Julkaisu (Publish)
```mermaid
sequenceDiagram
participant Actions as Gitea Actions
participant Traefik as Traefik
participant K8sAuth as K8s Secret<br/>git-pages-publish-auth
participant K8sToken as K8s Secret<br/>git-pages-publish-token
participant GP as git-pages
Note over Actions: 1. Lue plaintext-token
Actions->>K8sToken: lue token-avain
K8sToken-->>Actions: plaintext token
Note over Actions: 2. Lähettää raportin
Actions->>Traefik: PUT / + BasicAuth<br/>publish:TOKEN + repo-url
Traefik->>K8sAuth: lue users (htpasswd)
K8sAuth-->>Traefik: publish:$apr1$...
alt Token match
Traefik->>GP: välitä
GP-->>Traefik: 200 OK
Traefik-->>Actions: 200 OK
else Token ei match
Traefik-->>Actions: 401 Unauthorized
end
```
**Kaksi secretiä (Traefik-yhteensopivuus):**
- `git-pages-publish-auth` = `users` (htpasswd, Traefik käyttää)
- `git-pages-publish-token` = `token` (plaintext, luetaan Giteaan viedessä)
---
### Flow 2: Luku (Read)
```mermaid
sequenceDiagram
participant Browser as Selain
participant Traefik as Traefik
participant GP as git-pages
Browser->>Traefik: GET /OWNER/REPO/commit/SHA/raportti/index.html
Traefik->>GP: välitä (ei authia)
GP-->>Traefik: HTML
Traefik-->>Browser: HTML
```
GET/HEAD-reitillä ei ole Middlewarea. Luku on julkinen, jos URL tunnetaan.
---
### Flow 3: Retention (Siivous)
```mermaid
sequenceDiagram
participant Sidecar as Retention Sidecar
participant K8sSecret as K8s Secret<br/>git-pages-retention-gitea
participant GiteaAPI as Gitea API
participant GP as git-pages (localhost:3000)
Note over Sidecar: 1. Lue PAT
Sidecar->>K8sSecret: lue token
K8sSecret-->>Sidecar: Gitea PAT
Note over Sidecar: 2. Lue manifest
Sidecar->>GP: GET .git-pages/manifest.json
GP-->>Sidecar: sisällysluettelo
Note over Sidecar: 3. Kysy branch
Sidecar->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH
GiteaAPI-->>Sidecar: 200 / 404
Note over Sidecar: 4. Luo whiteout-tar + PATCH
Sidecar->>GP: PATCH / (whiteout)
GP-->>Sidecar: 200 OK
```
**Huomio:** Retention-PAT:in omistajalla on oltava lukuoikeus KAIKKIIN repoihin,
joista raportteja on PVC:llä.
---
## Troubleshooting
- **"secret not found"** — luiko secretit ennen Helm-asennusta?
- **"401 Unauthorized"** — onko Gitea Actions secret oikea?
- **"found 2 elements for secret"** — Traefik vaatii single-key secretin. Varmista että `git-pages-publish-auth` sisältää vain `users`-avaimen.
- **"token hukkuu"** — generoi uusi token (Vaihe 3) ja päivitä molemmat publish-secretit:
```bash
# 1. Generoi uusi
GIT_PAGES_PUBLISH_TOKEN="$(openssl rand -base64 24)"
# 2. Päivitä K8s secrets (molemmat)
NS=git-pages
kubectl delete secret git-pages-publish-auth -n "$NS"
kubectl delete secret git-pages-publish-token -n "$NS"
kubectl create secret generic git-pages-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$GIT_PAGES_PUBLISH_TOKEN")" \
-n "$NS"
kubectl create secret generic git-pages-publish-token \
--from-literal=token="$GIT_PAGES_PUBLISH_TOKEN" \
-n "$NS"
# 3. Päivitä Gitea Actions secret jokaisessa repoissa (luke README:stä)
```
## Automatisointi: useamman repon salaisuuden lisääminen
Jos repoja on monta, voit käyttää Gitea API:ta (vaatii admin-tokenin):
```bash
ADMIN_TOKEN="<gitea-admin-token>"
NS=git-pages
# Lue plaintext-token erillisestä secretistä
TOKEN=$(kubectl get secret git-pages-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
for repo in "owner/repo1" "owner/repo2" "owner/repo3"; do
curl -X POST "https://gitea.example.com/api/v1/repos/$repo/actions/secrets" \
-H "Authorization: token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"GIT_PAGES_PUBLISH_TOKEN\",\"data\":\"$TOKEN\"}"
done
```
Tai `tea` CLI:lla (Gitea:n virallinen CLI):
```bash
tea actions secrets add --repo owner/repo1 GIT_PAGES_PUBLISH_TOKEN "$TOKEN"
tea actions secrets add --repo owner/repo2 GIT_PAGES_PUBLISH_TOKEN "$TOKEN"
```
-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."
-16
View File
@@ -1,16 +0,0 @@
git-pages installed.
See docs/secrets.md for secret prerequisites (K8s + Gitea Actions).
Install:
helm upgrade --install git-pages ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml
Host: https://{{ .Values.ingress.host }}
Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/reports/{sha8}/index.html
Publish (CI):
PATCH https://{{ .Values.ingress.host }}/
Authorization: Basic publish:<GIT_PAGES_PUBLISH_TOKEN>
Upgrade: helm upgrade {{ .Release.Name }} ./git-pages -n {{ .Release.Namespace }} -f dev-values.yaml
Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }}
-20
View File
@@ -1,20 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "git-pages.fullname" . }}-config
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
data:
config.toml: |
log-format = "text"
[server]
pages = "tcp/:3000"
caddy = "-"
metrics = "tcp/:3002"
[storage]
type = "fs"
[storage.fs]
root = "/app/data"
-120
View File
@@ -1,120 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "git-pages.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "git-pages.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "git-pages.componentLabels" . | nindent 8 }}
spec:
securityContext:
fsGroup: 1000
containers:
- name: git-pages
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- git-pages
args:
- -config
- /etc/git-pages/config.toml
{{- if .Values.pagesInsecure }}
env:
- name: PAGES_INSECURE
value: "1"
{{- end }}
ports:
- name: http
containerPort: 3000
protocol: TCP
- name: metrics
containerPort: 3002
protocol: TCP
volumeMounts:
- name: config
mountPath: /etc/git-pages
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 10
periodSeconds: 20
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if and .Values.persistence.enabled .Values.retention.enabled (eq .Values.retention.mode "sidecar") }}
- name: retention
image: "{{ .Values.retention.image.repository }}:{{ .Values.retention.image.tag }}"
imagePullPolicy: {{ .Values.retention.image.pullPolicy }}
securityContext:
runAsUser: 0
command:
- bash
- -c
- |
set -euo pipefail
echo "Retention sidecar: installing deps..."
apt-get update -qq
apt-get install -y --no-install-recommends curl jq python3 >/dev/null
echo "Retention sidecar: ready"
while true; do
/scripts/retention-cleanup.sh
echo "Retention sidecar: next run in 24h"
sleep 86400
done
env:
- name: PAGES_URL
value: http://localhost:3000
- name: PAGES_HOST
value: {{ .Values.ingress.host | quote }}
- name: RETENTION_CONFIG
value: /etc/retention/retention.json
- name: GITEA_API_URL
value: {{ .Values.retention.giteaApiUrl | quote }}
- name: GITEA_TOKEN
valueFrom:
secretKeyRef:
name: git-pages-retention-gitea
key: token
volumeMounts:
- name: retention-scripts
mountPath: /scripts
- name: retention-config
mountPath: /etc/retention
{{- end }}
volumes:
- name: config
configMap:
name: {{ include "git-pages.fullname" . }}-config
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "git-pages.fullname" . }}-data
{{- end }}
{{- if and .Values.persistence.enabled .Values.retention.enabled (eq .Values.retention.mode "sidecar") }}
- name: retention-scripts
configMap:
name: git-pages-retention
defaultMode: 0755
- name: retention-config
configMap:
name: git-pages-retention
items:
- key: retention.json
path: retention.json
{{- end }}
-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 }}
-22
View File
@@ -1,22 +0,0 @@
{{- if .Values.ingress.enabled }}
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "git-pages.fullname" . }}-publish-auth
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
basicAuth:
secret: git-pages-publish-auth
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "git-pages.fullname" . }}-https-redirect
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
redirectScheme:
scheme: https
permanent: true
{{- end }}
-15
View File
@@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "git-pages.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
{{- include "git-pages.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
+5
View File
@@ -0,0 +1,5 @@
apiVersion: v2
name: gitea-reports
description: Nginx + upload-sidecar for CI HTML reports (static serving, Traefik BasicAuth publish)
type: application
version: 0.2.0
+54 -26
View File
@@ -1,4 +1,4 @@
# git-pages
# gitea-reports
Jaettu **Gitea CI -raporttien** tallennus- ja lukupaikka: HTML-raportit (esim. Cucumber)
commit-kohtaisiin polkuihin, selaimella avattavina linkkeinä Gitean commitin CI-job
@@ -21,39 +21,39 @@ Luo secretit ennen Helm-asennusta: [docs/secrets.md](docs/secrets.md)
```bash
# Muokkaa oma values-tiedosto (tai käytä dev-values.yaml)
cp git-pages/dev-values.yaml my-values.yaml
cp gitea-reports/dev-values.yaml my-values.yaml
# Muuta: ingress.host, certificate.issuerRef.name, jne.
```
### 3. Helm-asennus
```bash
NS=git-pages
VALUES=my-values.yaml
NS=gitea-reports
VALUES=gitea-reports/dev-values.yaml
helm upgrade --install git-pages ./git-pages \
helm upgrade --install gitea-reports ./gitea-reports \
-n "$NS" --create-namespace \
-f "$VALUES"
helm template gitea-reports ./gitea-reports -n "$NS" -f "$VALUES"
```
Helm ajaa asennuksen jälkeen init-jobin, joka PUTtaa paikanpitäjäsivun
git-pagesiin. Tämä luo tarvittavan `.index`-tiedoston — sen jälkeen
Gitea Actions -scriptit voivat käyttää suoraan PATCHia ilman
PUT-fallbackia.
Nginx ja upload-sidecar käynnistyvät suoraan ilman init-vaihetta.
PV on tyhjä ennen ensimmäistä CI-julkaisua.
---
## Vie publish-token Gitea Actions-secretiin (per repo)
⚠️ **Tehtävä jokaiselle repoille**, joka julkaisee raportteja git-pagesiin.
⚠️ **Tehtävä jokaiselle repoille**, joka julkaisee raportteja gitea-reportsiin.
```bash
NS=git-pages
NS=gitea-reports
REPO_OWNER="niko"
REPO_NAME="gitea-ci-library"
# 1. Lue plaintext-token erillisestä secretistä
TOKEN=$(kubectl get secret git-pages-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
TOKEN=$(kubectl get secret gitea-reports-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
# 2. Kopioi leikepöydälle
echo -n "$TOKEN" | pbcopy # macOS
@@ -64,7 +64,7 @@ open "https://gitea.app.keskikuja.site/${REPO_OWNER}/${REPO_NAME}/settings/actio
# Linux: xdg-open "https://gitea.app.keskikuja.site/${REPO_OWNER}/${REPO_NAME}/settings/actions/secrets"
```
**Gitea UI:ssa:** New Secret → Name: `GIT_PAGES_PUBLISH_TOKEN` → Value: **liitä leikepöydältä** → Save
**Gitea UI:ssa:** New Secret → Name: `REPORTS_PUBLISH_TOKEN` → Value: **liitä leikepöydältä** → Save
> 💡 **Monelle repoille:** Toista vaiheet 34, tai katso [automatisointi](docs/secrets.md#automatisointi-useamman-repon-salaisuuden-lis%C3%A4%C3%A4miseen).
@@ -113,16 +113,16 @@ Esimerkki dev-ympäristöstä: [dev-values.yaml](dev-values.yaml).
Repojuuresta (sama `NS` kuin [docs/secrets.md](docs/secrets.md)):
```bash
NS=git-pages
VALUES=git-pages/dev-values.yaml
NS=gitea-reports
VALUES=gitea-reports/dev-values.yaml
helm upgrade --install git-pages ./git-pages \
helm upgrade --install gitea-reports ./gitea-reports \
-n "$NS" --create-namespace \
-f "$VALUES"
helm template git-pages ./git-pages -f "$VALUES"
helm template gitea-reports ./gitea-reports -f "$VALUES"
```
---
## CI-julkaisu
@@ -131,21 +131,49 @@ Julkaisu DNS-osoitteeseen BasicAuthilla:
```bash
# Esimerkki: julkaise raportti
curl -X PATCH https://ci-reports.helm-dev.keskikuja.site/owner/repo/commit/sha8/ \
-H "Authorization: Basic $(echo -n "publish:$GIT_PAGES_PUBLISH_TOKEN" | base64)" \
-H "Content-Type: application/x-tar" \
--data-binary @raportti.tar
curl -X PUT \
-u publish:$REPORTS_PUBLISH_TOKEN \
-H "Content-Type: application/tar+gz" \
--data-binary @report.tar.gz \
https://ci-reports.helm-dev.keskikuja.site/{owner}/{repo}/{branch}/{sha8}/{suite}/
```
**Vaaditut asetukset:**
| Missä | Arvo |
|-------|------|
| Gitea Actions secret | `GIT_PAGES_PUBLISH_TOKEN` (sama kuin K8s `git-pages-publish-token` `token`-avain) |
| Scriptissä | `curl` käyttää BasicAuth-headeria yllä |
| Gitea Actions secret | `REPORTS_PUBLISH_TOKEN` (sama kuin K8s `gitea-reports-publish-token` `token`-avain) |
| Scriptissä | `bash scripts/publish-gitea-reports.sh <suite>` |
**K8s secretit (Traefik-yhteensopivuus):**
- `git-pages-publish-auth` = htpasswd (Traefik BasicAuth middleware)
- `git-pages-publish-token` = plaintext (luetaan Giteaan viedessä)
- `gitea-reports-publish-auth` = htpasswd (Traefik BasicAuth middleware)
- `gitea-reports-publish-token` = plaintext (luetaan Giteaan viedessä)
Tarkemmat secret-ohjeet: [docs/secrets.md](docs/secrets.md).
---
## Testaus
Retention-logiikalle on unit-testit, jotka testaa funktiot ja Phase 3 -säännöt
erikseen ilman ulkoisia riippuvuuksia.
```bash
cd gitea-reports
bats tests/retention.bats
```
Testit käyttävät `<root>/files/retention-lib.sh` -jaettua kirjastoa, jota myös
`retention-cleanup.sh` sourceaa.
**Vaatimukset:** `bats`, `jq`, `date` (GNU date tai BSD date ISO 8601 -tuella).
---
## Retention
Ylläpitoscripti, joka poistaa vanhat raportit PV:ltä suoraan retentionsääntöjen mukaan.
Ajetaan CronJobina Kubernetesissa.
Retention lukee PV:tä suoraan (find, ls, stat), tarkistaa branchin olemassaolon Giteasta,
ja poistaa vanhat commit-kansiot `rm -rf`-komentona. Ei HTTP API:a retentionia varten.
@@ -1,5 +1,5 @@
# Dev instance — overrides values.yaml constants.
# helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml
# helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml
ingress:
host: ci-reports.helm-dev.keskikuja.site
@@ -19,11 +19,11 @@ retention:
rules:
branches:
default:
maxAgeDays: 90
minAgeDays: 3
keepMin: 5
main:
maxAgeDays: 365
keepMin: 20
minAgeDays: 3
keepMin: 10
master:
maxAgeDays: 365
keepMin: 20
minAgeDays: 3
keepMin: 10
+82
View File
@@ -0,0 +1,82 @@
# Architecture — Nginx + upload-sidecar
> gitea-reports on korvattu. Uusi arkkitehtuuri: Nginx palvelee staattisia tiedostoja PV:ltä, upload-sidecar (busybox httpd + CGI) ottaa vastaan tar.gz-PUTit.
## Yleiskuvaus
Yksi apex-host, monta Gitea-repoa. URL = FS-polku: `/{owner}/{repo}/{branch}/{sha8}/{suite}/`.
Ei Host-header-kikkaa, ei slugia, ei rewritea.
## Komponentit
| Komponentti | Rooli |
|-------------|-------|
| **nginx** | Palvelee staattisia tiedostoja /app/data -rootista (:8080) |
| **upload** (Python HTTP server) | Vastaanottaa PUT /path + tar.gz body, purkaa PV:lle (:8081) |
| **PVC** | Raporttisisältö suoraan FS-muodossa |
| **Service** | ClusterIP :8080 (read), :8081 (write) |
| **Traefik IngressRoute** | PATCH/PUT → upload:8081 (BasicAuth), GET/HEAD → nginx:8080 |
| **Traefik Middleware** | `gitea-reports-publish-auth` (BasicAuth), HTTPS-redirect |
| **cert-manager Certificate** | TLS |
## URL ja sisältö
```
https://ci-reports.helm-dev.keskikuja.site/niko/gitea-ci-library/main/f4baa286/cucumber/index.html
└── selvä URL ──┘ └─────────── Gitea-yhteensopiva polku ──────────────────────────┘
```
Levyllä:
```
/app/data/{owner}/{repo}/{branch}/{sha8}/
.meta
index.html
...
```
## Järjestelmäkaavio
```mermaid
flowchart TB
subgraph ext["Ulkoiset"]
PUB["Julkaisija\n(CI)"]
BR["Selain"]
GITEA["Gitea API\n(branch-lista)"]
end
subgraph edge["Reuna"]
TRAEFIK["Traefik\nIngressRoute + Middleware"]
CM["cert-manager\nTLS"]
end
subgraph cluster["Pod"]
NGINX["nginx\n(kontti)"]
UPLOAD["upload sidecar\n(Python HTTP server)"]
PVC["PVC /app/data"]
end
PUB -->|"PUT + BasicAuth\ntar.gz"| TRAEFIK
BR -->|"GET/HEAD"| TRAEFIK
TRAEFIK -->|"PUT/PATCH"| UPLOAD
TRAEFIK -->|"GET/HEAD"| NGINX
CM --> TRAEFIK
UPLOAD -->|"extract to PV"| PVC
NGINX --> PVC
```
## Julkaisu
1. Julkaisija paketoi tiedostot tar.gz:ksi
2. `PUT https://{host}/{owner}/{repo}/{branch}/{sha8}/{suite}/`
3. Traefik tarkistaa BasicAuth → välittää upload-sidecarille
4. CGI purkaa tar.gz:n suoraan PV:lle: `/app/data/{owner}/{repo}/{branch}/{sha8}/{suite}/`
## Luku
1. Selain avaa URL:n (GET/HEAD)
2. Traefik välittää nginxille
3. Nginx palauttaa suoraan PV:ltä
## Retention
CronJob lukee PV:tä suoraan (find, ls, stat), tarkistaa branchin olemassaolon Giteasta, poistaa vanhat commit-kansiot.
+90
View File
@@ -0,0 +1,90 @@
# Design Rationale — gitea-reports
Miksi gitea-reports on rakennettu näin. Periaatteet, jotka pitävät arkkitehtuurin koossa.
Teknologiavalinnat ovat näiden periaatteiden seurauksia — eivät erillisiä preferenssejä.
Tämä dokumentti on normatiivinen. Arkkitehtuurin tulee noudattaa sen periaatteita.
Ehdotus, joka on ristiriidassa dokumentin kanssa, on konflikti — ei hiljainen ohitus.
## The problem this solves
CI-testiajoista syntyy HTML-raportteja. Gitea ei tarjoa web-selaimella selattavaa arkistoa
näille raporteille.
Vaihtoehdot eivät sovi:
- **Gitea Actions artifactit**: vain ZIP-lataus — HTML ei renderöidy selaimessa
- **Gitea pages-branch**: yksi branch per repo; rinnakkaiset buildit törmäävät
- **Gitea Releases**: sotkee julkaisuhistorian sadoilla CI-buildien raporteilla
## How this is solved
Yksi klusteri, yksi apex-host, monta Gitea-repoa. URL on suoraan FS-polku:
`/{owner}/{repo}/{branch}/{sha8}/{suite}/`. Ei rewritea, ei subdomain-per-owner,
ei slugia.
Palvelu on read/write -jaettu: luku ja julkaisu eri sovelluksilla, eri porteilla,
eri Traefik-säännöillä.
## Why selvä URL + Gitea-yhteensopiva polku
Repo tunnistetaan polusta `/{owner}/{repo}/...`, ei hostista. Kaikki URL-taso
(branch, sha8, suite) on suoraan FS-polku. Tämä mahdollistaa yhden TLS-sertifikaatin,
yhden IngressRouten ja URL:n, joka on suoraan kopioitavissa commit-statusiin ilman
rewritea.
**Hylätty:** subdomain-per-owner (`{owner}.pages...`). Vaati per-owner Traefik-
rewritea ja wildcard-TLS:ää. Julkaisu-URL ja luku-URL olivat eri muodossa — kehittäjälle
vaikea ymmärtää ja debugata.
## Why luku ja kirjoitus eri sovelluksilla
Yksi binary, joka palvelee sekä lukua että kirjoitusta samassa prosessissa, on
yksinkertainen mutta yhdistää kaksi eri asiaa. Tässä arkkitehtuurissa nginx palvelee
staattisia tiedostoja; Python-skripti purkaa tar.gz-julkaisun PV:lle. Ne eivät tiedä
toisistaan.
Luku ei tarvitse julkaisulogiikkaa. Julkaisu ei tarvitse optimoitua staattista
palvelua. Erottamalla ne kumpikin voidaan valita parhaaseen tarkoitukseensa.
## Why security klusterin reunalla — ei sovelluksessa
Sovellus on "tyhmä": se palvelee mitä PV:llä on ja kirjoittaa mitä sille annetaan.
Valtuutus tapahtuu yksinomaan Traefik BasicAuth -middlewaressa.
Tämä mahdollistaa:
- Julkaisu- ja lukuoikeuksien erillisen hallinnan eri Traefik-säännöillä
- Sovelluksen vaihtamisen ilman turvallisuusauditointia
- Token-rotaation ilman sovelluksen uudelleenkäynnistystä
**Hylätty:** sovelluksen sisäinen forge-auth (Gitea PAT, DNS TXT -haaste).
Sitoo sovelluksen tiettyyn autentikointimekanismiin ja vaatii laajat oikeudet
(`write:repository`) pelkkää raporttijulkaisua varten.
## Why retention erillisenä CronJobina
Retention ei tarvitse olla aina käynnissä — se ajetaan kerran vuorokaudessa.
CronJob lukee PV:tä suoraan (find, ls, stat), ei HTTP API:n kautta. Tämä pitää
retention poissa podin kriittiseltä polulta (luku/julkaisu).
**Hylätty:** sidecar samassa podissa. Sidecar kuluttaa resursseja aina, ja
retention-logiikka sekoittuu julkaisu- ja lukukoodiin.
## Why yksi token, kaksi säilöä
Sama plaintext-arvo: htpasswd-hashina K8s Secretissä (Traefik lukee), plaintextina
Gitea Actions Secretissä (julkaisija lukee). Yksi rotaatio päivittää molemmat.
Token antaa vain julkaisuoikeuden tähän palveluun — ei Gitea PAT:ia eikä
`write:repository`-oikeutta.
## Why PVC suoraan FS-muodossa
Ei objektivarastoa, ei HTTP API:a retentionille. Sisältö on suoraan luettavissa
kubectl exec + ls/find. Yksinkertaisin mahdollinen tallennusmuoto. Yksi replica,
ei synkronointia.
## References
- [architecture.md](architecture.md) — komponentit ja vuokaavio
- [tech-stack.md](tech-stack.md) — teknologiavalinnat listana
- [secrets.md](secrets.md) — secret-arkkitehtuuri ja käyttöönotto
+267
View File
@@ -0,0 +1,267 @@
# Secrets — gitea-reports
## Quick Start
### Vaihe 1: Secret-arkkitehtuuri
Järjestelmässä on kaksi loogista salaista arvoa. Publish-token jaetaan kahteen K8s-secretiin (Traefik-yhteensopivuus):
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` (htpasswd) | `gitea-reports-publish-auth` (users) | - |
| `report_publish_api_token` (plaintext) | `gitea-reports-publish-token` (token) | Actions Secret: `REPORTS_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `gitea-reports-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
**Huomio:** Publish-token jaetaan kahteen secretiin, koska Traefik BasicAuth middleware vaatii single-key secretin sekä on muodossa, missä sitä ei saa takaisin. Jokainen repo mikä raportteja käyttää, tarvitsee selväkielisen arvon, joka on "ylimääräisessä" secretissä.
### Vaihe 2: Luo Gitea PAT (retention)
**Avaa Gitea browserissa:**
1. Kirjaudu Gitea-käyttäjällä, jolla on luku kaikkiin raporttirepoihin
2. **Settings****Applications****Generate New Token**
3. Token name: `CI-REPORTS_READ_FOR_RETENTION`
4. Scopes: valitse vain **`read:repository`**
5. **Generate Token****kopioi token heti** (näytetään vain kerran)
6. Tallenna token talteen (`GITEA_RETENTION_TOKEN`)
### Vaihe 3: Generoi publish-token
**Palaa terminaalille:**
```bash
GITEA_RETENTION_TOKEN="<from Gitea>"
REPORTS_PUBLISH_TOKEN="$(openssl rand -base64 24)"
echo "Publish-token generoitu. Tallennetaan K8s-secretiin Vaiheessa 4."
echo "$REPORTS_PUBLISH_TOKEN"
```
### Vaihe 4: Luo K8s secrets
```bash
NS=gitea-reports
# 1. Publish-auth: htpasswd (Traefik BasicAuth - vaatii single-key secretin)
kubectl create secret generic gitea-reports-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$REPORTS_PUBLISH_TOKEN")" \
-n "$NS"
# 2. Publish-token: plaintext (luetaan README:stä Giteaan viedessä)
kubectl create secret generic gitea-reports-publish-token \
--from-literal=token="$REPORTS_PUBLISH_TOKEN" \
-n "$NS"
# 3. Retention (käyttää Vaiheessa 2 luotua PAT:ia)
kubectl create secret generic gitea-reports-retention-gitea \
--from-literal=token="$GITEA_RETENTION_TOKEN" \
-n "$NS"
kubectl get secrets -n "$NS"
```
---
### Vaihe 5: Vie publish-token Gitea Actions-secretiin
Token näkyy terminaalissa Vaiheen 3 `echo`sta. Jos se on jo kadonnut näkyvistä, hae se K8s-secretistä:
```bash
NS=gitea-reports
kubectl get secret gitea-reports-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d; echo
```
Vie kopioitu secret Gitea Actions Secrets:
Kannattaa viedä tämä token ORG tason secretiksi Gitea Actions. Muuten joudut tekemään tämän jokaiselle rapositorylle erikseen.
Luo `REPORTS_PUBLISH_TOKEN` secret, ja terminaalista kopioitu arvo `values` kenttään.
Description voi olla esim:
> Julkaisuun liittyvä salaisuus, jonka gitea-pages auth tarkistaa, että saako julkaista raporttia.
**Gitea UI:ssa:** New Secret → Name: `REPORTS_PUBLISH_TOKEN` → Value: **liitä token** → Save
Toista jokaiselle repolle, joka julkaisee raportteja.
---
### Seuraava: Helm-asennus
Palaa takaisin [README.md](../README.md#käyttöönotto) ja jatka kohdasta "Instanssin values-tiedosto".
---
## Secret Arkkitehtuuri
### Loogiset salaisuudet
| Looginen nimi | K8s | Gitea |
|---|---|---|
| `report_publish_api_token` | `gitea-reports-publish-auth` (htpasswd) | Actions Secret: `REPORTS_PUBLISH_TOKEN` |
| `reports_retention_read_token` | `gitea-reports-retention-gitea` (token) | PAT: `CI-REPORTS_READ_FOR_RETENTION` |
### Secret Reference Architecture
```mermaid
graph TD
subgraph "Publish Flow"
P1["Actions Secret<br/>REPORTS_PUBLISH_TOKEN"]
P2["K8s Secret<br/>gitea-reports-publish-auth"]
P1 -->|token| TRAEFIK
P2 -->|htpasswd| TRAEFIK
TRAEFIK["Traefik BasicAuth"]
end
subgraph "Retention Flow"
R1["K8s Secret<br/>gitea-reports-retention-gitea"]
R2["Gitea PAT<br/>CI-REPORTS_READ_FOR_RETENTION"]
R1 -->|token| SC["Sidecar"]
SC -->|API auth| GITEA["Gitea API"]
SC -->|read branches| GITEA
end
```
---
## Data Flow
### Flow 1: Julkaisu (Publish)
```mermaid
sequenceDiagram
participant Actions as Gitea Actions
participant Traefik as Traefik
participant K8sAuth as K8s Secret<br/>gitea-reports-publish-auth
participant K8sToken as K8s Secret<br/>gitea-reports-publish-token
participant Upload as upload-sidecar
participant PV as PVC /app/data
Note over Actions: 1. Lue plaintext-token
Actions->>K8sToken: lue token-avain
K8sToken-->>Actions: plaintext token
Note over Actions: 2. Lähettää raportin
Actions->>Traefik: PUT /{polku} + BasicAuth<br/>publish:TOKEN + tar.gz
Traefik->>K8sAuth: lue users (htpasswd)
K8sAuth-->>Traefik: publish:$apr1$...
alt Token match
Traefik->>Upload: PUT /{path}
Upload->>PV: extract tar.gz
PV-->>Upload: 201 Created
Upload-->>Traefik: 201 Created
Traefik-->>Actions: 201 Created
else Token ei match
Traefik-->>Actions: 401 Unauthorized
end
```
**Kaksi secretiä (Traefik-yhteensopivuus):**
- `gitea-reports-publish-auth` = `users` (htpasswd, Traefik käyttää)
- `gitea-reports-publish-token` = `token` (plaintext, luetaan Giteaan viedessä)
---
### Flow 2: Luku (Read)
```mermaid
sequenceDiagram
participant Browser as Selain
participant Traefik as Traefik
participant Nginx as nginx
participant PV as PVC /app/data
Browser->>Traefik: GET /OWNER/REPO/branch/SHA/suite/index.html
Traefik->>Nginx: välitä (ei authia)
Nginx->>PV: lue tiedosto
PV-->>Nginx: HTML
Nginx-->>Traefik: HTML
Traefik-->>Browser: HTML
```
GET/HEAD-reitillä ei ole Middlewarea. Luku on julkinen, jos URL tunnetaan.
---
### Flow 3: Retention (Siivous)
```mermaid
sequenceDiagram
participant CronJob as Retention CronJob
participant K8sSecret as K8s Secret<br/>gitea-reports-retention-gitea
participant GiteaAPI as Gitea API
participant PV as PVC /app/data
Note over CronJob: 1. Lue PAT
CronJob->>K8sSecret: lue token
K8sSecret-->>CronJob: Gitea PAT
Note over CronJob: 2. Lue PV suoraan
CronJob->>PV: ls /app/data/{owner}/{repo}/{branch}/
PV-->>CronJob: commit-kansiot
Note over CronJob: 3. Kysy branch
CronJob->>GiteaAPI: GET /api/v1/repos/OWNER/REPO/branches/BRANCH
GiteaAPI-->>CronJob: 200 / 404
Note over CronJob: 4. Poista vanhat
CronJob->>PV: rm -rf vanhat commit-kansiot
```
**Huomio:** Retention-PAT:in omistajalla on oltava lukuoikeus KAIKKIIN repoihin,
joista raportteja on PVC:llä.
---
## Troubleshooting
- **"secret not found"** — luiko secretit ennen Helm-asennusta?
- **"401 Unauthorized"** — onko Gitea Actions secret oikea?
- **"found 2 elements for secret"** — Traefik vaatii single-key secretin. Varmista että `gitea-reports-publish-auth` sisältää vain `users`-avaimen.
- **"token hukkuu"** — generoi uusi token (Vaihe 3) ja päivitä molemmat publish-secretit:
```bash
# 1. Generoi uusi
REPORTS_PUBLISH_TOKEN="$(openssl rand -base64 24)"
# 2. Päivitä K8s secrets (molemmat)
NS=gitea-reports
kubectl delete secret gitea-reports-publish-auth -n "$NS"
kubectl delete secret gitea-reports-publish-token -n "$NS"
kubectl create secret generic gitea-reports-publish-auth \
--from-literal=users="$(docker run --rm httpd:2-alpine htpasswd -nb publish "$REPORTS_PUBLISH_TOKEN")" \
-n "$NS"
kubectl create secret generic gitea-reports-publish-token \
--from-literal=token="$REPORTS_PUBLISH_TOKEN" \
-n "$NS"
# 3. Päivitä Gitea Actions secret jokaisessa repoissa (luke README:stä)
```
## Automatisointi: useamman repon salaisuuden lisääminen
Jos repoja on monta, voit käyttää Gitea API:ta (vaatii admin-tokenin):
```bash
ADMIN_TOKEN="<gitea-admin-token>"
NS=gitea-reports
# Lue plaintext-token erillisestä secretistä
TOKEN=$(kubectl get secret gitea-reports-publish-token -n "$NS" -o jsonpath='{.data.token}' | base64 -d)
for repo in "owner/repo1" "owner/repo2" "owner/repo3"; do
curl -X POST "https://gitea.example.com/api/v1/repos/$repo/actions/secrets" \
-H "Authorization: token $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"REPORTS_PUBLISH_TOKEN\",\"data\":\"$TOKEN\"}"
done
```
Tai `tea` CLI:lla (Gitea:n virallinen CLI):
```bash
tea actions secrets add --repo owner/repo1 REPORTS_PUBLISH_TOKEN "$TOKEN"
tea actions secrets add --repo owner/repo2 REPORTS_PUBLISH_TOKEN "$TOKEN"
```
@@ -1,7 +1,7 @@
# Tech Stack — git-pages
# Tech Stack — gitea-reports
> Mitä teknologioita `git-pages/` Helm chart käyttää ja edellyttää. Tämä dokumentti koskee vain
> `git-pages/`-alikansiota monorepossa — ei juuren `gitea-ci-library`-kirjastoa.
> Mitä teknologioita `gitea-reports/` Helm chart käyttää ja edellyttää. Tämä dokumentti koskee vain
> `gitea-reports/`-alikansiota monorepossa — ei juuren `gitea-ci-library`-kirjastoa.
---
@@ -9,13 +9,9 @@
| Teknologia | Versio | Käyttö |
|---|---|---|
| **git-pages** (Codeberg) | `0.9.1` | Staattinen sisältö, apex index-site (`/.index`), HTTP PATCH/PUT -julkaisu |
| **Filesystem storage** | — | Sisältö PVC:llä (`/app/data`) |
| **TOML** | — | Sovellusconfig ConfigMapissa (`config.toml`) |
Image: `codeberg.org/git-pages/git-pages:0.9.1` (ei `v`-etuliitettä tagissa).
Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei forge-authia.
| **nginx** | alpine | Staattinen tiedostopalvelin (GET/HEAD) |
| **Python** | 3-alpine | PUT-vastaanotto + tar.gz-purku PV:lle |
| **Filesystem storage** | — | Sisältö PVC:llä |
---
@@ -26,7 +22,7 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f
| **Kubernetes** | 1.24+ | Deployment, Service, PVC, Secret |
| **Helm** | v3 | Chart asennus ja päivitys |
| **Traefik** | CRD `traefik.io/v1alpha1` | IngressRoute, Middleware (`basicAuth`, HTTPS-redirect) |
| **cert-manager** | — | TLS-sertifikaatti (`git-pages-tls`) |
| **cert-manager** | — | TLS-sertifikaatti (`gitea-reports-tls`) |
---
@@ -43,8 +39,8 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f
| Resurssi | Lähde | Dokumentti |
|---|---|---|
| `git-pages-publish-auth` | Manuaalinen Secret | [secrets.md](secrets.md) |
| `git-pages-tls` | cert-manager Certificate | Automaattinen asennuksessa |
| `gitea-reports-publish-auth` | Manuaalinen Secret | [secrets.md](secrets.md) |
| `gitea-reports-tls` | cert-manager Certificate | Automaattinen asennuksessa |
| `ClusterIssuer` | Klusteri (esim. `letsencrypt-prod`) | `dev-values.yaml` |
---
@@ -66,7 +62,7 @@ Chart ajaa `PAGES_INSECURE=1` — julkaisuvaltuutus Traefik Middlewaressä, ei f
| `values.yaml` | Chart-vakiot | Image, resurssit, Traefik entrypointit |
| `{env}-values.yaml` | Instanssi | Host, issuer, PVC koko/storageClass |
Esimerkki: `helm upgrade --install git-pages ./git-pages -n git-pages -f dev-values.yaml`
Esimerkki: `helm upgrade --install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml`
---
@@ -74,8 +70,8 @@ Esimerkki: `helm upgrade --install git-pages ./git-pages -n git-pages -f dev-val
| Teknologia | Syy |
|---|---|
| **Gitea `pages`-branch** | Ei Gitea git-pages -integraatiota |
| **Gitea `pages`-branch** | Ei Gitea gitea-reports -integraatiota |
| **deadnews/gitea-pages** | Ei Gitea API -hakua |
| **MinIO / S3** | Erillinen raporttivarasto — ei tämän chartin scope |
| **Gitea forge-auth / PAT** | Julkaisu BasicAuth-tokenilla (`git-pages-publish-auth`) |
| **Gitea forge-auth / PAT** | Julkaisu BasicAuth-tokenilla (`gitea-reports-publish-auth`) |
| **Helm-managed publish Secret** | `publishAuth.create: false` — secret manuaalisesti |
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Retention cleanup: read PV directly, no git-pages API.
# Removes old commit-level report dirs based on branch existence + age rules.
set -euo pipefail
DATA_ROOT="${DATA_ROOT:-/app/data}"
CONFIG="${RETENTION_CONFIG:-/etc/retention/retention.json}"
GITEA_API_URL="${GITEA_API_URL:-}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
[ -f "$CONFIG" ] || { echo "ERROR: config missing: $CONFIG" >&2; exit 1; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/retention-lib.sh"
DELETED_COUNT=0
ERROR_COUNT=0
for owner_dir in "$DATA_ROOT"/*/; do
[ -d "$owner_dir" ] || continue
owner=$(basename "$owner_dir")
for repo_dir in "$owner_dir"*/; do
[ -d "$repo_dir" ] || continue
repo=$(basename "$repo_dir")
for branch_dir in "$repo_dir"*/; do
[ -d "$branch_dir" ] || continue
branch=$(basename "$branch_dir")
echo "=== ${owner}/${repo}/${branch} ==="
if ! branch_exists "$owner" "$repo" "$branch"; then
echo " Branch does not exist in Gitea -> DELETE ALL"
rm -rf "$branch_dir"
DELETED_COUNT=$((DELETED_COUNT + 1))
continue
fi
rules=$(jq -r ".branches.\"$branch\" // .branches.default" "$CONFIG")
keep_min=$(echo "$rules" | jq -r '.keepMin // 5')
min_age=$(echo "$rules" | jq -r '.minAgeDays // 7')
commits=$(ls -1t "$branch_dir" 2>/dev/null || true)
count=0
for sha8 in $commits; do
[ -d "$branch_dir/$sha8" ] || continue
count=$((count + 1))
[ "$count" -le "$keep_min" ] && continue
age=$(age_days_from_stat "$branch_dir/$sha8")
[ "$age" -lt "$min_age" ] && continue
echo " DELETE: ${owner}/${repo}/${branch}/${sha8} (age=${age}d, keepMin=${keep_min})"
rm -rf "$branch_dir/$sha8"
DELETED_COUNT=$((DELETED_COUNT + 1))
done
done
done
done
echo ""
echo "=== Summary ==="
echo " Deleted: ${DELETED_COUNT}"
echo " Errors: ${ERROR_COUNT}"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Shared functions for retention-cleanup.sh
# Can be sourced by tests for unit testing
age_days_from_stat() {
local dir="$1" epoch_dir now
epoch_dir=$(stat -c %Y "$dir" 2>/dev/null || stat -f %m "$dir" 2>/dev/null || echo 0)
[ "$epoch_dir" -eq 0 ] && echo 99999 && return
now=$(date +%s)
echo $(( (now - epoch_dir) / 86400 ))
}
read_rule() {
local config="$1" branch="$2" key="$3" default="$4"
v=$(jq -r --arg b "$branch" --arg k "$key" '.branches[$b][$k] // empty' "$config")
[ -n "$v" ] && echo "$v" || echo "$default"
}
branch_exists() {
local owner="$1" repo="$2" branch="$3"
local branch_urlenc code
[ -z "$GITEA_API_URL" ] && return 0
[ -z "$GITEA_TOKEN" ] && return 0
branch_urlenc=$(echo "$branch" | sed 's/\//%2F/g')
code=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token $GITEA_TOKEN" \
"$GITEA_API_URL/api/v1/repos/$owner/$repo/branches/$branch_urlenc" 2>/dev/null || echo "000")
[ "$code" = "200" ]
}
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Run retention cleanup on PVC directly (no git-pages API).
set -euo pipefail
/scripts/retention-cleanup.sh
echo "Retention job done."
+19
View File
@@ -0,0 +1,19 @@
Nginx + upload-sidecar installed.
See docs/secrets.md for secret prerequisites (K8s + Gitea Actions).
Install:
helm upgrade --install gitea-reports ./gitea-reports -n {{ .Release.Namespace }} --create-namespace -f dev-values.yaml
Host: https://{{ .Values.ingress.host }}
Reports: https://{{ .Values.ingress.host }}/{owner}/{repo}/{branch}/{sha8}/{suite}/
Publish (CI):
curl -X PUT \
-u publish:<token> \
-H "Content-Type: application/tar+gz" \
--data-binary @report.tar.gz \
https://{{ .Values.ingress.host }}/{owner}/{repo}/{branch}/{sha8}/{suite}/
Upgrade: helm upgrade {{ .Release.Name }} ./gitea-reports -n {{ .Release.Namespace }} -f dev-values.yaml
Uninstall: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }}
@@ -1,8 +1,8 @@
{{- define "git-pages.name" -}}
{{- define "gitea-reports.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "git-pages.fullname" -}}
{{- define "gitea-reports.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
@@ -15,25 +15,25 @@
{{- end }}
{{- end }}
{{- define "git-pages.chart" -}}
{{- define "gitea-reports.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "git-pages.labels" -}}
helm.sh/chart: {{ include "git-pages.chart" . }}
{{ include "git-pages.selectorLabels" . }}
{{- define "gitea-reports.labels" -}}
helm.sh/chart: {{ include "gitea-reports.chart" . }}
{{ include "gitea-reports.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "git-pages.selectorLabels" -}}
app.kubernetes.io/name: {{ include "git-pages.name" . }}
{{- define "gitea-reports.selectorLabels" -}}
app.kubernetes.io/name: {{ include "gitea-reports.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "git-pages.componentLabels" -}}
{{ include "git-pages.labels" . }}
{{- define "gitea-reports.componentLabels" -}}
{{ include "gitea-reports.labels" . }}
app.kubernetes.io/component: pages-server
{{- end }}
@@ -2,11 +2,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ include "git-pages.fullname" . }}-tls
name: {{ include "gitea-reports.fullname" . }}-tls
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
secretName: git-pages-tls
secretName: {{ include "gitea-reports.fullname" . }}-tls
dnsNames:
- {{ .Values.ingress.host | quote }}
issuerRef:
+43
View File
@@ -0,0 +1,43 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "gitea-reports.fullname" . }}-config
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
data:
default.conf: |
client_max_body_size {{ .Values.nginx.bodySize }};
server {
listen {{ .Values.nginx.port }};
root /app/data;
autoindex on;
client_max_body_size {{ .Values.nginx.bodySize }};
location / {
try_files $uri $uri/ $uri/index.html =404;
}
}
upload-server.py: |
#!/usr/bin/env python3
import http.server, os, sys, tarfile, io, urllib.parse
PORT = int(os.environ.get("UPLOAD_PORT", "8081"))
DATA_ROOT = "/app/data"
class Handler(http.server.BaseHTTPRequestHandler):
def do_PUT(self):
path = urllib.parse.unquote(self.path)
dest = DATA_ROOT + path.rstrip("/")
os.makedirs(dest, exist_ok=True)
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
with tarfile.open(fileobj=io.BytesIO(body), mode="r:gz") as tar:
tar.extractall(dest)
self.send_response(201)
self.end_headers()
def log_message(self, fmt, *args):
sys.stderr.write("%s - %s\n" % (self.client_address[0], fmt % args))
http.server.HTTPServer(("", PORT), Handler).serve_forever()
+75
View File
@@ -0,0 +1,75 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "gitea-reports.fullname" . }}
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "gitea-reports.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "gitea-reports.componentLabels" . | nindent 8 }}
spec:
securityContext:
fsGroup: 1000
containers:
- name: nginx
image: "{{ .Values.nginx.image }}:{{ .Values.nginx.tag }}"
imagePullPolicy: IfNotPresent
ports:
- name: http-read
containerPort: {{ .Values.nginx.port }}
protocol: TCP
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
resources:
{{- toYaml .Values.nginx.resources | nindent 12 }}
- name: upload
image: "{{ .Values.upload.image }}:{{ .Values.upload.tag }}"
imagePullPolicy: IfNotPresent
command:
- python3
- /upload/upload-server.py
ports:
- name: http-write
containerPort: {{ .Values.upload.port }}
protocol: TCP
volumeMounts:
- name: upload-script
mountPath: /upload
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
resources:
{{- toYaml .Values.upload.resources | nindent 12 }}
volumes:
- name: nginx-conf
configMap:
name: {{ include "gitea-reports.fullname" . }}-config
items:
- key: default.conf
path: default.conf
- name: upload-script
configMap:
name: {{ include "gitea-reports.fullname" . }}-config
defaultMode: 0755
items:
- key: upload-server.py
path: upload-server.py
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "gitea-reports.fullname" . }}-data
{{- end }}
@@ -2,9 +2,9 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: {{ include "git-pages.fullname" . }}
name: {{ include "gitea-reports.fullname" . }}
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
entryPoints:
- {{ .Values.ingress.entryPoints.websecure }}
@@ -14,24 +14,24 @@ spec:
(Method(`PATCH`) || Method(`PUT`))
kind: Rule
middlewares:
- name: {{ include "git-pages.fullname" . }}-publish-auth
- name: {{ include "gitea-reports.fullname" . }}-publish-auth
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
- name: {{ include "gitea-reports.fullname" . }}
port: {{ .Values.upload.port }}
- match: Host(`{{ .Values.ingress.host }}`) && (Method(`GET`) || Method(`HEAD`))
kind: Rule
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
- name: {{ include "gitea-reports.fullname" . }}
port: {{ .Values.nginx.port }}
tls:
secretName: git-pages-tls
secretName: {{ include "gitea-reports.fullname" . }}-tls
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: {{ include "git-pages.fullname" . }}-http
name: {{ include "gitea-reports.fullname" . }}-http
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
entryPoints:
- {{ .Values.ingress.entryPoints.web }}
@@ -41,8 +41,8 @@ spec:
!PathPrefix(`/.well-known/acme-challenge/`)
kind: Rule
middlewares:
- name: {{ include "git-pages.fullname" . }}-https-redirect
- name: {{ include "gitea-reports.fullname" . }}-https-redirect
services:
- name: {{ include "git-pages.fullname" . }}
port: {{ .Values.service.port }}
- name: {{ include "gitea-reports.fullname" . }}
port: {{ .Values.nginx.port }}
{{- end }}
+22
View File
@@ -0,0 +1,22 @@
{{- if .Values.ingress.enabled }}
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "gitea-reports.fullname" . }}-publish-auth
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
basicAuth:
secret: {{ include "gitea-reports.fullname" . }}-publish-auth
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ include "gitea-reports.fullname" . }}-https-redirect
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
redirectScheme:
scheme: https
permanent: true
{{- end }}
@@ -2,9 +2,9 @@
apiVersion: v1
kind: Secret
metadata:
name: git-pages-publish-auth
name: {{ include "gitea-reports.fullname" . }}-publish-auth
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
type: Opaque
stringData:
users: |
@@ -2,9 +2,9 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "git-pages.fullname" . }}-data
name: {{ include "gitea-reports.fullname" . }}-data
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.accessMode }}
@@ -2,12 +2,14 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
data:
retention.json: |
{{- .Values.retention.rules | toJson | nindent 4 }}
retention-lib.sh: |
{{- .Files.Get "files/retention-lib.sh" | nindent 4 }}
retention-cleanup.sh: |
{{- .Files.Get "files/retention-cleanup.sh" | nindent 4 }}
retention-run.sh: |
@@ -2,9 +2,9 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
schedule: {{ .Values.retention.schedule | quote }}
concurrencyPolicy: Forbid
@@ -16,10 +16,10 @@ spec:
template:
metadata:
labels:
app.kubernetes.io/name: git-pages-retention
app.kubernetes.io/name: {{ include "gitea-reports.fullname" . }}-retention
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: git-pages-retention
serviceAccountName: {{ include "gitea-reports.fullname" . }}-retention
restartPolicy: OnFailure
containers:
- name: retention
@@ -34,15 +34,9 @@ spec:
set -euo pipefail
apt-get update -qq
apt-get install -y --no-install-recommends curl jq >/dev/null
chmod +x /scripts/retention-run.sh /scripts/retention-cleanup.sh
chmod +x /scripts/retention-run.sh
/scripts/retention-run.sh
env:
- name: NAMESPACE
value: {{ .Release.Namespace | quote }}
- name: DEPLOYMENT
value: {{ include "git-pages.fullname" . | quote }}
- name: INSTANCE
value: {{ .Release.Name | quote }}
- name: DATA_ROOT
value: /app/data
- name: RETENTION_CONFIG
@@ -52,7 +46,7 @@ spec:
- name: GITEA_TOKEN
valueFrom:
secretKeyRef:
name: git-pages-retention-gitea
name: {{ include "gitea-reports.fullname" . }}-retention-gitea
key: token
volumeMounts:
- name: data
@@ -64,14 +58,14 @@ spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ include "git-pages.fullname" . }}-data
claimName: {{ include "gitea-reports.fullname" . }}-data
- name: scripts
configMap:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
defaultMode: 0755
- name: config
configMap:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
items:
- key: retention.json
path: retention.json
@@ -2,16 +2,16 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale"]
@@ -23,15 +23,15 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
labels:
{{- include "git-pages.componentLabels" . | nindent 4 }}
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
subjects:
- kind: ServiceAccount
name: git-pages-retention
name: {{ include "gitea-reports.fullname" . }}-retention
namespace: {{ .Release.Namespace }}
{{- end }}
+19
View File
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "gitea-reports.fullname" . }}
labels:
{{- include "gitea-reports.componentLabels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
{{- include "gitea-reports.selectorLabels" . | nindent 4 }}
ports:
- name: http-read
port: {{ .Values.nginx.port }}
targetPort: http-read
protocol: TCP
- name: http-write
port: {{ .Values.upload.port }}
targetPort: http-write
protocol: TCP
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env bats
setup() {
source "$(dirname "$BATS_TEST_DIRNAME")/files/retention-lib.sh"
CONFIG=$(mktemp)
}
teardown() {
rm -f "$CONFIG"
}
write_config() {
cat > "$CONFIG"
}
# ---------------------------------------------------------------------------
# read_rule
# ---------------------------------------------------------------------------
@test "read_rule returns default when branch has no override" {
write_config <<'EOF'
{"branches":{"default":{"minAgeDays":90,"keepMin":5}}}
EOF
result=$(read_rule "$CONFIG" "nonexistent" "minAgeDays" 90)
[ "$result" = "90" ]
}
@test "read_rule returns branch-specific value" {
write_config <<'EOF'
{"branches":{"default":{"minAgeDays":90,"keepMin":5},"main":{"minAgeDays":365,"keepMin":20}}}
EOF
result=$(read_rule "$CONFIG" "main" "keepMin" 5)
[ "$result" = "20" ]
}
@test "read_rule returns default for undefined key even if branch exists" {
write_config <<'EOF'
{"branches":{"default":{"minAgeDays":90,"keepMin":5},"main":{"minAgeDays":365}}}
EOF
result=$(read_rule "$CONFIG" "main" "keepMin" 5)
[ "$result" = "5" ]
}
# ---------------------------------------------------------------------------
# age_days_from_stat
# ---------------------------------------------------------------------------
@test "age_days_from_stat returns 0 for newly created dir" {
local tmpdir=$(mktemp -d)
result=$(age_days_from_stat "$tmpdir")
[ "$result" -eq 0 ] || [ "$result" -eq 1 ]
rm -rf "$tmpdir"
}
@test "age_days_from_stat returns 99999 for nonexistent dir" {
result=$(age_days_from_stat "/nonexistent/path")
[ "$result" = "99999" ]
}
@test "age_days_from_stat returns positive for old dir" {
local tmpdir=$(mktemp -d)
# Touch mtime back 2 days
touch -t "$(date -v-2d +%Y%m%d%H%M.%S 2>/dev/null || date -d '2 days ago' +%Y%m%d%H%M.%S 2>/dev/null)" "$tmpdir" 2>/dev/null || true
result=$(age_days_from_stat "$tmpdir")
[ "$result" -ge 1 ]
rm -rf "$tmpdir"
}
# ---------------------------------------------------------------------------
# branch_exists — edge cases (no mock needed)
# ---------------------------------------------------------------------------
@test "branch_exists: empty GITEA_API_URL → return 0 (skip)" {
GITEA_API_URL=""
GITEA_TOKEN="test-token"
run branch_exists "owner" "repo" "any-branch"
[ "$status" -eq 0 ]
}
@test "branch_exists: empty GITEA_TOKEN → return 0 (skip)" {
GITEA_API_URL="https://gitea.example.com"
GITEA_TOKEN=""
run branch_exists "owner" "repo" "any-branch"
[ "$status" -eq 0 ]
}
# ---------------------------------------------------------------------------
# retention-cleanup integration — mock PV tree
# ---------------------------------------------------------------------------
@test "retention-cleanup: deletes only old commits beyond keepMin" {
local dataroot=$(mktemp -d)
local config=$(mktemp)
cat > "$config" <<'EOF'
{"branches":{"default":{"minAgeDays":0,"keepMin":2}}}
EOF
# Create mock PV: owner/repo/branch/{c1,c2,c3}
mkdir -p "$dataroot/owner/repo/main/c1/suite"
mkdir -p "$dataroot/owner/repo/main/c2/suite"
mkdir -p "$dataroot/owner/repo/main/c3/suite"
DATA_ROOT="$dataroot"
RETENTION_CONFIG="$config"
GITEA_API_URL=""
GITEA_TOKEN=""
# Run main loop (subset of retention-cleanup.sh logic)
for branch_dir in "$dataroot/owner/repo/main/"; do
branch=$(basename "$branch_dir")
rules=$(jq -r ".branches.\"$branch\" // .branches.default" "$config")
keep_min=$(echo "$rules" | jq -r '.keepMin // 5')
min_age=$(echo "$rules" | jq -r '.minAgeDays // 7')
commits=$(ls -1t "$branch_dir" 2>/dev/null || true)
count=0
for sha8 in $commits; do
[ -d "$branch_dir/$sha8" ] || continue
count=$((count + 1))
[ "$count" -le "$keep_min" ] && continue
age=$(age_days_from_stat "$branch_dir/$sha8")
[ "$age" -lt "$min_age" ] && continue
rm -rf "$branch_dir/$sha8"
done
done
# c1 (oldest, 3rd in ls -1t) should be deleted, c2 and c3 kept
[ -d "$dataroot/owner/repo/main/c2" ]
[ -d "$dataroot/owner/repo/main/c3" ]
[ ! -d "$dataroot/owner/repo/main/c1" ]
rm -rf "$dataroot" "$config"
}
@test "retention-cleanup: deletes entire branch dir when branch not in Gitea" {
local dataroot=$(mktemp -d)
mkdir -p "$dataroot/owner/repo/deleted-branch/c1/suite"
mkdir -p "$dataroot/owner/repo/active-branch/c2/suite"
DATA_ROOT="$dataroot"
RETENTION_CONFIG="/dev/null"
GITEA_API_URL=""
GITEA_TOKEN=""
# With empty GITEA_API_URL, branch_exists returns 0 → no deletion
# (can't mock Gitea 404 without external deps)
rm -rf "$dataroot"
}
@@ -1,33 +1,42 @@
# Constants — shared across all instances. Do not put per-env values here.
# Per instance: use {env}-values.yaml (e.g. dev-values.yaml):
# helm install git-pages ./git-pages -n git-pages -f dev-values.yaml
# helm install gitea-reports ./gitea-reports -n gitea-reports -f dev-values.yaml
nameOverride: ""
fullnameOverride: ""
image:
repository: codeberg.org/git-pages/git-pages
tag: "0.9.1"
pullPolicy: IfNotPresent
nginx:
image: nginx
tag: alpine
port: 8080
bodySize: 100M
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi
pagesInsecure: true
upload:
image: python
tag: 3-alpine
port: 8081
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
service:
type: ClusterIP
port: 3000
persistence:
enabled: true
accessMode: ReadWriteOnce
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
ingress:
enabled: true
entryPoints:
@@ -37,15 +46,6 @@ ingress:
certificate:
enabled: true
# Post-install init job: creates placeholder site so .index exists.
# Consumers can use PATCH directly without PUT fallback.
initJob:
enabled: true
image:
repository: debian
tag: bookworm-slim
pullPolicy: IfNotPresent
# Optional Helm-managed secret — prefer manual create (see docs/secrets.md).
publishAuth:
create: false
@@ -53,7 +53,7 @@ publishAuth:
retention:
enabled: false
mode: sidecar
mode: cronjob
schedule: "0 3 * * *"
image:
repository: debian
@@ -63,5 +63,5 @@ retention:
rules:
branches:
default:
maxAgeDays: 90
minAgeDays: 7
keepMin: 5
+5 -6
View File
@@ -81,11 +81,10 @@ generate_index() {
} > "$REPORT_DIR/index.html"
}
STAGED="reports/${SHA8}/${SUITE}"
mkdir -p "$STAGED"
mkdir -p "$SUITE"
if [ "$TOTAL" -eq 1 ]; then
cp -a "$REPORT_DIR/." "$STAGED/"
cp -a "$REPORT_DIR/." "$SUITE/"
sh .ci/scripts/publish-git-pages.sh "$SUITE"
first_entry=$(echo "$ENTRIES" | head -1)
@@ -98,13 +97,13 @@ if [ "$TOTAL" -eq 1 ]; then
SINGLE_ENTRY="${first_name}/index.html"
fi
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${SINGLE_ENTRY}"
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8}/${SUITE}/${SINGLE_ENTRY}"
sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "" "$URL"
else
generate_index
cp -a "$REPORT_DIR/." "$STAGED/"
cp -a "$REPORT_DIR/." "$SUITE/"
sh .ci/scripts/publish-git-pages.sh "$SUITE"
sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "$SUITE"
fi
rm -rf "$STAGED"
rm -rf "$SUITE"
+1 -1
View File
@@ -21,7 +21,7 @@ while IFS='=' read -r key value || [ -n "$key" ]; do
done < "$CONF_FILE"
[ -z "${GITEA_TOKEN:-}" ] && echo "ERROR: GITEA_TOKEN secret is not set" >&2 && ERRORS=1
[ -z "${GIT_PAGES_PUBLISH_TOKEN:-}" ] && echo "ERROR: GIT_PAGES_PUBLISH_TOKEN secret is not set" >&2 && ERRORS=1
[ -z "${REPORTS_PUBLISH_TOKEN:-}" ] && echo "ERROR: REPORTS_PUBLISH_TOKEN secret is not set" >&2 && ERRORS=1
if [ "$ERRORS" -ne 0 ]; then
echo "FATAL: CI config validation failed" >&2
+9 -1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
echo "gitops-dispatch: validating env vars..."
: "${GITOPS_FILE:?}"
: "${GITOPS_YQ_TPL:?}"
: "${GITOPS_VERSION:?}"
@@ -13,6 +14,7 @@ set -euo pipefail
TIMEOUT="${GITOPS_DISPATCH_TIMEOUT:-30}"
echo "gitops-dispatch: constructing inputs..."
INPUTS=$(jq -nc \
--arg file "$GITOPS_FILE" \
--arg yq_tpl "$GITOPS_YQ_TPL" \
@@ -20,9 +22,13 @@ INPUTS=$(jq -nc \
--arg source_repo "$GITOPS_SOURCE_REPO" \
--arg source_commit "$GITOPS_SOURCE_COMMIT" \
--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)"
echo "gitops-dispatch: dispatching to $GITOPS_REPO/$GITOPS_WORKFLOW..."
set +e
OUTPUT=$(bash "$DIR/dispatch-workflow.sh" \
"$GITOPS_REPO" "$GITOPS_WORKFLOW" "main" \
@@ -30,7 +36,9 @@ OUTPUT=$(bash "$DIR/dispatch-workflow.sh" \
EXIT=$?
set -e
echo "=== DISPATCH OUTPUT (exit=$EXIT) ==="
echo "$OUTPUT"
echo "=== END DISPATCH ==="
STATUS="failure"
GITOPS_SHA=""
+8 -3
View File
@@ -84,7 +84,12 @@ _gitops_update() {
cd "${CLONE_DIR}" || _gitops_fail "Failed to enter clone directory"
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
echo "No changes — ${INPUT_FILE} already at ${VERSION}"
@@ -93,8 +98,8 @@ _gitops_update() {
exit 0
fi
git -c user.name="gitea-ci-bot" \
-c user.email="ci@keskikuja.site" \
git -c user.name="${GIT_USER_NAME:-gitea-ci-bot}" \
-c user.email="${GIT_USER_EMAIL:-ci@keskikuja.site}" \
commit -m "[skip ci] gitops: update version to ${VERSION}" || _gitops_fail "Failed to commit"
GITOPS_SHA="$(git rev-parse HEAD)"
git push || _gitops_fail "Failed to push"
+40 -40
View File
@@ -4,54 +4,54 @@ set -eu
SUITE_PATH="${1:-}"
[ -n "$SUITE_PATH" ] || { echo "ERROR: suite_path argument required" >&2; exit 1; }
[ -n "${GITEA_API_URL:-}" ] || { echo "ERROR: GITEA_API_URL is not set" >&2; exit 1; }
[ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; }
[ -n "${GIT_PAGES_PUBLISH_TOKEN:-}" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${GITHUB_REPOSITORY:-}" ] || { echo "ERROR: GITHUB_REPOSITORY is not set" >&2; exit 1; }
[ -n "${GITHUB_SHA:-}" ] || { echo "ERROR: GITHUB_SHA is not set" >&2; exit 1; }
[ -n "${GITHUB_REF_NAME:-}" ] || { echo "ERROR: GITHUB_REF_NAME is not set" >&2; exit 1; }
OWNER="${GITHUB_REPOSITORY%%/*}"
REPO="${GITHUB_REPOSITORY##*/}"
SHA8=$(echo "$GITHUB_SHA" | cut -c1-8)
BRANCH="${GITHUB_REF_NAME}"
SUITE="${SUITE_PATH%/}"
PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}"
REPORT_DIR="reports/${SHA8}/${SUITE_PATH%/}"
REPORT_BASE="${GIT_PAGES_URL}/${OWNER}/${REPO}/reports/${SHA8}"
[ -d "$REPORT_DIR" ] || { echo "ERROR: not a directory: $REPORT_DIR" >&2; exit 1; }
[ -d "$SUITE" ] || { echo "ERROR: not a directory: $SUITE" >&2; exit 1; }
PUBLISH_SITE_URL="${GIT_PAGES_URL}/"
EMPTY=1
for _f in "$SUITE"/* "$SUITE"/.*; do
[ -e "$_f" ] && { EMPTY=0; break; }
done
[ "$EMPTY" -eq 0 ] || { echo "ERROR: no files to publish in $SUITE" >&2; exit 1; }
WORK=$(mktemp -d)
TAR=$(mktemp)
trap 'rm -rf "$WORK" "$TAR"' EXIT
RELPATH="${REPORT_DIR#reports/${SHA8}/}"
if [ "$RELPATH" != "$REPORT_DIR" ] && [ -n "$RELPATH" ]; then
TARGET="$WORK/${OWNER}/${REPO}/reports/${SHA8}/${RELPATH}"
else
TARGET="$WORK/${OWNER}/${REPO}/reports/${SHA8}"
fi
mkdir -p "$TARGET"
cp -a "$REPORT_DIR/." "$TARGET/"
# Kopioi raporttitiedostot
mkdir -p "$WORK/$SUITE"
cp -a "$SUITE/." "$WORK/$SUITE/"
if [ ! -f "$TARGET/index.html" ]; then
# Generoi index.html (sama logiikka kuin nykyään)
cd "$WORK/$SUITE"
if [ ! -f "index.html" ]; then
ITEM_LIST=""
ITEM_COUNT=0
for f in "$TARGET"/*; do
for f in *; do
[ -f "$f" ] || continue
base=$(basename "$f")
[ "$base" = "index.html" ] && continue
ITEM_LIST="${ITEM_LIST}file:${base}
[ "$f" = "index.html" ] && continue
ITEM_LIST="${ITEM_LIST}file:${f}
"
ITEM_COUNT=$((ITEM_COUNT + 1))
done
for d in "$TARGET"/*/; do
for d in */; do
[ -d "$d" ] || continue
base=$(basename "$d")
d="${d%/}"
[ -f "$d/index.html" ] || continue
ITEM_LIST="${ITEM_LIST}dir:${base}
ITEM_LIST="${ITEM_LIST}dir:${d}
"
ITEM_COUNT=$((ITEM_COUNT + 1))
done
@@ -82,36 +82,36 @@ if [ ! -f "$TARGET/index.html" ]; then
done
echo '</ul></body></html>'
} > "$TARGET/index.html"
} > "index.html"
fi
fi
cat > "$TARGET/.meta" <<EOF
{"branch":"${GITHUB_REF_NAME:-}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
# .meta tiedosto retentionia varten
cat > ".meta" <<EOF
{"branch":"${BRANCH}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF
find "$WORK/$OWNER" \( -type f -o -type l \) -print | sed "s|^${WORK}/||" | tar -cf "$TAR" -C "$WORK" -T -
publish() {
method="$1"
curl -sS -X "$method" "$PUBLISH_SITE_URL" \
-u "${PAGES_USER}:${GIT_PAGES_PUBLISH_TOKEN}" \
-H "Content-Type: application/x-tar" \
-H "Atomic: no" \
-H "Create-Parents: yes" \
--data-binary @"$TAR" \
-o /tmp/git-pages-publish-response.txt \
-w "%{http_code}"
}
cd "$WORK"
HTTP_CODE=$(publish PATCH)
# Pakkaa: pelkät tiedostot ilman $SUITE-etuliitettä
tar czf "$TAR" -C "$WORK/$SUITE" .
# PUT upload-sidecariin
PUBLISH_URL="${GIT_PAGES_URL}/${OWNER}/${REPO}/${BRANCH}/${SHA8}/${SUITE}/"
HTTP_CODE=$(curl -sS -X PUT "$PUBLISH_URL" \
-u "${PAGES_USER}:${REPORTS_PUBLISH_TOKEN}" \
-H "Content-Type: application/tar+gz" \
--data-binary @"$TAR" \
-o /tmp/git-pages-publish-response.txt \
-w "%{http_code}")
case "$HTTP_CODE" in
200|201|204) ;;
*)
echo "ERROR: git-pages publish HTTP ${HTTP_CODE}" >&2
echo "ERROR: publish HTTP ${HTTP_CODE}" >&2
cat /tmp/git-pages-publish-response.txt >&2
exit 1
;;
esac
echo "$REPORT_BASE"
echo "${PUBLISH_URL}"
+1 -1
View File
@@ -7,7 +7,7 @@ SUITE_PATH="${1:-}"
[ -n "${GITEA_API_URL:-}" ] || { echo "ERROR: GITEA_API_URL is not set" >&2; exit 1; }
[ -n "${GITEA_TOKEN:-}" ] || { echo "ERROR: GITEA_TOKEN is not set" >&2; exit 1; }
[ -n "${GIT_PAGES_URL:-}" ] || { echo "ERROR: GIT_PAGES_URL is not set" >&2; exit 1; }
[ -n "${GIT_PAGES_PUBLISH_TOKEN:-}" ] || { echo "ERROR: GIT_PAGES_PUBLISH_TOKEN is not set" >&2; exit 1; }
[ -n "${REPORTS_PUBLISH_TOKEN:-}" ] || { echo "ERROR: REPORTS_PUBLISH_TOKEN is not set" >&2; exit 1; }
SCRIPT_DIR="$(dirname "$0")"
+1 -1
View File
@@ -18,7 +18,7 @@ if [ -n "$CUSTOM_URL" ]; then
elif [ -n "$SUITE" ]; then
SUITE="${SUITE%/}/"
SHA8_CUT=$(echo "$GITHUB_SHA" | cut -c1-8)
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8_CUT}/${SUITE}"
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/${SHA8_CUT}/${SUITE}"
else
URL="${GITEA_API_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
fi
+1 -1
View File
@@ -473,7 +473,7 @@ VERSION_FILE=git-pages/Chart.yaml
| Secret | Pakollinen |
|---|---|
| `GITEA_TOKEN` | Aina (Gitean sisäinen, automaattisesti saatavilla) |
| `GIT_PAGES_PUBLISH_TOKEN` | Aina |
| `REPORTS_PUBLISH_TOKEN` | Aina |
| `DOCKER_USERNAME` | Vain jos buildaat kontteja |
| `DOCKER_PASSWORD` | Vain jos buildaat kontteja |
| `HELM_USER` | Vain jos pushaat Helm chartin OCI-rekisteriin (oletus `github.actor`) |
+21 -4
View File
@@ -368,9 +368,18 @@ Pakkaa ja pushee Helm-chartin OCI-registryyn. Käyttää `alpine/helm`-konttia.
```yaml
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ä:**
```yaml
@@ -382,11 +391,19 @@ helm-build-push:
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
chart_path: platform-helm
# extra_dependency_paths: subchart-a,subchart-b # tarvittaessa
```
Chart-hakemisto johdetaan `VERSION_FILE`-polusta: `dirname "${VERSION_FILE}"`.
Jos `VERSION_FILE` on `Chart.yaml`, konteksti on juuri. Jos `platform-helm/Chart.yaml`,
konteksti on `platform-helm/`.
`chart_path` on eksplisiittinen polku chart-hakemistoon (esim. `platform-helm`).
`VERSION_FILE` määrää version lähteen (`Chart.yaml:n` `version`-kenttä) —
nämä voivat olla eri polkuja, mutta tyypillisesti molemmat osoittavat samaan
chart-hakemistoon.
**`extra_dependency_paths`:** Jos chartilla on alikarttoja (subchartteja) jotka
vaativat `helm dependency update` -ajon ennen päächartin buildia, anna niiden
polut pilkulla eroteltuna. Provider ajaa `helm dependency update` jokaiselle
polulle ennen päächartin buildia.
**Yksittäisten Helm-UI-linkkien raportointi:** `HELM_UI_URL` on
tarkoitettu yleiselle registry UI:lle — provider muodostaa linkin
+139 -298
View File
@@ -1,10 +1,9 @@
---
name: gitops-update
description: |
Setting up GitOps version updates: GitOps-repo workflow template, code
repo dispatch, secret requirements, and two-repo commit-status pattern.
Activates when the user needs to wire up artifact builds to GitOps
configuration updates.
Getting GitOps configuration updates working for a consumer project —
GitOps repo setup, consumer pipeline wiring, secrets, and commit-status
output.
activation-gate: |
User mentions GitOps update, gitops-update, dispatch to another repo,
two-repo version bump, cross-repo deployment, or wiring build output to
@@ -13,47 +12,26 @@ category: ci
impact: high
---
# GitOps Update — Provider-palvelu
# GitOps Update — consumer setup
`scripts/gitops-update.sh` ja `scripts/dispatch-workflow.sh` muodostavat
GitOps-päivityspalvelun. Artifact buildataan code repossa, minkä jälkeen
code repo dispatchaa GitOps-repoon, joka päivittää konfiguraatiotiedoston
ja pushaa muutoksen.
## What you need
## 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)
│ 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
```
## 1. GitOps-repo setup
**Token-periaate:** Vain GitOps-repoon kirjoitetaan. Code repo asettaa
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`:
Create `.gitea/workflows/gitops-service.yaml`:
```yaml
name: GitOps Update
run-name: "GitOps Service (${{ inputs.dispatch_id || 'manual' }})"
run-name: "GitOps (${{ inputs.dispatch_id || 'manual' }})"
on:
workflow_dispatch:
inputs:
@@ -78,6 +56,15 @@ on:
git_tag_prefix:
required: false
type: string
extra_cmd:
required: false
type: string
author_name:
required: false
type: string
author_email:
required: false
type: string
env:
INPUT_FILE: ${{ inputs.file }}
@@ -86,27 +73,26 @@ env:
SOURCE_REPO: ${{ inputs.source_repo }}
SOURCE_COMMIT: ${{ inputs.source_commit }}
GITOPS_REPO: ${{ github.repository }}
GITOPS_BRANCH: ${{ github.ref_name }}
GITEA_API_URL: ${{ gitea.server_url }}
GIT_TAG_PREFIX: ${{ inputs.git_tag_prefix || '' }}
GITOPS_EXTRA_CMD: ${{ inputs.extra_cmd || '' }}
GIT_USER_NAME: ${{ inputs.author_name || '' }}
GIT_USER_EMAIL: ${{ inputs.author_email || '' }}
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Install yq
run: |
wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq
- name: Run GitOps update
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
@@ -114,297 +100,152 @@ jobs:
bash .ci/scripts/gitops-update.sh
```
**Huomiot:**
- `GITEA_TOKEN` on Gitean auto-token — scopeutuu GitOps-repoon, riittää
cloneen, committiin, pushiin ja commit-statusiin GitOps-repossa
- `run-name` ja `dispatch_id` mahdollistavat dispatchaavan skriptin tunnistaa
tämän workflow-runin yksiselitteisesti `display_title`-kentästä, vaikka
samassa repossa olisi samanaikaisia ajoja
- yq ladataan lennossa (kompromissi, ks. "Tuleva CI-kontti")
> **⚠️ yq ladataan lennossa.** Tämä on väliaikainen kompromissi. Myöhemmin
> julkaistaan Docker Hubiin custom CI-kontti, jossa nodejs + git + yq
> valmiina. Sama patterni kuin `ci-bats` ja `ci-cucumber`.
> Ks. `skills/ci-container-build/SKILL.md`.
### 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
gitops-update:
needs: [helm-build-push]
needs: [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: 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"
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.version.outputs.version }}
GITOPS_FILE: dev/Chart.yaml
GITOPS_YQ_TPL: '.version = "{{VERSION}}"'
GITOPS_REPO: niko/your-gitops-repo
```
### 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.
Kumpikin artefakti dispatchaa oman GitOps-päivityksensä rinnakkain:
To run extra commands (e.g. `helm dependency update`) after the version bump and before the commit:
```yaml
gitops-helm:
needs: [helm-build-push]
gitops-update:
needs: [load-config, check-version, helm-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 helm version
id: helm
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"
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
GITOPS_FILE: Chart.yaml
GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "agent-platform-helm") | .version) = "{{VERSION}}"'
GITOPS_REPO: niko/agent-platform-gitops
GITOPS_EXTRA_CMD: helm dependency update
```
Kaksi dispatchia, kaksi eri tiedostoa, kaksi eri `GIT_TAG_PREFIX`-arvoa.
Kummallakin on oma commit-status-linja ja oma summary-rivi.
`dispatch-workflow.sh` hoitaa rinnakkaisuuden `display_title`-matchauksella.
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.
**GITEA_TOKEN dispatch-vaiheessa:** Tarvitaan manuaalinen token,
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.
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.
### Commit-status dispatchin perusteella
### 2.3 Parameters
`dispatch-workflow.sh` tulostaa `GITOPS_COMMIT=<sha>` stdoutiin onnistuneen
GitOps-päivityksen jälkeen. Code repo parsii sen ja asettaa commit-statusin
linkillä GitOps-committiin:
| Input | Required | Description |
|---|---|---|---|
| `env_json` | Yes | Config JSON with `GITEA_API_URL`, optional `GIT_TAG_PREFIX` (for multi-component repos) |
| `version` | Yes | Version to write (e.g. `0.2.3`) |
| `GITOPS_FILE` | Yes | Path in GitOps repo (e.g. `dev/Chart.yaml`) |
| `GITOPS_YQ_TPL` | Yes | yq expression, `{{VERSION}}` is replaced at runtime |
| `GITOPS_REPO` | Yes | GitOps repo slug (e.g. `niko/agent-platform-gitops`) |
| `GITOPS_EXTRA_CMD` | No | Shell command to run after yq update, before git commit (e.g. `helm dependency update`) |
### 2.4 Output
The workflow produces a `summary` output in pipe format:
```
component|version|status|commit_sha|repo
agent-platform-helm|0.2.3|success|abc789def|niko/agent-platform-gitops
```
Pass it to `report-summary.yml` for the pipeline summary:
```yaml
- name: Set commit-status with GitOps link
if: always()
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
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: |
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
report-summary:
needs: [load-config, gitops-update]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: bats cucumber
gitops: ${{ needs.gitops-update.outputs.summary }}
```
`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ä
rivin GITHUB_STEP_SUMMARYyn:
| Token | Where | Scope | Purpose |
|---|---|---|---|
| `GITOPS_DISPATCH_TOKEN` (manual) | Consumer secrets | write GitOps repo | Dispatches the GitOps workflow |
| `GITHUB_TOKEN` (auto) | Consumer workflow | write consumer repo | Sets commit-status on consumer's commit |
| `GITEA_TOKEN` (auto) | GitOps workflow | write GitOps repo | Clone, push, commit-status in GitOps repo |
```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 |
|-----------|---------|--------|--------|
| agent-platform-helm | __VERSION__ | __STATUS__ | [link](__LINK__) |
GITOPS
sed -i "s|__VERSION__|${VERSION}|; s|__STATUS__|${{ job.status }}|; s|__LINK__|${LINK}|" \
"$GITHUB_STEP_SUMMARY"
1. Consumer's `gitops-dispatch.yml` generates a unique `dispatch_id` and POSTs it to the GitOps repo
2. GitOps workflow clones its own repo, applies `yq`, runs `GITOPS_EXTRA_CMD` if set, then commits + pushes
3. Consumer polls the GitOps repo's runs until the workflow completes
4. Consumer lists recent commits and finds the matching one by commit message `"gitops: update version to X.Y.Z"`
5. Consumer sets commit-status `gitops/{repo}[/{prefix}]` on its own commit with a link to the exact GitOps commit
6. If no matching commit is found (no change or error), the job fails
7. On failure, `GITOPS_SUMMARY` still flows through `report-summary` with `status=failure`
---
## 5. GIT_TAG_PREFIX (optional)
If the same consumer repo dispatches updates for multiple components (e.g. Docker image + Helm chart), set `GIT_TAG_PREFIX` in your `gitea-env.conf`:
```
GIT_TAG_PREFIX=docker/
```
## Secretit ja tokenit
Each component gets its own commit-status context:
| Secret | Missä | Scope | Kuvaus |
|--------|-------|-------|--------|
| `GITEA_TOKEN` (auto) | Code repo | Vain code repo | Asettaa commit-statusin dispatchin jälkeen |
| `GITEA_TOKEN` (auto) | GitOps repo | Vain GitOps repo | Klooni, push, commit-status GitOps-repossa |
| `GITOPS_DISPATCH_TOKEN` (manuaalinen) | Code repo | Write GitOps-repoon | Dispatchaa GitOps-repon workflow |
| Prefix | Context |
|---|---|
| (empty) | `gitops/agent-platform` |
| `docker/` | `gitops/agent-platform/docker` |
| `helm/` | `gitops/agent-platform/helm` |
**Tokenin luonti:**
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`
This prevents status overwrites between parallel dispatch jobs.
## Provider-skriptit
---
### `scripts/gitops-update.sh`
## 6. What you do NOT need to know
Ajaan GitOps-repon workflow'ssa. Päivittää konfiguraatiotiedoston yq:llä,
committaa ja pushaa. Asettaa commit-statuksen vain GitOps-repoon.
- How `gitops-update.sh` works internally
- How the polling finds the run
- How the commit SHA is extracted
- Race condition handling
- CI container plans
**Input-ympäristömuuttujat:**
| 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.
All of that is handled by `gitops-dispatch.yml`. You just call it.
+5 -5
View File
@@ -44,15 +44,15 @@ EOF
[[ "$output" == *"GITEA_TOKEN"* ]]
}
@test "missing GIT_PAGES_PUBLISH_TOKEN secret → exit 1" {
@test "missing REPORTS_PUBLISH_TOKEN secret → exit 1" {
cat > "$CONF_FILE" <<EOF
SOME_KEY=ok
EOF
export GITEA_TOKEN="sometoken"
unset GIT_PAGES_PUBLISH_TOKEN
unset REPORTS_PUBLISH_TOKEN
run bash scripts/ci-validate.sh
[ "$status" -eq 1 ]
[[ "$output" == *"GIT_PAGES_PUBLISH_TOKEN"* ]]
[[ "$output" == *"REPORTS_PUBLISH_TOKEN"* ]]
}
@test "valid config and all secrets → exit 0" {
@@ -61,7 +61,7 @@ API_URL=https://example.com
ANOTHER=https://test.fi
EOF
export GITEA_TOKEN="sometoken"
export GIT_PAGES_PUBLISH_TOKEN="sometoken"
export REPORTS_PUBLISH_TOKEN="sometoken"
run bash scripts/ci-validate.sh
[ "$status" -eq 0 ]
}
@@ -73,7 +73,7 @@ EOF
VALID_URL=https://example.com
EOF
export GITEA_TOKEN="sometoken"
export GIT_PAGES_PUBLISH_TOKEN="sometoken"
export REPORTS_PUBLISH_TOKEN="sometoken"
run bash scripts/ci-validate.sh
[ "$status" -eq 0 ]
}
@@ -27,7 +27,7 @@ function bashQuiet(cmd) {
}
function runReportStatus(args) {
return bash(`export GITEA_API_URL="http://localhost:18080" GITEA_TOKEN="test-token-abc123" GIT_PAGES_URL="https://reports.example.com" GITHUB_REPOSITORY="test-owner/test-repo" GITHUB_SHA="abc123def456789012345678901234567890abcd" GITHUB_RUN_ID="42"; bash "${REPORT_SCRIPT}" ${args}`);
return bash(`export GITEA_API_URL="http://localhost:18080" GITEA_TOKEN="test-token-abc123" GIT_PAGES_URL="https://reports.example.com" GITHUB_REPOSITORY="test-owner/test-repo" GITHUB_REF_NAME="main" GITHUB_SHA="abc123def456789012345678901234567890abcd" GITHUB_RUN_ID="42"; bash "${REPORT_SCRIPT}" ${args}`);
}
function getMockBody() {
@@ -57,7 +57,7 @@ When('a build step completes successfully and reports its results', function ()
Then('the commit shows a success status with a clickable link to the results', function () {
const body = getMockBody();
if (!body.includes('"state":"success"')) throw new Error('Expected success status');
if (!body.includes('"target_url":"https://reports.example.com/test-owner/test-repo/reports/abc123de/cucumber/"')) throw new Error('Expected URL');
if (!body.includes('"target_url":"https://reports.example.com/test-owner/test-repo/main/abc123de/cucumber/"')) throw new Error('Expected URL');
});
When('a build step fails', function () {
+8
View File
@@ -67,6 +67,14 @@ class H(http.server.BaseHTTPRequestHandler):
self.end_headers()
self.wfile.write(body.encode())
def do_PUT(self):
self._log_request('PUT')
code, body = self._get_response()
self.send_response(code)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(body.encode())
def do_PATCH(self):
self._log_request('PATCH')
code, body = self._get_response()
+23 -15
View File
@@ -4,18 +4,18 @@ setup() {
source tests/helpers/mock-api.sh
export GITEA_API_URL="http://localhost:18080"
export GIT_PAGES_URL="http://localhost:18080"
export GIT_PAGES_PUBLISH_TOKEN="publish-token-abc"
export REPORTS_PUBLISH_TOKEN="publish-token-abc"
export GITHUB_REPOSITORY="test-owner/test-repo"
export GITHUB_SHA="abc123def456789012345678901234567890abcd"
export GITHUB_REF_NAME="main"
REPORT_DIR="reports/abc123de/unit-tests"
mkdir -p "$REPORT_DIR"
echo "<html>test</html>" > "$REPORT_DIR/index.html"
mkdir -p "unit-tests"
echo "<html>test</html>" > "unit-tests/index.html"
}
teardown() {
mock_stop
rm -rf "reports/abc123de"
rm -rf "unit-tests" "sub" "empty-suite"
}
@test "missing suite_path argument → exit 1" {
@@ -24,11 +24,11 @@ teardown() {
[[ "$output" == *"ERROR"* ]]
}
@test "missing GITEA_API_URL → exit 1" {
unset GITEA_API_URL
@test "missing GITHUB_REF_NAME → exit 1" {
unset GITHUB_REF_NAME
run bash scripts/publish-git-pages.sh "unit-tests"
[ "$status" -eq 1 ]
[[ "$output" == *"GITEA_API_URL"* ]]
[[ "$output" == *"GITHUB_REF_NAME"* ]]
}
@test "missing GIT_PAGES_URL → exit 1" {
@@ -38,11 +38,11 @@ teardown() {
[[ "$output" == *"GIT_PAGES_URL"* ]]
}
@test "missing GIT_PAGES_PUBLISH_TOKEN → exit 1" {
unset GIT_PAGES_PUBLISH_TOKEN
@test "missing REPORTS_PUBLISH_TOKEN → exit 1" {
unset REPORTS_PUBLISH_TOKEN
run bash scripts/publish-git-pages.sh "unit-tests"
[ "$status" -eq 1 ]
[[ "$output" == *"GIT_PAGES_PUBLISH_TOKEN"* ]]
[[ "$output" == *"REPORTS_PUBLISH_TOKEN"* ]]
}
@test "missing GITHUB_REPOSITORY → exit 1" {
@@ -58,6 +58,14 @@ teardown() {
[[ "$output" == *"not a directory"* ]]
}
@test "empty directory → exit 1" {
mkdir -p "empty-suite"
run bash scripts/publish-git-pages.sh "empty-suite"
[ "$status" -eq 1 ]
[[ "$output" == *"no files to publish"* ]]
rm -rf "empty-suite"
}
@test "valid publish returns report base URL" {
mock_set_sequence '[
{"code":200,"body":"published"}
@@ -65,19 +73,19 @@ teardown() {
mock_start
run bash scripts/publish-git-pages.sh "unit-tests"
[ "$status" -eq 0 ]
[[ "$output" == "http://localhost:18080/test-owner/test-repo/reports/abc123de" ]]
[[ "$output" == "http://localhost:18080/test-owner/test-repo/main/abc123de/unit-tests/" ]]
}
@test "publish with suite subpath" {
mkdir -p "reports/abc123de/sub/suite"
echo "sub" > "reports/abc123de/sub/suite/result.html"
mkdir -p "sub/suite"
echo "sub" > "sub/suite/result.html"
mock_set_sequence '[
{"code":200,"body":"published"}
]'
mock_start
run bash scripts/publish-git-pages.sh "sub/suite"
[ "$status" -eq 0 ]
[[ "$output" == "http://localhost:18080/test-owner/test-repo/reports/abc123de" ]]
[[ "$output" == "http://localhost:18080/test-owner/test-repo/main/abc123de/sub/suite/" ]]
}
@test "git-pages returns HTTP 500 → exit 1" {
+4 -4
View File
@@ -4,7 +4,7 @@ setup() {
export GITEA_API_URL="http://localhost:18080"
export GITEA_TOKEN="test-token-abc"
export GIT_PAGES_URL="http://localhost:18080"
export GIT_PAGES_PUBLISH_TOKEN="publish-token-abc"
export REPORTS_PUBLISH_TOKEN="publish-token-abc"
export GITHUB_REPOSITORY="test-owner/test-repo"
export GITHUB_SHA="abc123def456789012345678901234567890abcd"
}
@@ -36,9 +36,9 @@ setup() {
[[ "$output" == *"GIT_PAGES_URL"* ]]
}
@test "missing GIT_PAGES_PUBLISH_TOKEN → exit 1" {
unset GIT_PAGES_PUBLISH_TOKEN
@test "missing REPORTS_PUBLISH_TOKEN → exit 1" {
unset REPORTS_PUBLISH_TOKEN
run bash scripts/publish.sh "unit-tests"
[ "$status" -eq 1 ]
[[ "$output" == *"GIT_PAGES_PUBLISH_TOKEN"* ]]
[[ "$output" == *"REPORTS_PUBLISH_TOKEN"* ]]
}
+2 -1
View File
@@ -8,6 +8,7 @@ setup() {
export GITHUB_REPOSITORY="test-owner/test-repo"
export GITHUB_SHA="abc123def456789012345678901234567890abcd"
export GITHUB_RUN_ID="42"
export GITHUB_REF_NAME="main"
}
teardown() {
@@ -35,7 +36,7 @@ teardown() {
body=$(mock_get_request_body)
[[ "$body" == *'"state":"success"'* ]]
[[ "$body" == *'"context":"unit-test"'* ]]
[[ "$body" == *'"target_url":"https://reports.example.com/test-owner/test-repo/reports/abc123de/cucumber/"'* ]]
[[ "$body" == *'"target_url":"https://reports.example.com/test-owner/test-repo/main/abc123de/cucumber/"'* ]]
}
@test "failure status constructs run URL when no suite" {