Compare commits

..

51 Commits

Author SHA1 Message Date
moilanik 5a00763d8f asgas
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.7 required
ci-docker-build Docker build 0.1.7 OK
ci-docker-push Docker push 0.1.7 OK
ci-docker-tag Tag 0.1.7 OK
CI / Build & Push Artifact (push) Successful in 1m24s
2026-06-15 13:26:08 +03:00
moilanik 4a79ce9d17 sag
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.7 required
ci-docker-build Docker build 0.1.7 OK
ci-docker-push Docker push 0.1.7 OK
CI / Build & Push Artifact (push) Failing after 1m4s
2026-06-15 13:21:49 +03:00
moilanik 9d402578bd siivous
CI / Load gitea-env.conf to pipeline env (push) Successful in 14s
ci-check Build version 0.1.7 required
ci-docker-build Docker build 0.1.7 OK
CI / Build & Push Artifact (push) Failing after 53s
2026-06-15 13:19:08 +03:00
moilanik 63ce59e604 clean
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.6 required
ci-docker-build Docker build 0.1.6 OK
ci-docker-push Docker push 0.1.6 OK
ci-docker-tag Tag 0.1.6 OK
CI / Build & Push Artifact (push) Successful in 1m21s
2026-06-15 13:13:13 +03:00
moilanik a566998180 siivous
CI / Load gitea-env.conf to pipeline env (push) Successful in 11s
ci-check Build version 0.1.5 required
ci-docker-build Docker build 0.1.5 OK
ci-docker-push Docker push 0.1.5 OK
ci-docker-tag Tag 0.1.5 OK
CI / Build & Push Artifact (push) Successful in 1m24s
2026-06-15 12:43:33 +03:00
moilanik 1239cc5673 siivotaa temp artifactit pois
CI / Load gitea-env.conf to pipeline env (push) Successful in 10s
ci-check Build version 0.1.4 required
ci-docker-build Docker build 0.1.4 OK
ci-docker-push Docker push 0.1.4 OK
ci-docker-tag Tag 0.1.4 OK
CI / Build & Push Artifact (push) Successful in 1m26s
2026-06-15 12:38:44 +03:00
moilanik 20a6099969 kokeillaan meneekö oikeaan paikkaan
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Build version 0.1.3 required
ci-docker-build Docker build 0.1.3 OK
ci-docker-push Docker push 0.1.3 OK
ci-docker-tag Tag 0.1.3 OK
CI / Build & Push Artifact (push) Successful in 1m25s
2026-06-15 12:32:04 +03:00
moilanik c7141fc28f koodi repon packageksi
CI / Load gitea-env.conf to pipeline env (push) Successful in 15s
ci-check Build version 0.1.2 required
ci-docker-build Docker build 0.1.2 OK
ci-docker-push Docker push 0.1.2 OK
ci-docker-tag Tag 0.1.2 OK
CI / Build & Push Artifact (push) Successful in 1m34s
2026-06-15 12:05:20 +03:00
moilanik c87e585918 test
CI / Load gitea-env.conf to pipeline env (push) Successful in 14s
ci-check Build version 0.1.1 required
ci-docker-build Docker build 0.1.1 OK
ci-docker-push Docker push 0.1.1 OK
ci-docker-tag Tag 0.1.1 OK
CI / Build & Push Artifact (push) Successful in 1m22s
2026-06-15 11:49:54 +03:00
moilanik 8f4725e23f docker pat to actions secret in gitea side
CI / Load gitea-env.conf to pipeline env (push) Successful in 15s
ci-check Build version 0.1.0 required
ci-docker-build Docker build 0.1.0 OK
ci-docker-push Docker push 0.1.0 OK
ci-docker-tag Tag 0.1.0 OK
CI / Build & Push Artifact (push) Successful in 1m56s
2026-06-15 11:43:16 +03:00
moilanik f35c24857f docker yleiseksi rakenteeltaan
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.0 required
ci-docker-build Docker build 0.1.0 OK
ci-docker-push Docker push 0.1.0 FAILED
CI / Build & Push Artifact (push) Failing after 1m6s
2026-06-15 11:31:09 +03:00
moilanik c233ef8975 struj
CI / Load gitea-env.conf to pipeline env (push) Successful in 14s
ci-check Build version 0.1.0 required
ci-docker-build Docker build 0.1.0 OK
ci-docker-push Docker push 0.1.0 FAILED
CI / Build & Push Artifact (push) Failing after 1m10s
2026-06-15 10:46:06 +03:00
moilanik f32b345f58 kokeillaan ilman quality gate
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.0 required
ci-docker-push Docker push 0.1.0 FAILED
CI / Build & Push Artifact (push) Failing after 1m4s
2026-06-15 10:28:02 +03:00
moilanik 0740dbf815 adrfhhda
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
ci-docker-push Docker push 0.1.0 FAILED
CI / Build & Push Artifact (push) Failing after 3m28s
2026-06-15 09:53:58 +03:00
moilanik 0efd7db43a asgasr
CI / Load gitea-env.conf to pipeline env (push) Successful in 11s
ci-check Build version 0.1.0 required
CI / Build & Push Artifact (push) Successful in 15s
2026-06-15 09:40:21 +03:00
moilanik 705af709c4 etaerst
CI / Load gitea-env.conf to pipeline env (push) Successful in 11s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Failing after 4m22s
2026-06-15 09:29:56 +03:00
moilanik 1d396c8278 runner päivityksessä cucumber testi meni pieleen. korjaus
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m30s
2026-06-15 09:20:23 +03:00
moilanik 16b25970ff päivitettu runner image 1.0.4 -> 1.0.8
CI / Load gitea-env.conf to pipeline env (push) Successful in 2m8s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
CI / Build & Push Artifact (push) Failing after 2m13s
2026-06-15 08:50:54 +03:00
moilanik 52601104b0 sdgsf
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Failing after 3m45s
2026-06-15 08:19:41 +03:00
moilanik 8312cff6ec asg
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 3m11s
2026-06-15 08:07:50 +03:00
moilanik 8c11306f2b sadg 2026-06-15 08:07:14 +03:00
moilanik 2ae96a5355 asdgas
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Check version FAILED
CI / Build & Push Artifact (push) Failing after 18s
2026-06-15 08:00:59 +03:00
moilanik fc76234379 fake
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Check version FAILED
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Failing after 2m21s
2026-06-15 06:42:26 +03:00
moilanik 0fa291f103 asgasg
CI / Load gitea-env.conf to pipeline env (push) Successful in 11s
ci-check Check version FAILED
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Failing after 2m28s
2026-06-15 06:32:43 +03:00
moilanik db47775249 build ehto muutos
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m25s
2026-06-15 06:13:47 +03:00
moilanik 1be3b5b434 asgre
CI / Load gitea-env.conf to pipeline env (push) Successful in 14s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Failing after 2m43s
2026-06-15 06:07:35 +03:00
moilanik 544ec4afe4 sadg
CI / Load gitea-env.conf to pipeline env (push) Successful in 11s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m32s
2026-06-15 06:03:28 +03:00
moilanik 815c39c6a7 fake
CI / Load gitea-env.conf to pipeline env (push) Successful in 11s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Failing after 2m30s
2026-06-15 05:57:08 +03:00
moilanik ccec73e40a kiristetään ehtoa!
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Failing after 2m30s
2026-06-15 05:49:14 +03:00
moilanik 26394e5a54 build ehto korjattu
CI / Load gitea-env.conf to pipeline env (push) Successful in 11s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m24s
2026-06-15 05:39:10 +03:00
moilanik 8d9bd42f6c build vaatimus tarkistettu
CI / Load gitea-env.conf to pipeline env (push) Successful in 11s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m31s
2026-06-15 05:29:51 +03:00
moilanik 69d574955c pikkasen muutetaan ehtoja
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m33s
2026-06-15 05:20:20 +03:00
moilanik cbd63b7581 google ai kanssa katstottu pipeline
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m34s
2026-06-15 05:06:20 +03:00
moilanik ae84083eae dsfh
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m35s
2026-06-14 14:20:15 +03:00
moilanik 2d3fd96768 argha
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m29s
2026-06-14 14:07:13 +03:00
moilanik 95f0aca47e aedsvg<sDV
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check-running Checking version...
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
CI / Build & Push Artifact (push) Failing after 1m21s
2026-06-14 14:01:23 +03:00
moilanik b6c4d5ae4f fake
CI / Load gitea-env.conf to pipeline env (push) Successful in 15s
ci-check-running Checking version...
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m31s
2026-06-14 13:49:59 +03:00
moilanik 5e011b3993 äpkjoå¨0j
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-bats Bats tests
ci-build Build complete
ci-check-running Checking version...
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
CI / Build & Push Artifact (push) Has been cancelled
2026-06-14 13:45:12 +03:00
moilanik 5f14554b1f asg
CI / Load gitea-env.conf to pipeline env (push) Successful in 16s
ci-check-running Checking version...
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m30s
2026-06-14 13:39:27 +03:00
moilanik 416939fb82 srghaer
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check-running Checking version...
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m29s
2026-06-14 13:25:04 +03:00
moilanik ccf833d698 öoämj
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-check Build version 0.1.0 required
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m28s
2026-06-14 13:11:50 +03:00
moilanik 5848f47c0e wseg
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-check Check version
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 3m29s
2026-06-14 11:08:18 +03:00
moilanik b32a97ed9b qwen
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m23s
2026-06-14 10:55:18 +03:00
moilanik e853e22d1d trigger CI: test build fix
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m31s
2026-06-14 10:42:11 +03:00
moilanik 83e35f5324 arghr
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m29s
2026-06-14 10:29:10 +03:00
moilanik 95257c17b9 ä',pk'pnå9gv
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m31s
2026-06-14 10:12:57 +03:00
moilanik b51adf3410 adfhadfh
CI / Load gitea-env.conf to pipeline env (push) Successful in 13s
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m34s
2026-06-14 09:55:53 +03:00
moilanik daa5dc58ed tuleeko kontti?
CI / Load gitea-env.conf to pipeline env (push) Successful in 10s
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Build & Push Artifact (push) Successful in 2m34s
2026-06-14 09:48:39 +03:00
moilanik 5ac7516672 fix(retention): add retry on Gitea API errors
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Quality Gate (push) Successful in 2m17s
CI / Build & Push Artifact (push) Has been skipped
- Retry up to 3 times (2 retries) on non-200/non-404 responses
- 10 second delay between retries
- Fail-safe: keep report if all retries fail
2026-06-14 09:17:09 +03:00
moilanik 622e8acdc5 fix(retention): fail-safe on Gitea API errors
- Keep reports when Gitea API returns non-200/non-404 status
- Only delete on actual 404 (branch truly doesn't exist)
- Log warning for API errors to aid debugging
2026-06-14 09:14:52 +03:00
moilanik 6e26281fea fix: add missing GITEA_TOKEN auth to tags API call in check job
CI / Load gitea-env.conf to pipeline env (push) Successful in 12s
CI / Build & Push Artifact (push) Has been skipped
ci-cucumber Cucumber tests
ci-bats Bats tests
ci-build Build complete
CI / Quality Gate (push) Successful in 2m14s
2026-06-14 08:56:54 +03:00
77 changed files with 1773 additions and 5150 deletions
-20
View File
@@ -1,20 +0,0 @@
.git/
.gitignore
node_modules/
reports/
coverage/
.ai/
.cursor/
.vscode/
tmp/
.DS_Store
*.md
docs/
guides/
.simplecov
cucumber.js
package-lock.json
package.json
CURRENT_PROVIDER_VERSION
README.md
AGENTS.md
+28 -20
View File
@@ -1,29 +1,37 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
REPORT_DIR="${1:-}" WORKSPACE_VOLUME="${1:-}"
COVERAGE_DIR="${REPORT_DIR}/coverage" REPORT_DIR="${2:-}"
[ -n "$WORKSPACE_VOLUME" ] || { echo "ERROR: workspace volume name required" >&2; exit 1; }
[ -n "$REPORT_DIR" ] || { echo "ERROR: report directory required" >&2; exit 1; } [ -n "$REPORT_DIR" ] || { echo "ERROR: report directory required" >&2; exit 1; }
if [ -d coverage ]; then HAS_COVERAGE=false
mkdir -p "$COVERAGE_DIR" COVERAGE_SRC=""
cp -a coverage/. "$COVERAGE_DIR/" if docker run --rm -v "$WORKSPACE_VOLUME":/data alpine sh -c '[ -d /data/coverage ] && ls -A /data/coverage | grep -q .' 2>/dev/null; then
COVERAGE_SRC="/data/coverage"
fi fi
if [ -d "$COVERAGE_DIR" ] && [ ! -f "$COVERAGE_DIR/index.html" ]; then if [ -n "$COVERAGE_SRC" ]; then
SHA8="${GITHUB_SHA:0:8}" mkdir -p "$REPORT_DIR/coverage"
{ docker run --rm -v "$WORKSPACE_VOLUME":/data alpine tar c -C "$COVERAGE_SRC" . | tar x -C "$REPORT_DIR/coverage"
echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">' HAS_COVERAGE=true
echo "<title>Coverage report ${SHA8}</title>"
echo '<style>body{font-family:sans-serif;margin:2em}h1{color:#1e293b}ul{list-style:none;padding:0}li{margin:.5em 0}a{color:#2563eb}</style>'
echo "</head><body><h1>Coverage report <code>${SHA8}</code></h1><ul>"
while IFS= read -r -d '' f; do
base=$(basename "$f")
name="${base%.*}"
name="${name//-/ }"
echo "<li><a href=\"${base}\">${name^}</a></li>"
done < <(find "$COVERAGE_DIR" -maxdepth 1 -type f \( -name '*.html' -o -name '*.txt' \) ! -name index.html -print0 2>/dev/null || true)
echo '</ul></body></html>'
} > "$COVERAGE_DIR/index.html"
fi fi
cat > "$REPORT_DIR/index.html" << EOF
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
<title>Bats report ${GITHUB_SHA:0:8}</title>
<style>body{font-family:sans-serif;margin:2em;max-width:960px}
h1{color:#1e293b}a{color:#2563eb;text-decoration:none}a:hover{text-decoration:underline}
</style></head><body>
<h1>Bats report <code>${GITHUB_SHA:0:8}</code></h1>
<ul>
<li><a href="test-report.html">Test results</a></li>
EOF
if [ "$HAS_COVERAGE" = true ]; then
echo '<li><a href="coverage/index.html">Coverage report</a></li>' >> "$REPORT_DIR/index.html"
fi
echo '</ul></body></html>' >> "$REPORT_DIR/index.html"
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
SHA8="${GITHUB_SHA:0:8}"
REPORTS_DIR="reports/${SHA8}"
mkdir -p "${REPORTS_DIR}"
BATS_PASS=$(grep -c 'ok' "${REPORTS_DIR}/bats/results.txt" 2>/dev/null || echo 0)
BATS_FAIL=$(grep -c 'not ok' "${REPORTS_DIR}/bats/results.txt" 2>/dev/null || echo 0)
CUCUMBER_PASS=$(jq '.summary.passed // 0' "${REPORTS_DIR}/cucumber/report.json" 2>/dev/null || echo 0)
CUCUMBER_FAIL=$(jq '.summary.failed // 0' "${REPORTS_DIR}/cucumber/report.json" 2>/dev/null || echo 0)
{
echo "<!DOCTYPE html><html><head><meta charset='utf-8'>"
echo "<title>CI report ${SHA8}</title>"
echo "<style>body{font-family:sans-serif;margin:2em}a{color:#2563eb}table{border-collapse:collapse}"
echo "th,td{border:1px solid #ccc;padding:8px;text-align:left}"
echo ".pass{color:#059669}.fail{color:#dc2626}</style></head><body>"
echo "<h1>CI report <code>${SHA8}</code></h1>"
echo "<p>Commit: ${GITHUB_SHA}<br>Branch: ${GITHUB_REF_NAME}<br>Run: ${GITHUB_RUN_ID}</p>"
echo "<table><tr><th>Suite</th><th>Passed</th><th>Failed</th><th>Report</th></tr>"
echo "<tr><td>Bats</td><td class='pass'>${BATS_PASS}</td><td class='fail'>${BATS_FAIL}</td>"
echo "<td><a href='bats/results.txt'>results.txt</a>"
echo " | <a href='bats/junit.xml'>junit.xml</a></td></tr>"
echo "<tr><td>Cucumber</td><td class='pass'>${CUCUMBER_PASS}</td><td class='fail'>${CUCUMBER_FAIL}</td>"
echo "<td><a href='cucumber/index.html'>report</a>"
echo " | <a href='cucumber/report.json'>json</a></td></tr>"
echo "</table></body></html>"
} > "${REPORTS_DIR}/index.html"
+369
View File
@@ -0,0 +1,369 @@
name: Build & Publish Artifact
on:
workflow_call:
inputs:
env_json:
required: true
type: string
bats-image:
required: true
type: string
cucumber-node-image:
required: true
type: string
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
required: true
DOCKER_USERNAME:
required: false
DOCKER_PASSWORD:
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 }}
REPO: ${{ github.repository }}
DOCKER_REGISTRY: ${{ fromJson(inputs.env_json).DOCKER_REGISTRY || '' }}
DOCKER_IMAGE_NAME: ${{ fromJson(inputs.env_json).DOCKER_IMAGE_NAME || '' }}
DOCKER_UI_URL: ${{ fromJson(inputs.env_json).DOCKER_UI_URL || '' }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
outputs:
artifact_exists: ${{ steps.set-outputs.outputs.artifact_exists }}
steps:
- uses: actions/checkout@v4
- name: Set Gitea status to PENDING
run: |
echo "===== gitea-ci-library - Check existing artifact | begin ====="
bash scripts/report-status.sh pending "Checking version..." ci-check
- name: Check existing artifact and calculate version
run: |
RAW_VERSION=$(jq -r '.version' package.json)
BASE_VERSION=$(echo "$RAW_VERSION" | cut -d'.' -f1-2)
echo "gitea-ci-library - Tunnistettu Major.Minor versio: $BASE_VERSION"
TAGS_JSON=$(curl -s -f -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/tags")
TAG=$(echo "$TAGS_JSON" | jq -r 'if type == "array" then .[] | select(.commit.sha == "${{ github.sha }}") | .name else empty end' | head -1)
mkdir -p /tmp/build-ctx
if [ -n "$TAG" ]; then
echo "ARTIFACT_EXISTS=true" > /tmp/build-ctx/build.env
echo "NEXT_VERSION=$TAG" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Artefakti löytyi jo tagilla: $TAG."
else
echo "ARTIFACT_EXISTS=false" > /tmp/build-ctx/build.env
HIGHEST_PATCH=$(echo "$TAGS_JSON" | jq -r --arg bv "$BASE_VERSION." '
if type == "array" then .[] | .name | select(startswith($bv)) | sub($bv; "") | tonumber else empty end' | sort -rn | head -1)
if [ -z "$HIGHEST_PATCH" ]; then NEXT_PATCH=0; else NEXT_PATCH=$((HIGHEST_PATCH + 1)); fi
FULL_VERSION="${BASE_VERSION}.${NEXT_PATCH}"
echo "NEXT_VERSION=$FULL_VERSION" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Uusi vapaa versio: $FULL_VERSION"
fi
- name: Set job outputs
id: set-outputs
run: |
source /tmp/build-ctx/build.env
echo "artifact_exists=$ARTIFACT_EXISTS" >> "$GITHUB_OUTPUT"
- name: Upload build env artifact
uses: actions/upload-artifact@v3
with:
name: build-context
path: /tmp/build-ctx/build.env
- name: Set Gitea status to SUCCESS
if: success()
run: |
source /tmp/build-ctx/build.env
if [ "${ARTIFACT_EXISTS}" = "true" ]; then
bash scripts/report-status.sh success "Skip build: version $NEXT_VERSION exists" ci-check
else
bash scripts/report-status.sh success "Build version $NEXT_VERSION required" ci-check
fi
- name: Set Gitea status to FAILURE
if: failure()
run: bash scripts/report-status.sh failure "Check version FAILED" ci-check
# quality-gate:
# needs: [check]
# uses: niko/gitea-ci-library/.gitea/workflows/quality-gate.yml@main
# secrets: inherit
# with:
# env_json: ${{ inputs.env_json }}
# bats-image: ${{ inputs.bats-image }}
# cucumber-node-image: ${{ inputs.cucumber-node-image }}
build:
runs-on: ubuntu-latest
# needs: [check, quality-gate]
needs: [check]
# Skipataan koko build jos artefakti löytyy jo
if: needs.check.outputs.artifact_exists != 'true'
steps:
- uses: actions/checkout@v4
- name: Download build env
uses: actions/download-artifact@v3
with:
name: build-context
path: /tmp/build-ctx
- name: Check if build needed
id: gatekeeper
run: |
source /tmp/build-ctx/build.env
if [ "${ARTIFACT_EXISTS}" = "true" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Set Gitea status to PENDING
if: steps.gatekeeper.outputs.skip == 'false'
run: |
echo "===== gitea-ci-library - Docker Build | begin ====="
bash scripts/report-status.sh pending "Building Docker image..." ci-docker-build
- name: Build container
if: steps.gatekeeper.outputs.skip == 'false'
run: |
source /tmp/build-ctx/build.env
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
docker build \
--label "git.commit=${{ github.sha }}" \
--label "git.commitBy=${{ github.actor }}" \
--label "build.date=${NOW}" \
-t "${DOCKER_IMAGE_NAME}:${NEXT_VERSION}" .
- name: Report status SUCCESS
if: steps.gatekeeper.outputs.skip == 'false' && success()
run: |
source /tmp/build-ctx/build.env
bash scripts/report-status.sh success "Docker build $NEXT_VERSION OK" ci-docker-build
- name: Report status FAILURE
if: steps.gatekeeper.outputs.skip == 'false' && failure()
run: |
source /tmp/build-ctx/build.env
bash scripts/report-status.sh failure "Docker build $NEXT_VERSION FAILED" ci-docker-build
- name: Save Docker image
if: steps.gatekeeper.outputs.skip == 'false' && success()
run: |
source /tmp/build-ctx/build.env
mkdir -p /tmp/image
docker save "${DOCKER_IMAGE_NAME}:${NEXT_VERSION}" -o /tmp/image/artifact.tar
- name: Upload Docker image artifact
if: steps.gatekeeper.outputs.skip == 'false' && success()
uses: actions/upload-artifact@v3
with:
name: docker-image
path: /tmp/image/artifact.tar
push:
runs-on: ubuntu-latest
needs: [check, build]
if: needs.check.outputs.artifact_exists != 'true'
steps:
- uses: actions/checkout@v4
- name: Download build env
uses: actions/download-artifact@v3
with:
name: build-context
path: /tmp/build-ctx
- name: Verify Build Status
id: gatekeeper
run: |
BUILD_RESULT="${{ needs.build.result }}"
source /tmp/build-ctx/build.env
if [ "$BUILD_RESULT" != "success" ]; then
echo "gitea-ci-library - Edellinen vaihe epäonnistui. Keskeytetään." >&2
exit 1
fi
if [ "${ARTIFACT_EXISTS}" = "true" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Load saved Docker image
if: steps.gatekeeper.outputs.skip == 'false'
uses: actions/download-artifact@v3
with:
name: docker-image
path: /tmp/image
- name: Set Gitea status to PENDING
if: steps.gatekeeper.outputs.skip == 'false'
run: |
echo "===== gitea-ci-library - Docker Push | begin ====="
bash scripts/report-status.sh pending "Pushing to registry..." ci-docker-push
- name: Push to Docker Registry
if: steps.gatekeeper.outputs.skip == 'false'
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME || github.actor }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
source /tmp/build-ctx/build.env
docker load -i /tmp/image/artifact.tar
REGISTRY="${DOCKER_REGISTRY:?DOCKER_REGISTRY not set in env.conf}"
IMAGE="${DOCKER_IMAGE_NAME:?DOCKER_IMAGE_NAME not set in env.conf}"
REGISTRY_HOST="${REGISTRY%%/*}"
FULL_IMAGE="${REGISTRY}/${IMAGE}:${NEXT_VERSION}"
echo "Pushing ${FULL_IMAGE} ..."
docker tag "${DOCKER_IMAGE_NAME}:${NEXT_VERSION}" "$FULL_IMAGE"
echo "$DOCKER_PASSWORD" | docker login "$REGISTRY_HOST" -u "$DOCKER_USERNAME" --password-stdin
docker push "$FULL_IMAGE"
docker logout "$REGISTRY_HOST"
- name: Report status SUCCESS
if: steps.gatekeeper.outputs.skip == 'false' && success()
run: |
source /tmp/build-ctx/build.env
CONTAINER_URL=""
if [ -n "${DOCKER_UI_URL:-}" ] && [ -n "${NEXT_VERSION:-}" ]; then
CONTAINER_URL="${DOCKER_UI_URL}/${NEXT_VERSION}"
fi
bash scripts/report-status.sh success "Docker push $NEXT_VERSION OK" ci-docker-push "" "$CONTAINER_URL"
- name: Report status FAILURE
if: steps.gatekeeper.outputs.skip == 'false' && failure()
run: |
source /tmp/build-ctx/build.env
bash scripts/report-status.sh failure "Docker push $NEXT_VERSION FAILED" ci-docker-push
- name: Delete Docker image artifact from Gitea (Python)
if: always()
shell: python
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
SERVER_URL: ${{ gitea.server_url }}
REPOSITORY: ${{ gitea.repository }}
RUN_ID: ${{ gitea.run_id }}
ARTIFACT_NAME: "docker-image"
run: |
import os, urllib.request, json
token = os.environ['GITEA_TOKEN']
server = os.environ['SERVER_URL']
repo = os.environ['REPOSITORY']
run_id = os.environ['RUN_ID']
target_name = os.environ['ARTIFACT_NAME']
# 1. Haetaan listaus artifakteista
url = f"{server}/api/v1/repos/{repo}/actions/runs/{run_id}/artifacts"
req = urllib.request.Request(url, headers={"Authorization": f"token {token}"})
try:
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode())
# 2. Etsitään oikea ID
artifacts = data.get("artifacts", [])
artifact_id = next((a["id"] for a in artifacts if a["name"] == target_name), None)
# 3. Jos löytyi, lähetetään DELETE-pyyntö
if artifact_id:
del_url = f"{server}/api/v1/repos/{repo}/actions/artifacts/{artifact_id}"
del_req = urllib.request.Request(del_url, headers={"Authorization": f"token {token}"}, method="DELETE")
with urllib.request.urlopen(del_req) as del_res:
print(f"Artifakti {target_name} (ID: {artifact_id}) poistettu onnistuneesti levyltä.")
else:
print(f"Artifaktia '{target_name}' ei löytynyt listalta. Se on jo poistettu.")
except Exception as e:
print(f"Poisto ohitettiin virheen vuoksi (esim. puuttuvat oikeudet): {e}")
tag-commit:
runs-on: ubuntu-latest
needs: [check, push]
if: needs.check.outputs.artifact_exists != 'true'
steps:
- uses: actions/checkout@v4
- name: Download build env
uses: actions/download-artifact@v3
with:
name: build-context
path: /tmp/build-ctx
- name: Verify Push Status
id: gatekeeper
run: |
PUSH_RESULT="${{ needs.push.result }}"
source /tmp/build-ctx/build.env
if [ "$PUSH_RESULT" != "success" ]; then
echo "gitea-ci-library - Push vaihe epäonnistui. Keskeytetään." >&2
exit 1
fi
if [ "${ARTIFACT_EXISTS}" = "true" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Set Gitea status to PENDING
if: steps.gatekeeper.outputs.skip == 'false'
run: |
echo "===== gitea-ci-library - Create Tag | begin ====="
bash scripts/report-status.sh pending "Creating tag..." ci-docker-tag
- name: Create git tag
if: steps.gatekeeper.outputs.skip == 'false'
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REPO: ${{ github.repository }}
SERVER_URL: ${{ gitea.server_url }}
RUN_NUMBER: ${{ github.run_number }}
SHA: ${{ github.sha }}
run: |
source /tmp/build-ctx/build.env
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
"$SERVER_URL/api/v1/repos/$REPO/tags" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"$NEXT_VERSION\", \"message\": \"Build #$RUN_NUMBER\", \"target\": \"$SHA\"}")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "409" ]; then
exit 0
else
exit 1
fi
- name: Report status SUCCESS
if: steps.gatekeeper.outputs.skip == 'false' && success()
run: |
source /tmp/build-ctx/build.env
bash scripts/report-status.sh success "Tag $NEXT_VERSION OK" ci-docker-tag
- name: Report status FAILURE
if: steps.gatekeeper.outputs.skip == 'false' && failure()
run: |
source /tmp/build-ctx/build.env
bash scripts/report-status.sh failure "Tag $NEXT_VERSION FAILED" ci-docker-tag
-47
View File
@@ -1,47 +0,0 @@
name: Check Existing Artifact
on:
workflow_call:
inputs:
env_json:
required: true
type: string
secrets:
GITEA_TOKEN:
required: true
outputs:
artifact_exists:
value: ${{ jobs.check.outputs.artifact_exists }}
version:
value: ${{ jobs.check.outputs.version }}
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
VERSION_FILE: ${{ fromJson(inputs.env_json).VERSION_FILE || '' }}
jobs:
check:
runs-on: ubuntu-latest
outputs:
artifact_exists: ${{ steps.set-outputs.outputs.artifact_exists }}
version: ${{ steps.set-outputs.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Check existing artifact and calculate version
env:
SERVER_URL: ${{ gitea.server_url }}
REPO: ${{ github.repository }}
SHA: ${{ github.sha }}
run: bash .ci/scripts/check-version.sh
- name: Set job outputs
id: set-outputs
run: |
source /tmp/build-ctx/build.env
echo "artifact_exists=$ARTIFACT_EXISTS" >> "$GITHUB_OUTPUT"
echo "version=$NEXT_VERSION" >> "$GITHUB_OUTPUT"
@@ -1,59 +0,0 @@
name: CI Container Build & Push
on:
workflow_call:
inputs:
env_json:
required: true
type: string
dockerfile_path:
required: true
type: string
image_name:
required: true
type: string
tag:
required: false
type: string
default: 'latest'
secrets:
DOCKER_USERNAME:
required: false
DOCKER_PASSWORD:
required: true
jobs:
build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push container
env:
DOCKER_REGISTRY: ${{ fromJson(inputs.env_json).DOCKER_REGISTRY || '' }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME || github.actor }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
if [ -z "${DOCKER_REGISTRY}" ]; then echo "ERROR: DOCKER_REGISTRY not set in conf"; exit 1; fi
REGISTRY="${DOCKER_REGISTRY}"
REGISTRY_HOST="${REGISTRY%%/*}"
DOCKERFILE="${{ inputs.dockerfile_path }}"
IMAGE_NAME="${{ inputs.image_name }}"
TAG="${{ inputs.tag }}"
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
CONTEXT_DIR=$(dirname "${DOCKERFILE}")
docker build \
--label "git.commit=${{ github.sha }}" \
--label "git.commitBy=${{ github.actor }}" \
--label "build.date=${NOW}" \
-f "${DOCKERFILE}" \
-t "${IMAGE_NAME}:${TAG}" \
"${CONTEXT_DIR}"
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
echo "Pushing ${FULL_IMAGE} ..."
docker tag "${IMAGE_NAME}:${TAG}" "$FULL_IMAGE"
echo "$DOCKER_PASSWORD" | docker login "$REGISTRY_HOST" -u "$DOCKER_USERNAME" --password-stdin
docker push "$FULL_IMAGE"
docker logout "$REGISTRY_HOST"
+34
View File
@@ -0,0 +1,34 @@
name: CI
on:
push:
branches: ["**"]
workflow_dispatch:
jobs:
load-config:
name: Load gitea-env.conf to pipeline env
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
with:
config_path: .gitea/workflows/gitea-env.conf
# feature:
# name: Quality Gate
# if: github.ref != 'refs/heads/main'
# needs: [load-config]
# uses: niko/gitea-ci-library/.gitea/workflows/quality-gate.yml@main
# secrets: inherit
# with:
# env_json: ${{ needs.load-config.outputs.env_json }}
# bats-image: bats/bats:latest
# cucumber-node-image: node:22
main:
name: Build & Push Artifact
# if: github.ref == 'refs/heads/main' # FIXME: väliaikainen — ajetaan tässä haarassa
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/build_publish-artifact.yml@feature/docker-kuntoon
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
bats-image: bats/bats:latest
cucumber-node-image: node:22
+1 -24
View File
@@ -1,41 +1,21 @@
name: Config Provider name: Config Provider Library
on: on:
workflow_call: workflow_call:
inputs: inputs:
config_path: config_path:
required: true required: true
type: string type: string
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
required: true
outputs: outputs:
env_json: env_json:
value: ${{ jobs.parse-config.outputs.json_data }} value: ${{ jobs.parse-config.outputs.json_data }}
config_path:
value: ${{ jobs.parse-config.outputs.config_path }}
env:
CI_CONF_FILE: ${{ inputs.config_path }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
jobs: jobs:
parse-config: parse-config:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
json_data: ${{ steps.convert.outputs.JSON_OUT }} json_data: ${{ steps.convert.outputs.JSON_OUT }}
config_path: ${{ steps.set-path.outputs.CONFIG_PATH }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Validate CI config
run: bash .ci/scripts/ci-validate.sh
- id: convert - id: convert
run: | run: |
@@ -49,6 +29,3 @@ jobs:
CLEAN_JSON=$(echo "$JSON_STRING" | jq -c .) CLEAN_JSON=$(echo "$JSON_STRING" | jq -c .)
echo "JSON_OUT=$CLEAN_JSON" >> "$GITHUB_OUTPUT" echo "JSON_OUT=$CLEAN_JSON" >> "$GITHUB_OUTPUT"
- id: set-path
run: echo "CONFIG_PATH=${{ inputs.config_path }}" >> "$GITHUB_OUTPUT"
-116
View File
@@ -1,116 +0,0 @@
name: Docker Build & Push
on:
workflow_call:
inputs:
env_json:
required: true
type: string
version:
required: true
type: string
secrets:
GITEA_TOKEN:
required: true
DOCKER_USERNAME:
required: false
DOCKER_PASSWORD:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
DOCKER_REGISTRY: ${{ fromJson(inputs.env_json).DOCKER_REGISTRY || '' }}
DOCKER_IMAGE_NAME: ${{ fromJson(inputs.env_json).DOCKER_IMAGE_NAME || '' }}
DOCKER_UI_URL: ${{ fromJson(inputs.env_json).DOCKER_UI_URL || '' }}
DOCKERFILE: ${{ fromJson(inputs.env_json).DOCKERFILE || 'Dockerfile' }}
GIT_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
VERSION: ${{ inputs.version }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Build and push container
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME || github.actor }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
if [ -z "${DOCKER_REGISTRY}" ]; then echo "ERROR: DOCKER_REGISTRY not set in env.conf"; exit 1; fi
if [ -z "${DOCKER_IMAGE_NAME}" ]; then echo "ERROR: DOCKER_IMAGE_NAME not set in env.conf"; exit 1; fi
REGISTRY="${DOCKER_REGISTRY}"
IMAGE="${DOCKER_IMAGE_NAME}"
REGISTRY_HOST="${REGISTRY%%/*}"
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
CONTEXT_DIR=$(dirname "${DOCKERFILE}")
docker build \
--label "git.commit=${{ github.sha }}" \
--label "git.commitBy=${{ github.actor }}" \
--label "build.date=${NOW}" \
-f "${DOCKERFILE}" \
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:latest" \
"${CONTEXT_DIR}"
FULL_IMAGE="${REGISTRY}/${IMAGE}:${VERSION}"
echo "Pushing ${FULL_IMAGE} ..."
docker tag "${IMAGE}:${VERSION}" "$FULL_IMAGE"
echo "$DOCKER_PASSWORD" | docker login "$REGISTRY_HOST" -u "$DOCKER_USERNAME" --password-stdin
docker push "$FULL_IMAGE"
FULL_LATEST="${REGISTRY}/${IMAGE}:latest"
echo "Pushing ${FULL_LATEST} ..."
docker tag "${IMAGE}:latest" "$FULL_LATEST"
docker push "$FULL_LATEST"
docker logout "$REGISTRY_HOST"
- name: Report status SUCCESS
if: success()
run: |
CONTAINER_URL=""
if [ -n "${DOCKER_UI_URL:-}" ] && [ -n "${VERSION:-}" ]; then
CONTAINER_URL="${DOCKER_UI_URL}/${DOCKER_IMAGE_NAME}/${VERSION}"
fi
DIR=$(dirname "${DOCKERFILE}")
if [ "$DIR" != "." ]; then
bash .ci/scripts/report-status.sh success "${DIR}: Docker push ${VERSION}" "${DIR}-ci-docker-build-push" "" "$CONTAINER_URL"
else
bash .ci/scripts/report-status.sh success "Docker push ${VERSION}" ci-docker-build-push "" "$CONTAINER_URL"
fi
tag-commit:
runs-on: ubuntu-latest
needs: [build-push]
steps:
- uses: actions/checkout@v4
- name: Create git tag
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
SERVER_URL: ${{ gitea.server_url }}
RUN_NUMBER: ${{ github.run_number }}
SHA: ${{ github.sha }}
run: |
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
"$SERVER_URL/api/v1/repos/${{ github.repository }}/tags" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${GIT_TAG_PREFIX}${VERSION}\", \"message\": \"Build #$RUN_NUMBER\", \"target\": \"$SHA\"}")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "409" ]; then
exit 0
else
exit 1
fi
-51
View File
@@ -1,51 +0,0 @@
name: Bats Tests
on:
workflow_call:
inputs:
env_json:
required: true
type: string
bats-image:
required: false
type: string
default: gitea.app.keskikuja.site/niko/ci-bats:git
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
jobs:
bats:
runs-on: ubuntu-latest
container:
image: ${{ inputs.bats-image }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Run bats tests
run: |
mkdir -p reports/bats
bashcov -- bats tests/ > reports/bats/results.txt 2>&1
- name: Post-process coverage
if: always()
run: bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
- name: Post-process test report
if: always()
run: bash .ci/.gitea/scripts/bats-report.sh reports/bats
- name: Report
if: always()
run: bash .ci/scripts/ci-report.sh "Bats test report" unit-tests bats ${{ job.status }}
@@ -1,41 +0,0 @@
name: CI Container Build Bats
on:
workflow_dispatch:
inputs:
config_path:
required: true
type: string
default: '.gitea/workflows/example-gitea-env.conf'
description: 'Polku .gitea-env.conf-tiedostoon'
dockerfile_path:
required: true
type: string
default: 'Dockerfile.ci-bats'
description: 'Polku Dockerfileen'
image_name:
required: true
type: string
default: 'ci-bats'
description: 'Kontin nimi ilman registry-polkua'
tag:
required: true
type: string
default: 'latest'
description: 'Image-tägi'
jobs:
load-config:
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
dockerfile_path: ${{ inputs.dockerfile_path }}
image_name: ${{ inputs.image_name }}
tag: ${{ inputs.tag }}
@@ -1,41 +0,0 @@
name: CI Container Build Cucumber
on:
workflow_dispatch:
inputs:
config_path:
required: true
type: string
default: '.gitea/workflows/example-gitea-env.conf'
description: 'Polku .gitea-env.conf-tiedostoon'
dockerfile_path:
required: true
type: string
default: 'Dockerfile.ci-cucumber'
description: 'Polku Dockerfileen'
image_name:
required: true
type: string
default: 'ci-cucumber'
description: 'Kontin nimi ilman registry-polkua'
tag:
required: true
type: string
default: 'latest'
description: 'Image-tägi'
jobs:
load-config:
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
dockerfile_path: ${{ inputs.dockerfile_path }}
image_name: ${{ inputs.image_name }}
tag: ${{ inputs.tag }}
@@ -1,47 +0,0 @@
name: Cucumber Tests
on:
workflow_call:
inputs:
env_json:
required: true
type: string
cucumber-node-image:
required: false
type: string
default: gitea.app.keskikuja.site/niko/ci-cucumber:with-python
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
jobs:
cucumber:
runs-on: ubuntu-latest
container:
image: ${{ inputs.cucumber-node-image }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Run cucumber tests
shell: bash
run: |
mkdir -p reports/cucumber
npx cucumber-js \
--format json:reports/cucumber/results.json \
--format html:reports/cucumber/test-report.html 2>&1
- name: Report
if: always()
shell: bash
run: bash .ci/scripts/ci-report.sh "Cucumber test report" acc-tests cucumber ${{ job.status }}
-39
View File
@@ -1,39 +0,0 @@
name: CI Feature
on:
push:
branches-ignore:
- main
workflow_dispatch:
jobs:
load-config:
name: Load example-gitea-env.conf to pipeline env
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: .gitea/workflows/example-gitea-env.conf
bats:
name: Bats tests
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/example-bats-tests.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
cucumber:
name: Cucumber tests
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/example-cucumber-tests.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
report-summary:
name: Report Summary
needs: [load-config, bats, cucumber]
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
-81
View File
@@ -1,81 +0,0 @@
name: CI Main
on:
push:
branches:
- main
- feature/gitops
workflow_dispatch:
jobs:
load-config:
name: Load example-gitea-env.conf to pipeline env
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: .gitea/workflows/example-gitea-env.conf
check-version:
name: Check existing artifact
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/check-version.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
bats:
name: Bats tests
needs: [load-config, check-version]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: niko/gitea-ci-library/.gitea/workflows/example-bats-tests.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
cucumber:
name: Cucumber tests
needs: [load-config, check-version]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: niko/gitea-ci-library/.gitea/workflows/example-cucumber-tests.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
docker-build-push:
name: Build & Push Docker
needs: [load-config, check-version, bats, cucumber]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: niko/gitea-ci-library/.gitea/workflows/docker-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
docker-gitops:
name: GitOps
needs: [docker-build-push]
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
GITOPS_FILE: dev/values.yaml
GITOPS_YQ_TPL: '.service.tag = "{{VERSION}}"'
GITOPS_REPO: niko/gitea-ci-gitops-tests
report-summary:
name: Report Summary
needs: [load-config, check-version, docker-build-push, docker-gitops]
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.docker-gitops.outputs.summary }}
tag-maintenance:
name: Move provider version tag
needs: [docker-gitops]
if: success()
uses: niko/gitea-ci-library/.gitea/workflows/tag-maintenance.yml@main
secrets: inherit
-11
View File
@@ -1,11 +0,0 @@
name: Hello World (Manual)
on:
workflow_dispatch:
push:
# branches: [ main ] # Tai [ feature/ci-container ]
branches: [feature/ci-container]
jobs:
greet:
runs-on: ubuntu-latest
steps:
- run: echo "Hello"
-59
View File
@@ -1,59 +0,0 @@
name: CI Git-Pages Main
on:
push:
branches:
- main
paths:
- git-pages/**
- .gitea/workflows/git-pages.*
workflow_dispatch:
jobs:
load-config:
name: Load git-pages.gitea-env.conf to pipeline env
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
secrets: inherit
with:
config_path: .gitea/workflows/git-pages.gitea-env.conf
check-version:
name: Check existing artifact
needs: [load-config]
uses: niko/gitea-ci-library/.gitea/workflows/check-version.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
helm-push:
name: Build & Push Helm chart
needs: [load-config, check-version]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: niko/gitea-ci-library/.gitea/workflows/helm-build-push.yml@main
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
chart_path: git-pages
chart-gitops:
name: Update chart to the cluster
needs: [helm-push]
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
GITOPS_FILE: dev/Chart.yaml
GITOPS_YQ_TPL: '(.dependencies[] | select(.name == "git-pages") | .version) = "{{VERSION}}"'
GITOPS_REPO: niko/gitea-ci-gitops-tests
report-summary:
name: Report Summary
needs: [load-config, helm-push, chart-gitops]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: ""
gitops: |
${{ needs.chart-gitops.outputs.summary }}
@@ -1,6 +0,0 @@
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
@@ -2,6 +2,4 @@ GITEA_API_URL=https://gitea.app.keskikuja.site
GIT_PAGES_URL=https://ci-reports.helm-dev.keskikuja.site GIT_PAGES_URL=https://ci-reports.helm-dev.keskikuja.site
DOCKER_REGISTRY=gitea.app.keskikuja.site/niko DOCKER_REGISTRY=gitea.app.keskikuja.site/niko
DOCKER_IMAGE_NAME=gitea-ci-library-test-image DOCKER_IMAGE_NAME=gitea-ci-library-test-image
DOCKER_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container DOCKER_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container/gitea-ci-library-test-image
#DOCKERFILE=Dockerfile.platform
-58
View File
@@ -1,58 +0,0 @@
name: GitOps Dispatch
on:
workflow_call:
inputs:
env_json:
required: true
type: string
version:
required: true
type: string
GITOPS_FILE:
required: true
type: string
GITOPS_YQ_TPL:
required: true
type: string
GITOPS_REPO:
required: true
type: string
secrets:
GITOPS_DISPATCH_TOKEN:
required: true
outputs:
summary:
description: 'Pipe-format: component|version|status|commit_sha|repo'
value: ${{ jobs.dispatch.outputs.summary }}
env:
GITOPS_VERSION: ${{ inputs.version }}
GITOPS_FILE: ${{ inputs.GITOPS_FILE }}
GITOPS_YQ_TPL: ${{ inputs.GITOPS_YQ_TPL }}
GITOPS_REPO: ${{ inputs.GITOPS_REPO }}
GITOPS_SOURCE_REPO: ${{ github.repository }}
GITOPS_SOURCE_COMMIT: ${{ github.sha }}
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GITOPS_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
GITOPS_WORKFLOW: gitops-service.yaml
jobs:
dispatch:
runs-on: ubuntu-latest
outputs:
summary: ${{ steps.run.outputs.GITOPS_SUMMARY }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Run gitops dispatch
id: run
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"
-105
View File
@@ -1,105 +0,0 @@
name: Helm Build & Push
on:
workflow_call:
inputs:
env_json:
required: true
type: string
version:
required: true
type: string
secrets:
GITEA_TOKEN:
required: true
HELM_USER:
required: false
HELM_PASSWORD:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
HELM_REGISTRY: ${{ fromJson(inputs.env_json).HELM_REGISTRY || '' }}
HELM_UI_URL: ${{ fromJson(inputs.env_json).HELM_UI_URL || '' }}
GIT_TAG_PREFIX: ${{ fromJson(inputs.env_json).GIT_TAG_PREFIX || '' }}
CHART_FILE: ${{ fromJson(inputs.env_json).VERSION_FILE || 'Chart.yaml' }}
VERSION: ${{ inputs.version }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-push:
runs-on: ubuntu-latest
container:
image: alpine/helm:3.19.0
steps:
- name: Install Node.js for actions/checkout
# COMPROMISE: Requires internet access.
# Does NOT work in air-gapped environments.
# Replace with a custom image (e.g., extending alpine/helm + nodejs) if needed.
run: apk add --no-cache nodejs
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Package Helm chart
run: |
CHART_DIR=$(dirname "${CHART_FILE}")
helm dependency update "${CHART_DIR}"
helm package "${CHART_DIR}" \
--version "${VERSION}" \
--app-version "${VERSION}" \
--destination /tmp/helm-packages
- name: Push to OCI registry
env:
HELM_USER: ${{ secrets.HELM_USER || github.actor }}
HELM_PASSWORD: ${{ secrets.HELM_PASSWORD }}
run: |
REGISTRY="${HELM_REGISTRY:?HELM_REGISTRY not set in env.conf}"
echo "$HELM_PASSWORD" | helm registry login "${REGISTRY}" \
-u "$HELM_USER" \
--password-stdin
helm push /tmp/helm-packages/*.tgz "oci://${REGISTRY}"
helm registry logout "${REGISTRY}"
- name: Report status with UI link
if: success() && env.HELM_UI_URL != ''
run: |
CHART_NAME=$(grep '^name:' "${CHART_FILE}" | awk '{print $2}')
UI_URL="${HELM_UI_URL}/${CHART_NAME}/${VERSION}"
if [ "${CHART_PATH}" != "." ] && [ -n "${CHART_PATH}" ]; then
bash .ci/scripts/report-status.sh success "${CHART_PATH}: Helm push ${VERSION}" "${CHART_PATH}-ci-helm-build-push" "" "$UI_URL"
else
bash .ci/scripts/report-status.sh success "Helm push ${VERSION}" ci-helm-build-push "" "$UI_URL"
fi
tag-commit:
runs-on: ubuntu-latest
needs: [build-push]
steps:
- uses: actions/checkout@v4
- name: Create git tag
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
SERVER_URL: ${{ gitea.server_url }}
RUN_NUMBER: ${{ github.run_number }}
SHA: ${{ github.sha }}
run: |
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
"$SERVER_URL/api/v1/repos/${{ github.repository }}/tags" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${GIT_TAG_PREFIX}${VERSION}\", \"message\": \"Build #$RUN_NUMBER\", \"target\": \"$SHA\"}")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "409" ]; then
exit 0
else
exit 1
fi
+173
View File
@@ -0,0 +1,173 @@
name: Quality Gate
on:
workflow_call:
inputs:
env_json:
required: true
type: string
bats-image:
required: true
type: string
cucumber-node-image:
required: true
type: string
secrets:
GITEA_TOKEN:
required: true
GIT_PAGES_PUBLISH_TOKEN:
required: true
env:
GITEA_API_URL: ${{ fromJson(inputs.env_json).GITEA_API_URL }}
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GIT_PAGES_PUBLISH_TOKEN: ${{ secrets.GIT_PAGES_PUBLISH_TOKEN }}
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Pending
run: bash .ci/scripts/report-status.sh pending "Validating CI config..." ci-validate
- name: Validate CI config
id: validate
run: bash .ci/scripts/ci-validate.sh
- name: Report status
if: always()
run: |
if [ "${{ steps.validate.outcome }}" = "success" ]; then
bash .ci/scripts/report-status.sh success "CI config valid" ci-validate
else
bash .ci/scripts/report-status.sh failure "CI validation FAILED" ci-validate
exit 1
fi
bats:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Pending
run: bash .ci/scripts/report-status.sh pending "Running Bats tests..." ci-bats
- name: Run bats tests
id: bats-tests
shell: bash
run: |
docker volume create bats-workspace
tar c . | docker run --rm -i -v bats-workspace:/data alpine tar x -C /data
mkdir -p "reports/${GITHUB_SHA:0:8}/bats"
set +e
docker run --rm \
-v bats-workspace:/data \
--entrypoint bash ${{ inputs.bats-image }} \
-c 'apk add -q lsof python3 jq curl ruby && cd /data && gem install bashcov -v 3.3.0 2>&1 | tail -1 && bashcov -- bats tests/' \
> "reports/${GITHUB_SHA:0:8}/bats/results.txt" 2>&1
BATS_EXIT=$?
bash .ci/.gitea/scripts/bats-coverage.sh bats-workspace "reports/${GITHUB_SHA:0:8}/bats"
docker volume rm bats-workspace > /dev/null 2>&1
bash .ci/.gitea/scripts/bats-report.sh "reports/${GITHUB_SHA:0:8}/bats"
echo "BATS_EXIT=${BATS_EXIT}" >> "${GITHUB_ENV}"
exit ${BATS_EXIT}
- name: Publish bats reports
if: always()
run: bash .ci/scripts/publish-git-pages.sh bats
- name: Report status
if: always()
run: |
if [ "${BATS_EXIT}" = "0" ]; then
bash .ci/scripts/report-status.sh success "Bats tests OK" ci-bats bats
else
bash .ci/scripts/report-status.sh failure "Bats tests FAILED" ci-bats bats
fi
cucumber:
runs-on: ubuntu-latest
container:
image: ${{ inputs.cucumber-node-image }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Pending
run: bash .ci/scripts/report-status.sh pending "Running Cucumber tests..." ci-cucumber
- name: Run cucumber tests
id: cucumber-tests
shell: bash
run: |
apt-get update -qq && apt-get install -y -qq --no-install-recommends lsof jq
npm install @cucumber/cucumber > /dev/null 2>&1
mkdir -p "reports/${GITHUB_SHA:0:8}/cucumber"
set +e
npx cucumber-js \
--format json:"reports/${GITHUB_SHA:0:8}/cucumber/report.json" \
--format html:"reports/${GITHUB_SHA:0:8}/cucumber/index.html" 2>&1
CUCUMBER_EXIT=$?
echo "CUCUMBER_EXIT=${CUCUMBER_EXIT}" >> "${GITHUB_ENV}"
exit ${CUCUMBER_EXIT}
- name: Publish cucumber reports
if: always()
run: bash .ci/scripts/publish-git-pages.sh cucumber
- name: Report status
if: always()
run: |
if [ "${CUCUMBER_EXIT}" = "0" ]; then
if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
bash .ci/scripts/report-status.sh success "Cucumber tests OK" ci-cucumber cucumber
else
bash .ci/scripts/report-status.sh success "Cucumber tests OK" ci-cucumber
fi
else
if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
bash .ci/scripts/report-status.sh failure "Cucumber tests FAILED" ci-cucumber cucumber
else
bash .ci/scripts/report-status.sh failure "Cucumber tests FAILED" ci-cucumber
fi
fi
build:
runs-on: ubuntu-latest
needs: [bats, cucumber]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Pending
run: bash .ci/scripts/report-status.sh pending "Generating report index..." ci-build
- name: Generate report index
id: report-index
run: bash .ci/.gitea/scripts/generate-report-index.sh
- name: Report status
if: always()
run: |
if [ "${{ steps.report-index.outcome }}" = "success" ]; then
bash .ci/scripts/report-status.sh success "Build complete" ci-build
else
bash .ci/scripts/report-status.sh failure "Build FAILED" ci-build
exit 1
fi
-57
View File
@@ -1,57 +0,0 @@
name: Report Summary
on:
workflow_call:
inputs:
env_json:
required: true
type: string
suites:
required: true
type: string
description: Space-separated suite names published to git-pages
gitops:
required: false
type: string
description: 'Pipe-separated rows: component|version|status|commit_sha|repo'
env:
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
jobs:
summary:
runs-on: ubuntu-latest
steps:
- name: Generate report links
shell: bash
run: |
SHA8="${GITHUB_SHA:0:8}"
BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}"
{
echo "## Test Reports"
echo ""
echo "| Suite | Report |"
echo "|-------|--------|"
for suite in ${{ inputs.suites }}; do
echo "| ${suite} | [View report](${BASE}/${suite}/) |"
done
} >> "${GITHUB_STEP_SUMMARY}"
if [ -n "${{ inputs.gitops }}" ]; then
GITEA_URL="${{ fromJson(inputs.env_json).GITEA_API_URL }}"
{
echo ""
echo "## GitOps updates"
echo ""
echo "| Component | Version | Status | GitOps commit |"
echo "|-----------|---------|--------|--------------|"
echo '${{ inputs.gitops }}' | while IFS='|' read -r comp ver status sha repo; do
[ -z "$comp" ] && continue
if [ -n "$sha" ]; then
echo "| $comp | $ver | $status | [link]($GITEA_URL/$repo/commit/$sha) |"
else
echo "| $comp | $ver | $status | — |"
fi
done
} >> "${GITHUB_STEP_SUMMARY}"
fi
-38
View File
@@ -1,38 +0,0 @@
name: Tag Maintenance
on:
workflow_call:
secrets:
GITEA_TOKEN:
required: true
jobs:
move-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Read current provider version
id: version
run: echo "TAG=$(cat CURRENT_PROVIDER_VERSION | tr -d '[:space:]')" >> "$GITHUB_OUTPUT"
- name: Move tag to commit
run: |
TAG="${{ steps.version.outputs.TAG }}"
SHA="${{ github.sha }}"
curl -sf -X DELETE \
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
"${{ gitea.server_url }}/api/v1/repos/${{ github.repository }}/tags/${TAG}" || true
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
-H "Content-Type: application/json" \
"${{ gitea.server_url }}/api/v1/repos/${{ github.repository }}/tags" \
-d "{\"tag_name\": \"${TAG}\", \"message\": \"Release ${TAG}\", \"target\": \"${SHA}\"}")
if [ "$HTTP_CODE" = "201" ]; then
echo "${TAG} tag moved to ${SHA}"
else
echo "ERROR: failed to move ${TAG} tag (HTTP $HTTP_CODE)" >&2
exit 1
fi
-1
View File
@@ -7,4 +7,3 @@ tmp/
coverage/ coverage/
.DS_Store .DS_Store
reports/ reports/
.vscode/
-1
View File
@@ -1 +0,0 @@
v1
-3
View File
@@ -1,3 +0,0 @@
FROM bats/bats:1.11.0
RUN apk add --no-cache lsof python3 jq curl ruby nodejs git && \
gem install bashcov -v 3.3.0
-7
View File
@@ -1,7 +0,0 @@
FROM node:22
RUN apt-get update -qq && \
apt-get install -y -qq --no-install-recommends lsof jq python3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
npm install -g @cucumber/cucumber
ENV NODE_PATH=/usr/local/lib/node_modules
+2 -25
View File
@@ -2,15 +2,6 @@
Reusable workflow -kirjasto Gitea Actionsille. Lisätietoja: [docs/](docs/) Reusable workflow -kirjasto Gitea Actionsille. Lisätietoja: [docs/](docs/)
**Consumer-käyttöönotto:** [skills/consumer-pipelines/SKILL.md](skills/consumer-pipelines/SKILL.md) — pipeline-standardit ja säännöt consumer-projekteille
**GitOps-päivitys:** [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md) — GitOps-repon job-template, dispatch ja token-ohjeet
**Single repo & monorepo:** Kirjasto toimii molemmissa. Monorepo-tuki
polkusuodatuksella, komponenttikohtaisilla versioilla ja git-tägien
etuliitteillä — jokainen komponentti julkaistaan itsenäisesti omassa
tahdissaan. Katso [skills/consumer-pipelines/SKILL.md](skills/consumer-pipelines/SKILL.md).
## Provider-binding — miten consumer löytää providerin ## Provider-binding — miten consumer löytää providerin
Consumer kutsuu provideria `uses:`-viittauksella. Ei discoveryä — polku kovakoodataan consumerin Consumer kutsuu provideria `uses:`-viittauksella. Ei discoveryä — polku kovakoodataan consumerin
@@ -125,8 +116,8 @@ Hae token Giteasta:
```bash ```bash
GITEA_URL="https://<gitea-server-url>" GITEA_URL="https://<gitea-server-url>"
GITEA_ACTIONS_NAMESPACE="gitea-actions"
GITEA_ACTIONS_TOKEN="<registration-token>" GITEA_ACTIONS_TOKEN="<registration-token>"
GITEA_ACTIONS_NAMESPACE="gitea-actions"
``` ```
### 3. Tee secret vain init install yhteydessä ### 3. Tee secret vain init install yhteydessä
@@ -160,7 +151,6 @@ helm upgrade --install act-runner gitea/actions \
--set giteaRootURL="$GITEA_URL" \ --set giteaRootURL="$GITEA_URL" \
--set existingSecret=act-runner-token \ --set existingSecret=act-runner-token \
--set existingSecretKey=token \ --set existingSecretKey=token \
--set statefulset.replicas=3 \
--set statefulset.runner.tag=1.0.8 \ --set statefulset.runner.tag=1.0.8 \
--set statefulset.dind.tag=29.5.2-dind \ --set statefulset.dind.tag=29.5.2-dind \
--set-string 'statefulset.runner.config=log: --set-string 'statefulset.runner.config=log:
@@ -222,9 +212,7 @@ Consumer-repossa on oltava seuraavat asetukset:
|--------|--------| |--------|--------|
| `GIT_PAGES_PUBLISH_TOKEN` | Git-pages-palvelimen BasicAuth-token. Nimi on lukittu — tämä tarkka nimi vaaditaan. | | `GIT_PAGES_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. `GITEA_TOKEN` on Gitean sisäinen secret (`secrets.GITEA_TOKEN`), joka on automauttisesti saatavilla — sitä ei tarvitse erikseen luoda.
Jos workflow tarvitsee oikeuksia **toiseen** repoon (esim. dispatch GitOps-repoon), tarvitaan manuaalinen token. Katso [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md).
### Config-tiedosto (`.gitea/workflows/gitea-env.conf`) ### Config-tiedosto (`.gitea/workflows/gitea-env.conf`)
@@ -260,17 +248,6 @@ Jokaisen jobin alussa `ci-validate.sh` tarkistaa:
Jos validointi epäonnistuu, job keskeytyy exit-koodilla 1 ja Gitean commit-status näyttää epäonnistumisen linkkinä lokiin. Jos validointi epäonnistuu, job keskeytyy exit-koodilla 1 ja Gitean commit-status näyttää epäonnistumisen linkkinä lokiin.
### GitOps-päivitys
Artifact buildin jälkeen voidaan dispatchata GitOps-repoon, joka päivittää
konfiguraatiotiedoston (esim. Chart.yaml version) ja pushaa muutoksen.
Kaksi skriptiä:
- `scripts/dispatch-workflow.sh` — lähettää workflow_dispatch-pyynnön ja pollaa valmistumista
- `scripts/gitops-update.sh` — kloonaa, päivittää yq:llä, committaa ja pushaa
Tarkka asennus: [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md)
### Muuta ### Muuta
| Muuttuja | Kuvaus | | Muuttuja | Kuvaus |
+9 -8
View File
@@ -6,8 +6,8 @@ Provider-repossa (`gitea-ci-library`) kansioiden omistajuus on seuraava:
| Kansio / Tiedosto | Omistaja | Tyyppi | | Kansio / Tiedosto | Omistaja | Tyyppi |
|-------------------|----------|--------| |-------------------|----------|--------|
| `.gitea/workflows/` | Sekoitettu | Providerin reusable workflowt + consumerin example-pipeline | | `.gitea/workflows/` | Sekoitettu | Providerin reusable workflowt + consumerin pipeline |
| `.gitea/workflows/example-gitea-env.conf` | Consumer | KEY=VALUE config | | `.gitea/workflows/gitea-env.conf` | Consumer | KEY=VALUE config |
| `.gitea/scripts/` | Consumer | Consumer-skriptit | | `.gitea/scripts/` | Consumer | Consumer-skriptit |
| `scripts/` | Provider | Providerin sisäiset työkalut | | `scripts/` | Provider | Providerin sisäiset työkalut |
@@ -30,12 +30,12 @@ uses: org/repo/scripts/workflow.yml@branch
``` ```
Tästä syystä providerin reusable workflowt (`config-provider.yml`, Tästä syystä providerin reusable workflowt (`config-provider.yml`,
`check-version.yml`, `docker-build-push.yml`) ovat samassa `.gitea/workflows/`-kansiossa `build-feature.yml`) ovat samassa `.gitea/workflows/`-kansiossa consumerin
consumerin esimerkkipipeline-tiedostojen (`example-*`) kanssa. pipeline-tiedostojen (`ci.yml`) kanssa.
Erottelu on nimessä ja dokumentaatiossa, ei kansiorakenteessa: Erottelu on nimessä ja dokumentaatiossa, ei kansiorakenteessa:
- `config-provider.yml`, `check-version.yml`, `docker-build-push.yml` — providerin tarjoamia - `config-provider.yml`, `build-feature.yml` — providerin tarjoamia
- `example-feature.yml`, `example-main.yml`, `example-*.yml` — consumer-esimerkkejä - `ci.yml` — consumerin omistamia
## Providerin `scripts/` (juuressa) ## Providerin `scripts/` (juuressa)
@@ -52,7 +52,7 @@ Consumerin omat skriptit, osana consumerin pipeline-logiikkaa.
Kutsutaan consumerin workflowista ilman tupla checkouttia: Kutsutaan consumerin workflowista ilman tupla checkouttia:
`.gitea/scripts/bats-report.sh`. `.gitea/scripts/bats-report.sh`.
## Consumerin `.gitea/workflows/example-gitea-env.conf` ## Consumerin `.gitea/workflows/gitea-env.conf`
Consumerin konfiguraatiotiedosto. Providerin `config-provider.yml` Consumerin konfiguraatiotiedosto. Providerin `config-provider.yml`
lukee tämän ja muuntaa JSONiksi, mutta consumer omistaa sisällön. lukee tämän ja muuntaa JSONiksi, mutta consumer omistaa sisällön.
@@ -61,7 +61,8 @@ lukee tämän ja muuntaa JSONiksi, mutta consumer omistaa sisällön.
- Provider voi muuttaa `scripts/` ja `config-provider.yml` sisältöä - Provider voi muuttaa `scripts/` ja `config-provider.yml` sisältöä
ilman consumerin hyväksyntää (versiovaihdon yhteydessä) ilman consumerin hyväksyntää (versiovaihdon yhteydessä)
- Consumer voi muuttaa `example-*.yml` ja `.gitea/scripts/` sisältöä - Consumer voi muuttaa `.gitea/workflows/ci.yml`,
`.gitea/workflows/build-feature.yml` ja `.gitea/scripts/` sisältöä
ilman providerin muutoksia ilman providerin muutoksia
- Providerin workflowt käyttävät `.ci/scripts/...` -polkua (tupla checkout) - Providerin workflowt käyttävät `.ci/scripts/...` -polkua (tupla checkout)
- Consumerin workflowt käyttävät `.gitea/scripts/...` -polkua (natiivi checkout) - Consumerin workflowt käyttävät `.gitea/scripts/...` -polkua (natiivi checkout)
-85
View File
@@ -1,85 +0,0 @@
# 7. Statusraportoinnin pattern
## Päätös
Gitea Actionsin natiivi job-status on ensisijainen. Commit-status API:a
(`report-status.sh`) käytetään **vain** kun työvaihe tuottaa ulkoisen linkin
(testiraportti, Docker registry), jota natiivistaatus ei tue.
### Tool-jobit (validate, check-version, tag-commit)
Ei API-kutsuja. Luotetaan Gitean omaan job-statukseen.
```yaml
- uses: actions/checkout@v4
- name: Do work
run: do-something
```
### Test-jobit (bats, cucumber)
API:a käytetään raporttilinkin upottamiseksi commit-näkymään.
```
testit → publish (always) → status (always, exit-koodin mukaan)
```
```yaml
- name: Run tests
shell: bash
run: |
run-tests
EXIT=$?
echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
exit ${EXIT}
- name: Publish reports
if: always()
run: bash .ci/scripts/publish-git-pages.sh bats
- name: Report status
if: always()
shell: bash
run: |
if [ "${EXIT}" = "0" ]; then
bash .ci/scripts/report-status.sh success "Link to Bats reports" unit-tests bats
else
bash .ci/scripts/report-status.sh failure "Link to Bats reports" unit-tests bats
fi
```
### Build & push -jobit (docker-build-push)
API:a käytetään Docker registry -linkin upottamiseksi.
```
build → push → SUCCESS (registry-linkillä) / FAILURE
```
## Periaatteet
1. Gitea Actionsin natiivi job-status on ensisijainen — myös PENDING/Running-tila
tulee natiivisti. API:a käytetään vain custom-linkin tarpeeseen (ADR 0004).
2. `run`-komennon on nostettava virhe ylös — oli kyse tool-callista tai
testivirheestä (ADR 0008).
3. Test-jobit käyttävät `if: always()` publish- ja status-stepeissä — raportti
julkaistaan ja status asetetaan aina, riippumatta testin lopputuloksesta.
4. Testiraportit julkaistaan myös virhetilanteessa, mikäli ne ovat syntyneet.
5. Commit-statuksen duplikaatio natiivijob-statuksen kanssa hyväksytään
testijobeille — se on ainoa mekanismi upottaa raporttilinkki commit-näkymään.
6. Tool-jobit eivät käytä API:a lainkaan — ne luottavat Gitean natiiviin
job-statukseen.
## Tausta
Aiemmin commit-status API:a käytettiin jokaisessa työvaiheessa, myös niissä
joilla ei ollut raporttia linkitettäväksi (validate, check-version, tag-commit).
Tämä tuotti duplikaatiota: Gitea näytti sekä natiivin `CI Main / Validate CI config
Successful` että API-statuksen `ci-validate CI config valid`. Kehittäjälle
molemmat kertoivat saman asian.
Käytännön pakko kuitenkin pakottaa API:n käyttöön testijobeissa: ilman
raporttilinkkiä kukaan ei löydä testituloksia. Gitean natiivi job-status
linkittää aina jobin lokiin — ei ulkoiseen raporttiin. Tämä on paras
saatavilla oleva kompromissi.
-73
View File
@@ -1,73 +0,0 @@
# 8. Exit code — ainoa onnistumisen mittari
## Päätös
Jokaisen `run`-stepin on nostettava virheellinen exit-koodi ylös sellaisenaan.
Exit-koodia ei saa "syödä" missään tilanteessa. Onnistumisen ja epäonnistumisen
päättely tapahtuu **ainoastaan** exit-koodin perusteella — ei tiedostojen
olemassaolon, stdout-tulosteen tai minkään muun heuristiikan perusteella.
## Periaatteet
1. Exit-koodi on ainoa totuus. `0` = onnistui, kaikki muut = epäonnistui.
2. Exit-koodia ei saa syödä. Pipe (`|`) viimeisenä komentona `tee`:hen syö
exit-koodin — `docker run … | tee file` palauttaa aina 0.
3. Data transfer -pipet ovat sallittuja (`tar c . | docker run … tar x`),
koska niiden exit-koodilla ei ole semanttista merkitystä.
4. Testien tai työkalujen ajaminen ei saa päättyä pipeen.
5. `set -o pipefail` ei ole riittävä suojaus — PIPESTATUS resetoituu herkästi.
## Sallitut patternit
```yaml
# Oikein: suora ajo, exit koodi $?:iin
- name: Do work
run: |
some-command
EXIT=$?
echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
exit ${EXIT}
# Oikein: stdout talteen ilman pipeä
- name: Do work
run: |
some-command > results.txt 2>&1
EXIT=$?
echo "EXIT=${EXIT}" >> "${GITHUB_ENV}"
exit ${EXIT}
# Oikein: docker run ilman pipeä
- name: Run in container
run: |
docker run --rm image command > output.txt 2>&1
EXIT=$?
exit ${EXIT}
```
## Kielletyt patternit
```yaml
# Väärin: pipe syö exit-koodin
- run: docker run … | tee results.txt
# Väärin: pipe syö exit-koodin
- run: tar … | docker … | tee file
# Väärin: onnistumisen päättely tiedoston olemassaolosta
- run: |
some-command || true
[ -f success.txt ] && exit 0 || exit 1
```
## Tausta
Gitea Actionsissa `run`-stepin tila määräytyy viimeisen komennon exit-koodista.
Pipe (`|`) asettaa `$?`:ksi viimeisen komennon tuloksen — jos viimeinen komento
on `tee`, tulos on aina 0 riippumatta siitä mitä aiemmat komennot palauttivat.
Tämä on aiheuttanut tuotannossa tilanteita, joissa testit feilasivat mutta jobi
näytti vihreää, koska `tee` söi exit-koodin. Virhe havaittiin vasta kun raportteja
alettiin lukea manuaalisesti — commit-status valehteli.
Ratkaisu on yksiselitteinen: exit-koodi talteen `$?`-muuttujaan ennen kuin mikään
muu komento ehtii muuttaa sitä, ja stepin viimeinen komento on aina `exit ${EXIT}`.
@@ -1,53 +0,0 @@
# 9. Breaking changes kielletty
## Päätös
Providerin `v1`-tagin osoittamaa rajapintaa ei koskaan rikota.
Consumerin `uses:`-kutsut säilyvät yhteensopivina — uusi versiotagi
(`v2`, `v3`) luodaan VAIN jos taaksepäin yhteensopimaton muutos on
pakottava. Käytännössä: `v1` on pysyvä, ja sitä ylläpidetään
eteenpäin.
## Rajapinnan määritelmä
Providerin rajapinta = `config-provider.yml` ja `check-version.yml`
workflow_call-inputit ja -outputit:
- Inputtien nimet, tyypit ja required-arvot eivät muutu
- Outputtien nimet eivät katoa
- Secret-nimet eivät muutu
- Workflow-tiedoston nimi ja polku eivät muutu
Sisäinen toteutus (scriptit, checkout-logiikka, build-vaiheet) voi
muuttua vapaasti — consumer ei ole niistä riippuvainen.
## Versiotagin siirto
Tagi `v1` siirretään automaattisesti uusimpaan onnistuneeseen
main-commitin CI-ajoon (`tag-maintenance.yml`). Tagin nimi luetaan
tiedostosta `CURRENT_PROVIDER_VERSION`.
Jos breaking change joskus tulee pakottavaksi:
1. Päivitä `CURRENT_PROVIDER_VERSION``v2`
2. Luo uusi `v2`-tagi manuaalisesti (osoittaa uuteen rajapintaan)
3. `tag-maintenance.yml` alkaa ylläpitää `v2`:ta eteenpäin
4. `v1`-tagia **ei poisteta** — se jää osoittamaan viimeistä
v1-yhteensopivaa committia. `v1`:tä käyttävät consumerit
jatkavat toimintaansa ilman muutoksia.
5. Uudet consumerit ottavat käyttöön `@v2`:n.
Aktiivisesti ylläpidetään vain yhtä tagia (`CURRENT_PROVIDER_VERSION`).
Vanhat tagit säilyvät paikoillaan taaksepäin yhteensopivuutta varten.
## Perustelu
Yhden aktiivisen tagin ylläpito on yksinkertaisempaa kuin usean
rinnakkaisen version aktiivinen hallinta. Homelab-ympäristössä
consumerit ovat saman ylläpitäjän hallinnassa — eri tiimien
rinnakkaisia aktiiviversioita ei tarvita.
Breaking changen sattuessa vanha tagi säilyy — vanhat consumerit
eivät hajoa. Uusi tagi otetaan käyttöön uusissa consumereissa.
Breaking changen kielto pakottaa suunnittelemaan rajapinnat
huolellisesti etukäteen.
@@ -1,43 +0,0 @@
# 10. Pipeline-reititin — ei komentoja
## Päätös
CI-pipelinetiedostot (`ci-main.yml`, `ci-feature.yml`) ovat puhtaita
**reitittimiä**. Ne eivät saa sisältää `run:`-komentoja, inline-skriptejä
tai varsinaista build-/test-logiikkaa. Niiden ainoa sallittu sisältö on:
- `uses:` — viittaus reusable workflow'hun
- `needs:` — riippuvuus toisen jobin valmistumiseen
- `if:` — ehdollinen suoritus
- `secrets: inherit` — salaisuuksien välitys
## Mikä ei kuulu reitittimeen
- `run:`-komennot
- Inline-skriptit (shell, Python, tms.)
- `actions/checkout` (paitsi providerin sisäinen infrastruktuuri)
- Build-työkalut, testikomennot, Docker-komennot
- Raporttien generointi
## Missä logiikka on
| Kerros | Tiedosto | Sisältö |
|---|---|---|
| Reititin | `ci-main.yml`, `ci-feature.yml` | Vain `uses:`-kutsut |
| Workflow_call | `ci-unit-tests.yml`, `ci-acc-tests.yml`, … | Varsinainen testi-/build-logiikka |
| Provider | `config-provider.yml`, `check-version.yml`, … | Jaettu infrastruktuuri |
| Skriptit | `scripts/` | Apufunktiot (status, publish, validointi) |
## Perustelu
Reitittimen ja toteutuksen erottelu:
- Reititin kertoo **mitä** ajetaan ja **missä järjestyksessä** — luettavissa
yhdellä silmäyksellä ilman teknistä taustaa
- Toteutus (workflow_call) kertoo **miten** — tekninen henkilö voi keskittyä
yhteen tiedostoon kerrallaan
- Providerin reusable workflow't eivät koskaan sisällä inline-logiikkaa —
skriptit ovat omissa tiedostoissaan
Tämä on sama periaate kuin web-sovelluksissa: reititin ei sisällä
business-logiikkaa, vain HTTP-verbit ja polut.
+63 -65
View File
@@ -1,6 +1,6 @@
# AI Context: Gitea Actions CI -kirjasto # AI Context: Gitea Actions CI -kirjasto
**Updated**: 2026-06-19 (provider/consumer dual role, Project Skills -aktivointi) **Updated**: 2026-06-12 (POC-vaihe, suunniteltu uudelleenkirjoitus)
## Project Overview ## Project Overview
Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-, Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-,
@@ -8,92 +8,90 @@ raportointi-, deployment- ja test flow -prosessien orkestrointiin. Korvaa
`ci-jenkins-library`:n Gitea-natiivilla toteutuksella. Mikropalvelut `ci-jenkins-library`:n Gitea-natiivilla toteutuksella. Mikropalvelut
käyttävät kirjastoa `uses:`-direktiivillä. käyttävät kirjastoa `uses:`-direktiivillä.
POC on valmis: raporttien julkaisu git-pagesiin ja commit-status linkillä
toimii.
## Monorepo: kaksi erillistä kokonaisuutta ## Monorepo: kaksi erillistä kokonaisuutta
Tämä repo on käytännössä monorepo, jossa on kaksi itsenäistä osaa:
### 1. Juuri (`gitea-ci-library`) ### 1. Juuri (`gitea-ci-library`)
Provider- ja consumer-kirjasto: reusable workflowt, scriptit, ADRt, dokumentaatio, Provider-kirjasto: reusable workflowt, scriptit, ADRt, dokumentaatio.
ja consumer-esimerkit (dogfood). Consumer kutsuu provider-workflowta `uses:`-direktiivillä. Consumer kutsuu `build-feature.yml`-workflowa `uses:`-direktiivillä.
### 2. `git-pages/` — oma kokonaisuus ### 2. `git-pages/` — oma kokonaisuus
Helm-chartti Codeberg git-pagesille. Täysin itsenäinen — oma dokumentaatio, Helm-chartti Codeberg git-pagesille. Täysin itsenäinen — oma dokumentaatio,
omat tekniset valinnat, oma design-rationale. Kaikki git-pages-spesifi tieto omat tekniset valinnat, oma design-rationale. Kohdeltava kuten se olisi jo
kuuluu `git-pages/docs/`-alle, ei juuren `docs/`-kansioon. oma reponsa: kaikki git-pages-spesifi tieto kuuluu `git-pages/docs/`- alle,
ei juuren `docs/`-kansioon.
### Rajapinta juuren ja git-pagesin välillä
Ohut ja yksiselitteinen:
```
scripts/publish-git-pages.sh <report-dir>
→ PATCH tar osoitteeseen GIT_PAGES_URL
→ palauttaa BASE URL:n
git-pages tarjoaa:
- HTTP endpoint (GET/PATCH/PUT)
- retention (automaattinen)
- TLS, BasicAuth (Traefik)
```
Juuri ei tiedä git-pagesin sisäisestä toiminnasta (storage v2, .index,
blob-arkkitehtuuri). Git-pages ei tiedä workflowista, scripteistä tai
provider-logiikasta.
## Architecture (POC-tila)
- **Provider & Consumer -malli**: `build-feature.yml` on lukittu rajapinta.
ADR 0005.
- **Raporttien hostaus**: git-pages Helm-chartilla (`git-pages/`), `GIT_PAGES_URL` määrittää perusosoitteen.
- **Retention**: sidecar samassa podissa, HTTP API localhost:3000,
Gitea API branch-check.
- **Commit-status**: Gitea Actions näyttää automaattisesti. API vain
custom-linkkiin. ADR 0004.
- **Julkaisu**: `publish-git-pages.sh` → PATCH tar git-pagesiin.
## Repository Structure ## Repository Structure
| Path | Purpose | | Path | Purpose |
|---|---| |---|---|
| `.gitea/workflows/config-provider.yml` | Provider: lataa + validoi config-tiedoston, tuottaa `env_json` | | `.gitea/workflows/` | Reusable workflowt (`build-feature.yml`, `config-provider.yml`) |
| `.gitea/workflows/check-version.yml` | Provider: tarkistaa onko commitille jo artifact, laskee version | | `scripts/` | `publish-git-pages.sh`, `report-status.sh`, `dispatch-workflow.sh` |
| `.gitea/workflows/docker-build-push.yml` | Provider: buildaa + puskea Docker-imagen, tagittaa commitin | | **`git-pages/`** | **Oma kokonaisuus: Helm-chartti + docs + retention** |
| `.gitea/workflows/ci-container-build-push.yml` | Provider: buildaa + puskea CI-työkalukontin | | `docs/` | Root-tason arkkitehtuuri, ADRt (00010005) |
| `.gitea/workflows/example-*` | **Consumer-esimerkki**: tämän repon oma CI (dogfood) |
| `scripts/` | Provider-skriptit: `report-status.sh`, `publish-git-pages.sh`, `ci-validate.sh` |
| `.gitea/scripts/` | **Consumer-skriptit**: `bats-coverage.sh`, `bats-report.sh` |
| `docs/` | Arkkitehtuuri, ADRt (00040008) |
| `skills/consumer-pipelines/` | Consumer-pipeline-standardit (ks. Project Skills). Koskee vain consumer-puolta |
| `skills/ci-container-build/` | CI-kontin build-workflow'n template (ks. Project Skills) |
| `docs/adr/` | Architecture Decision Records | | `docs/adr/` | Architecture Decision Records |
| `git-pages/` | Raporttien hostaus (Helm-chartti) |
| `tests/` | Bats-testit skripteille | | `tests/` | Bats-testit skripteille |
| `.gitea/workflows/ci.yml` | Dogfood — kutsuu `build-feature.yml`:a |
### Provider workflowt (6 kpl) **Tarkemmat git-pages-asiat:** `git-pages/docs/` (implementation-notes,
architecture, design-rationale, secrets, tech-stack).
| Workflow | Input | Output | Kuvaus |
|---|---|---|---|
| `config-provider.yml` | `config_path` | `env_json`, `config_path` | Validoi ja jäsentää `.conf` → JSON. Sama kutsu hoitaa validoinnin. |
| `check-version.yml` | `env_json` | `artifact_exists`, `version` | Tarkistaa git-tagit ja `package.json`:n, laskee seuraavan version. Vain main-haarassa. |
| `docker-build-push.yml` | `env_json`, `version` | — | Buildaa Docker-imagen, puskea rekisteriin, tagittaa commitin. |
| `ci-container-build-push.yml` | `env_json`, `dockerfile_path`, `image_name`, `tag` | — | Buildaa CI-työkalukontin, puskea rekisteriin. Ei versiointia eikä git-tägäystä. |
| `report-summary.yml` | `env_json`, `suites` | — | Generoi `GITHUB_STEP_SUMMARY`-taulukon raporttilinkeillä (Gitea 1.27+) |
| `helm-build-push.yml` | `env_json`, `version` | — | Pakkaa + puskea Helm chartin OCI-registryyn, tagittaa commitin. **Tekninen velka:** asentaa node.js:n runtime-vaiheessa (`apk add --no-cache nodejs` ennen checkouttia) koska `alpine/helm`-kontissa ei ole nodea. Rikkoo Offline Container -periaatetta. Ratkaistaan myöhemmin: proper multi-tool CI-kontti (helm + nodejs + git) docker hubiin. Ei consumerin ongelma. |
### Example-tiedostot (consumer-referenssi)
| Tiedosto | Laukaisin | Flow |
|---|---|---|
| `example-feature.yml` | push [ei main] | load-config → bats + cucumber → report-summary |
| `example-main.yml` | push [main] | load-config → check-version → bats + cucumber → report-summary → docker-build-push |
| `example-bats-tests.yml` | workflow_call | Unit-testit Batsilla, raportit git-pagesiin, status linkillä |
| `example-cucumber-tests.yml` | workflow_call | Hyväksymätestit Cucumberilla, raportit git-pagesiin, status linkillä |
| `example-gitea-env.conf` | — | KEY=VALUE config tälle repolle |
## Provider & Consumer Dual Role
Tämä repo on **yhtä aikaa sekä provider että consumer**. Eri puolilla on eri säännöt:
- **Provider-puoli**: `.gitea/workflows/*.yml` (pl. `example-*`), `scripts/` — reusable workflowt joita muut projektit kutsuvat. Saa käyttää `docker run` -komentoja (esim. `docker-build-push.yml`). Consumer-pipeline-standardit (`skills/consumer-pipelines/`) eivät koske provideria.
- **Consumer-puoli**: `.gitea/workflows/example-*`, `.gitea/scripts/` — tämän repon oma CI (dogfood), toimii consumer-esimerkkinä. Käyttää `@main`-refiä provider-viittauksissa (sama repo). Noudattaa `skills/consumer-pipelines/`-sääntöjä.
- **Ulkoiset consumerit** käyttävät `@v1`-tagia provider-viittauksissa.
## Project Skills (skills/)
Tämä projekti sisältää omia `.ai/skills/`-järjestelmästä riippumattomia skillejä `skills/`-kansiossa. Jokainen alihakemisto sisältää `SKILL.md`:n jossa on `activation-gate`-kenttä.
**Sääntö:** Uuden tehtävän alussa skannaa `skills/*/SKILL.md` ja arvioi jokaisen `activation-gate` annettua tehtävää vasten. Jos gate matchaa, lataa skill aktiiviseksi ohjeeksi ennen toimenpiteitä.
| Skill | Gate | Kuvaus |
|---|---|---|
| `skills/consumer-pipelines/` | Consumer-pipeline-muutokset | Consumer-pipeline-standardit: reitittimen puhtaus, exit-koodi, konttipolitiikka, raportointi, nimeäminen. Koskee vain consumer-puolta. |
| `skills/ci-container-build/` | CI-kontin build | CI-kontin build-workflown template ja Dockerfile-ohjeet |
## Key Technical Decisions ## Key Technical Decisions
- **Provider & Consumer**: `build-feature.yml` lukittu rajapinta, muu koodi
- **Provider & Consumer -malli**: Tämä repo on sekä provider että consumer. Provider-workflowt reusableja muille, `example-*`-tiedostot tämän repon oma consumer-CI (dogfood). ADR 0005. vapaasti muutettavissa
- **Vain Gitea, vain reusable workflowt**: ei custom actioneita, ei multi-platform - **Vain Gitea, vain reusable workflowt**: ei custom actioneita, ei
- **Commit-status API vain raporttilinkeille**: Tool-jobit luottavat natiiviin. Test-jobit käyttävät API:a koska se on ainoa tapa upottaa raporttilinkki. ADR 0004, 0007. multi-platform
- **Exit-koodi on ainoa onnistumisen mittari**: Ei pipeä, ei tiedostoheuristiikkaa. ADR 0008.
- **Raportit git-pagesissa**: HTML selailtavissa, retention automaattinen - **Raportit git-pagesissa**: HTML selailtavissa, retention automaattinen
- **GITHUB_STEP_SUMMARY**: Summary-näkymä raporttilinkeille Gitea 1.27:ssä (forward-compat) - **Git-pages omana kokonaisuutena**: voi erottaa omaksi repokseen
tulevaisuudessa
## Tech Stack (POC)
- **Runtime:** Bash, curl, jq, python3 (retention whiteout)
- **Alusta:** Gitea Actions, Gitea act runner
- **Hostaus:** git-pages 0.9.1 (Codeberg), Traefik, cert-manager
- **Integraatiot:** Gitea REST API, Gitea Packages
## Common Commands ## Common Commands
- Helm-asennus: `helm upgrade --install git-pages ./git-pages -n <ns> -f <values>` - Helm-asennus: `helm upgrade --install git-pages ./git-pages -n <ns> -f <values>`
- Julkaisu: `bash scripts/publish-git-pages.sh <report-dir>` - Julkaisu: `bash scripts/publish-git-pages.sh <report-dir>`
- Status: `bash scripts/report-status.sh <state> <desc> <context> [suite] [url]` - Status: `bash scripts/report-status.sh <state> <desc> <url> <context>`
## What NOT to Do ## What NOT to Do
- Älä lisää tukea muille Git-alustoille - Älä lisää tukea muille Git-alustoille
- Älä lisää Docker custom actioneita ilman pakottavaa syytä - Älä lisää Docker custom actioneita ilman pakottavaa syytä
- Älä kirjoita git-pages-spesifiä tietoa juuren `docs/`-kansioon - Älä kirjoita git-pages-spesifiä tietoa juuren `docs/`-kansioon
- Älä käytä commit-status API:a jollei ole raporttia linkitettäväksi (ADR 0007) kuuluu `git-pages/docs/`-alle
- Älä käytä pipeä `run`-komennon viimeisenä — se syö exit-koodin (ADR 0008) - Älä POSTaa commit-status APIin jokaiselle vaiheelle — natiivi riittää
+17 -37
View File
@@ -1,6 +1,7 @@
# Architecture — Gitea Actions CI -kirjasto # Architecture — Gitea Actions CI -kirjasto
> Normatiivinen lähde: ADR 0004, 0005, 0006, 0007, 0008. > ⚠️ POC-vaihe. Tämä dokumentti kuvaa suunniteltua arkkitehtuuria.
> Normatiivinen lähde: ADR 0004, ADR 0005, `docs/design-rationale.md`.
--- ---
@@ -17,52 +18,31 @@ Kirjasto on Gitea-spesifi. Raportit hallinnoidaan git-pages Helm-chartilla
| Rooli | Kuvaus | | Rooli | Kuvaus |
|-------|--------| |-------|--------|
| **Provider** | `gitea-ci-library` — tarjoaa reusable workflowt (`config-provider.yml`, `check-version.yml`, `docker-build-push.yml`) ja scriptit | | **Provider** | `gitea-ci-library` — tarjoaa `build-feature.yml` (lukittu rajapinta) sekä scriptit |
| **Consumer** | Mikropalveluprojekti — kutsuu `uses:`-direktiivillä, omistaa pipeline-logiikan. Tämän repon oma toteutus: `example-*`-tiedostot | | **Consumer** | Mikropalveluprojekti — kutsuu `uses:`-direktiivillä, omistaa pipeline-logiikan |
Tarkemmin: ADR 0005. Tarkemmin: ADR 0005.
## Komponentit ## Komponentit (POC)
| Komponentti | Tyyppi | Kuvaus | | Komponentti | Tila |
|---|---|---| |-------------|------|
| `config-provider.yml` | Provider | Lataa + validoi `.conf`-tiedoston, tuottaa `env_json` | | `build-feature.yml` | Toimii. Ainoa reusable workflow. |
| `check-version.yml` | Provider | Tarkistaa git-tagit, laskee version, palauttaa `artifact_exists` + `version` | | `publish-git-pages.sh` | Toimii. PATCH tar git-pagesiin. |
| `docker-build-push.yml` | Provider | Buildaa Docker-imagen, puskea rekisteriin, tagittaa commitin | | `report-status.sh` | Toimii. POSTaa commit-status (vain custom-linkkiin). |
| `example-feature.yml` | Consumer | Feature-haaran CI: load-config → bats + cucumber → summary | | `dispatch-workflow.sh` | Toimii. Dispatchee workflown ja pollaa valmistumista. |
| `example-main.yml` | Consumer | Main-haaran CI: load-config → check-version → bats + cucumber → summary → docker | | `git-pages/` | Helm-chartti raporttien hostaukseen. Oma kokonaisuus, tarkemmin: `git-pages/docs/`. |
| `example-bats-tests.yml` | Consumer | Unit-testit Batsilla |
| `example-cucumber-tests.yml` | Consumer | Hyväksymätestit Cucumberilla |
| `report-summary.yml` | Provider | `GITHUB_STEP_SUMMARY`-taulukko raporttilinkeillä (Gitea 1.27+) |
| `publish-git-pages.sh` | Provider-skripti | PATCH tar git-pagesiin |
| `report-status.sh` | Provider-skripti | POSTaa commit-status (vain custom-linkkiin) |
| `ci-validate.sh` | Provider-skripti | Validoi `.conf`-tiedoston ja tarkistaa secretit |
| `dispatch-workflow.sh` | Provider-skripti | Dispatchee workflown ja pollaa valmistumista |
| `git-pages/` | Infra | Helm-chartti raporttien hostaukseen. Oma kokonaisuus |
## Statusraportointi
| Job-tyyppi | Mekanismi | Syy |
|---|---|---|
| Tool-jobit | Vain Gitea natiivi job-status | Ei raporttia linkitettäväksi |
| Test-jobit | Commit-status API linkillä | Ainoa tapa upottaa raporttilinkki commit-näkymään |
| Docker-build-push | Commit-status API linkillä | Linkki Docker registryyn |
Tarkemmin: ADR 0004, 0007.
## Ulkoiset palvelut ## Ulkoiset palvelut
| Palvelu | Rooli | | Palvelu | Rooli |
|---|---| |---------|-------|
| Gitea REST API | Commit-status (vain custom-linkit), git-tagit | | **Gitea REST API** | Commit-status, workflow-dispatch, run-pollaus |
| Gitea Packages | Docker-imagen säilytys | | **Gitea Packages** | Docker-imagen säilytys |
| git-pages | Raporttien hostaus | | **git-pages** | Raporttien hostaus |
## Arkkitehtuuriset rajoitteet ## Arkkitehtuuriset rajoitteet
- Provider-workflowt ovat reusableja (`workflow_call`), consumer omistaa orkestroinnin - `build-feature.yml` on ainoa consumerin kutsuma rajapinta (ADR 0005)
- Gitea Actionsin natiivi commit-status on ensisijainen (ADR 0004) - Gitea Actionsin natiivi commit-status on ensisijainen (ADR 0004)
- API:a käytetään vain custom-linkkeihin (ADR 0007)
- Exit-koodi on ainoa onnistumisen mittari — ei pipeä (ADR 0008)
- Raportit ovat julkisia URL:lla (osoite tunnettava) - Raportit ovat julkisia URL:lla (osoite tunnettava)
- Consumer-skriptit `.gitea/scripts/`-alla, provider-skriptit `scripts/`-alla (ADR 0006)
+1 -2
View File
@@ -1,5 +1,4 @@
**⚠️ STATUS: OSITTAIN VANHENTUNUT** — Statusraportointi (7) ja exit-koodit (8) **⚠️ STATUS: ALERT DRAFT** — Ei ole validoitu. Voi sisältää virheellisiä tai puutteellisia käytäntöjä.
on formalisoitu ADR:iin 0007 ja 0008. Loput osiot validioitu POC-ajossa.
# CI Pipeline Practices # CI Pipeline Practices
+254 -115
View File
@@ -1,130 +1,269 @@
# Konfiguraatiomalli — `gitea-env.conf` # Konfiguraatiomalli — `ci-flow-values.yaml`
> Consumer määrittelee ympäristökohtaiset arvot KEY=VALUE-muotoisessa > ⚠️ **POC-vaihe.** Tämä dokumentti on peritty Jenkins-versiosta ja sisältää
> `.conf`-tiedostossa. Providerin `config-provider.yml` validoi tiedoston > Docker-spesifistä legacyä (isContainerBuild, Docker-labelit). POC osoitti,
> ja muuntaa sen JSON:ksi (`env_json`), jota kaikki downstream-workflowt > että provider on agnostinen consumerin build-ekosysteemille.
> käyttävät. >
> Uusi ajattelu: `isContainerBuild()` → `isArtifactBuild()`. Provider ei
> tiedä eikä tarvitse tietää, buildaako consumer kontin, JARin, npm-paketin
> vai mitään muuta. Dokumentti odottaa uudelleenkirjoitusta.
>
> Normatiivinen lähde: `docs/design-rationale.md`, ADR 0005.
--- ---
## Tiedoston sijainti ja nimi ## Miksi redesign
Consumerin repossa: `.gitea/workflows/gitea-env.conf` (nimi vapaasti Jenkins-version `ci-flow-values.yaml` oli rakennettu Jenkinsin ympäristömuuttuja- ja credential-mallin ympärille. Gitea Actionsissa konfiguraatio luetaan suoraan YAML:sta workflow'n sisällä — ei tarvita env-muuttujien kautta kierrättämistä. Lisäksi:
valittavissa — `config-provider.yml`:n `config_path`-input määrittää polun).
Esimerkki (tämän repon dogfood): - `creditentials`-viittaukset korvautuvat Gitea org secrets/variables -mekanismilla
``` - `test-flow`-syntaksi yksinkertaistuu (ei enää JSON-serialisointia env-muuttujiin)
.gitea/workflows/example-gitea-env.conf - Docker/NPM-rekisterit MVP:ssä vain Gitea Packages — factory/adapter-pattern valmiina laajennukselle
``` - `sonarqube`-konfiguraatio pysyy samankaltaisena
---
## Skeema ## Skeema
```
KEY=VALUE
```
Yksi `KEY=VALUE` per rivi. Kommentit `#`-merkillä. Tyhjät rivit ohitetaan.
### Pakolliset avaimet
| Avain | Kuvaus | Esimerkki |
|---|---|---|
| `GITEA_API_URL` | Gitea-instanssin URL | `https://gitea.example.com` |
| `GIT_PAGES_URL` | Raporttihostauksen URL | `https://reports.example.com` |
### Docker-spesifit avaimet (vain jos käytetään `docker-build-push.yml`)
| Avain | Pakollinen | Kuvaus |
|---|---|---|
| `DOCKER_REGISTRY` | Kyllä | Rekisterin host/path, esim. `gitea.example.com/org` |
| `DOCKER_IMAGE_NAME` | Kyllä | Kontin nimi ilman registry-prefixiä |
| `DOCKER_UI_URL` | Ei | Linkki kontin UI-näkymään registryssä |
| `DOCKERFILE` | Ei | Dockerfile-nimi, oletus `Dockerfile` |
### Esimerkki
```ini
GITEA_API_URL=https://gitea.example.com
GIT_PAGES_URL=https://reports.example.com
DOCKER_REGISTRY=gitea.example.com/myorg
DOCKER_IMAGE_NAME=temperature-store
DOCKER_UI_URL=https://gitea.example.com/myorg/-/packages/container/temperature-store
#DOCKERFILE=Dockerfile.platform
```
---
## Salaisuudet
Salaisuudet eivät ole `.conf`-tiedostossa. Ne määritellään Gitean
organization/repository secrets -mekanismissa ja välitetään workflowlle
`secrets: inherit` -direktiivillä.
**`secrets.GITEA_TOKEN` on Gitean automaattisesti generoima token,
scopeutuu siihen repoon jossa workflow ajaa.** Se ei oikeuta
dispatchaamaan toiseen repoon eikä kirjoittamaan toisen repon
commit-statusta. Cross-repo-operaatioihin tarvitaan manuaalinen
org-tason token.
| Secret | Pakollinen | Käyttäjä |
|---|---|---|
| `GITEA_TOKEN` | Kyllä | `report-status.sh`, `check-version.yml`, `docker-build-push.yml`, `gitops-update.sh` (GitOps-repossa) |
| `GIT_PAGES_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` |
---
## `config-provider.yml` — lataus ja validointi
Provider-workflow joka lukee `.conf`-tiedoston, validoi sen ja palauttaa
JSON-muotoisen `env_json`:n.
**Input:** `config_path` (polku `.conf`-tiedostoon)
**Output:** `env_json` (JSON-string), `config_path` (sama polku takaisin)
**Validointi:**
- `.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
Kutsu:
```yaml ```yaml
load-config: # ci-flow-values.yaml — projektin juuressa
uses: org/gitea-ci-library/.gitea/workflows/config-provider.yml@v1 #
# Pakolliset osiot: docker (jos master-branch buildaa kontin), test-flow (jos ketjutetaan)
# Vapaaehtoiset: sonarqube, deployment
docker:
registry: gitea # gitea | artifactory | nexus (MVP: vain gitea)
imageName: temperature-store # kontin nimi, pakollinen
sonarqube:
url: https://sonar.example.com
projectKey: temperature-store
deployment:
jobName: deploy # deploy-workflown nimi (vakio)
projectFolder: microservices # polku Helm-repossa
fileName: values-{.environment}.yaml
property: container.version
test-flow:
- deploy: development # 1. deploy development-ympäristöön
wait: true # odota deployn valmistumista
- test:
name: "integration fast"
environment: integration
repo: tests/integration # testi-repo (owner/repo)
workflow: test.yml # workflow-tiedosto testi-repossa
ref: main # branch
tags: "@temperature and not @slow"
- deploy: staging
wait: true
- test:
name: e2e
environment: staging
repo: tests/e2e
workflow: test.yml
ref: main
tags: "@e2e and not @slow"
```
### Kenttäkuvaukset
#### `docker`
| Kenttä | Pakollinen | Kuvaus |
|--------|------------|--------|
| `registry` | Ei (oletus `gitea`) | Rekisterityyppi. MVP: `gitea`. Factory/adapter-pattern avaa `artifactory`, `nexus` myöhemmin |
| `imageName` | Kyllä | Kontin nimi. Lopullinen tagi: `{gitea_host}/{owner}/{imageName}:{version}.{run_number}` |
#### `sonarqube`
| Kenttä | Pakollinen | Kuvaus |
|--------|------------|--------|
| `url` | Kyllä | SonarQube-palvelimen URL |
| `projectKey` | Kyllä | SonarQube-projektin avain |
SonarQube-token tulee Gitea org secretsista (`SONAR_TOKEN`). Ei `creditentials`-viittausta.
#### `deployment`
| Kenttä | Pakollinen | Kuvaus |
|--------|------------|--------|
| `jobName` | Ei (oletus `deploy`) | Deploy-workflown tiedostonimi ilman `.yml`-päätettä |
| `projectFolder` | Kyllä | Polku mikropalvelun kansioon Helm-repossa |
| `fileName` | Kyllä | YAML-tiedoston nimi. `{.environment}` korvataan ympäristön nimellä |
| `property` | Kyllä | Päivitettävä avain (piste-eroteltu polku, esim. `container.version`) |
Deploy-token (kirjoitusoikeus Helm-repoon) tulee Gitea org secretsista (`DEPLOY_TOKEN`).
#### `test-flow`
Array testi-steppejä. Jokainen steppi on joko `deploy` tai `test`.
**`deploy`-steppi:**
| Kenttä | Pakollinen | Kuvaus |
|--------|------------|--------|
| `deploy` | Kyllä | Ympäristön nimi (esim. `development`, `staging`) |
| `wait` | Ei (oletus `true`) | Odotetaanko deployn valmistumista ennen seuraavaa steppiä |
**`test`-steppi:**
| Kenttä | Pakollinen | Kuvaus |
|--------|------------|--------|
| `name` | Kyllä | Testivaiheen nimi (näkyy statusviestissä) |
| `environment` | Kyllä | Ympäristö jota vasten testataan |
| `repo` | Kyllä | Testi-repo muodossa `owner/repo` |
| `workflow` | Kyllä | Workflow-tiedosto testi-repossa |
| `ref` | Kyllä | Branch (esim. `main`) |
| `tags` | Ei | Cucumber-tagit (esim. `"@smoke and not @slow"`) |
| `versionApiUrl` | Ei | URL deployed-version tarkistukseen |
| `versionCheckScript` | Ei | Polku version check -skriptiin (repossa tai kontissa) |
---
## `isArtifactBuild()`-mekanismi (POC: suunniteltu, ei toteutettu)
> **Jenkins-legacy:** Jenkins-versiossa tämä oli `isContainerBuild()`. POC
> osoitti, että provider ei tiedä eikä tarvitse tietää, buildaako consumer
> kontin, JARin vai npm-paketin. Siksi `isContainerBuild()` →
> `isArtifactBuild()`.
**Ongelma:** Samaa committia vasten voidaan ajaa master-workflow useita
kertoja. On mieletöntä buildata artifakti uudestaan, koska commit on jo
tagätty versiolla ja artifakti on olemassa rekisterissä. Uudelleenbuildaus
aiheuttaa versiokonflikteja ja tuhlaa CI-aikaa.
**Ratkaisu:** Workflow'n alussa tarkistetaan, onko tälle commitille jo
olemassa versiotagi.
```bash
# is-container-built.sh
TAG=$(git tag --points-at HEAD | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
if [ -n "$TAG" ]; then
echo "container_already_built=true" >> $GITHUB_ENV
echo "container_version=$TAG" >> $GITHUB_ENV
else
echo "container_already_built=false" >> $GITHUB_ENV
fi
```
Workflow käyttää tätä ehtona:
```yaml
jobs:
build-container:
if: env.container_already_built != 'true'
steps:
- run: docker build ...
test-flow:
if: always()
needs: [build-container]
steps:
- run: dispatch-workflow.sh ...
```
**Mitä `isContainerBuild() == true` tarkoittaa käytännössä:**
- Kontti on jo buildattu ja pushattu rekisteriin
- Commit on tagätty versiolla (esim. `1.2.3.42`)
- Build-steppi skipataan → siirrytään suoraan test flow'hun
- Sama versio deployataan ja testataan — ei uutta konttia
**Miksi tämä on välttämätöntä:**
- Estää versiokonfliktit: `1.2.3.42` ei voi olla kahdesti
- Säästää CI-aikaa: artifaktin buildaus on hitain vaihe
- Pitää commitin ja artifaktin välisen suhteen yksiselitteisenä: `git tag` kertoo suoraan mikä versio vastaa tätä committia
---
## Version check (Fibonacci-backoff)
Ennen kuin testit ajetaan, pitää varmistua että haluttu konttiversio on oikeasti deployattu ympäristöön. Muuten testataan väärää versiota.
**Malli:** Skripti, joka pollaa deployatun version API:a Fibonacci-backoffilla:
```bash
#!/bin/bash
# check-version.sh <version_api_url> <expected_version>
# Palauttaa 0 jos versiot täsmäävät, 1 muuten.
URL=$1
EXPECTED=$2
MAX_RETRIES=10
# Fibonacci-sekvenssi: 1 2 3 5 8 13 21 34 55 89
FIB=(1 2 3 5 8 13 21 34 55 89)
for i in $(seq 1 $MAX_RETRIES); do
ACTUAL=$(curl -s "$URL" | jq -r '.version // empty')
if [ "$ACTUAL" = "$EXPECTED" ]; then
echo "Version match: $ACTUAL"
exit 0
fi
echo "Attempt $i/$MAX_RETRIES: $ACTUAL != $EXPECTED, waiting ${FIB[$i-1]}s..."
sleep ${FIB[$i-1]}
done
echo "Version mismatch after $MAX_RETRIES attempts"
exit 1
```
**Miksi Fibonacci:** Uusi deploy käynnistyy nopeasti (ensimmäiset pollaukset tiheään). Jos kontin pullaus tai podin käynnistys kestää, pollausväli kasvaa — ei turhaan kuormiteta API:a. Maksimiaika: ~231 sekuntia (summa 1..89).
Version check -skripti joko:
- Asuu testi-repossa (projektin oma toteutus) → `versionCheckScript`-kenttä
- Tai käyttää geneeristä API:a → `versionApiUrl`-kenttä, skripti on osa kirjastoa
---
## `doNotDowngrade`
Jenkinsin deploy-jobissa oli `doNotDowngrade`-parametri, joka esti vanhemman version deployaamisen uudemman päälle. Gitea Actions -versiossa:
- **Ei MVP:ssä.** Deploy tekee sen mitä käsketään. `doNotDowngrade` on lisäturva, joka voidaan lisätä deploy-workflow'hun myöhemmin.
- **Mekanismi:** Ennen YAML:n muokkausta tarkistetaan nykyinen versio. Jos `new < current`, skipataan ja raportoidaan.
- **Toteutus:** Yksi `if`-ehto deploy-workflow'n alussa, ei vaadi muutoksia muualle.
---
## UX-esimerkki: projektin `.gitea/workflows/ci.yml`
Näin mikropalvelun kehittäjä käyttää kirjastoa:
```yaml
# .gitea/workflows/ci.yml — projektin juuressa
name: CI
on:
push:
branches: ["**"]
workflow_dispatch:
jobs:
feature:
if: github.ref != 'refs/heads/master'
uses: org/gitea-ci-library/.gitea/workflows/ci-feature.yml@v1
secrets: inherit secrets: inherit
with: with:
config_path: .gitea/workflows/gitea-env.conf config-file: ci-flow-values.yaml
maven-image: maven:3.9-eclipse-temurin-21
master:
if: github.ref == 'refs/heads/master'
uses: org/gitea-ci-library/.gitea/workflows/ci-master.yml@v1
secrets: inherit
with:
config-file: ci-flow-values.yaml
maven-image: maven:3.9-eclipse-temurin-21
docker-image: docker:26-dind
``` ```
--- Kehittäjä määrittelee:
- Millä kontilla buildataan (`maven-image` — koska kirjasto ei tiedä projektin Java-versiota)
- Mistä konfiguraatio luetaan (`config-file`)
- Millä docker-versiolla kontit rakennetaan (`docker-image`)
## `check-version.yml` — version päättely Kaikki Git-, raportointi-, SonarQube- ja deploy-konfiguraatio on `ci-flow-values.yaml`:ssa.
Provider-workflow joka etsii version prioriteettijärjestyksessä:
1. `VERSION`-tiedosto (plain text, esim. `0.2`)
2. `package.json``.version`-kenttä (Node.js)
3. `pom.xml``<version>`-elementti (Maven)
Hakee git-tagit Gitea API:sta ja laskee seuraavan vapaan patch-version.
**Output:** `artifact_exists` (true/false), `version` (string)
**Idempotentti:** Jos commitilla on jo versiotagi, `artifact_exists=true` ja
build-vaiheet skipataan. Samaa committia ei buildata kahdesti.
---
## `env_json`-propagointi
```
gitea-env.conf → config-provider.yml → env_json (JSON-string)
ci.yml with: env_json → kaikki downstream-workflowt
```
Jokainen provider-workflow purkaa tarvitsemansa arvot `fromJson(inputs.env_json).KEY`:lla.
Consumerin ei tarvitse tietää mitä avaimia kukin provider käyttää.
+107 -160
View File
@@ -1,195 +1,150 @@
# Design Rationale — Gitea Actions CI -kirjasto # Design Rationale — Gitea Actions CI -kirjasto
> Miksi kirjasto on rakennettu näin. Arvot, periaatteet ja reunaehdot, joiden > Miksi kirjasto on rakennettu näin. Arvot, periaatteet ja reunaehdot, joiden varaan arkkitehtuuri nojaa.
> varaan arkkitehtuuri nojaa.
> >
> Tämä dokumentti on **normatiivinen** — arkkitehtuurin on noudatettava näitä > Tämä dokumentti on **normatiivinen** — arkkitehtuurin on noudatettava näitä periaatteita. Jos ehdotettu muutos on ristiriidassa rationalen kanssa, rationalen on muututtava ensin.
> periaatteita. Jos ehdotettu muutos on ristiriidassa rationalen kanssa,
> rationalen on muututtava ensin.
--- ---
## Miksi tämä projekti on olemassa ## Miksi tämä projekti on olemassa
Mikropalveluarkkitehtuurissa jokainen palvelu tarvitsee CI-putken: testit, Organisaatiolla on tuotannossa Jenkins-pohjainen CI-järjestelmä (`ci-jenkins-library`, 53 lähdetiedostoa, 21 Cucumber-featurea), joka on osoittautunut toimivaksi vuosien ajan. Se integroi Git-commitit, testiraportoinnin, Docker-buildit, deploymentin ja test flow'n yhtenäiseksi putkeksi, jossa jokainen vaihe raportoi tilansa suoraan Git-committiin.
laatutarkistukset, buildin, kontituksen ja julkaisun. Ilman jaettua
kirjastoa jokainen tiimi kopioi saman YAML-boilerplaten, tekee omat
virheensä ja ylläpitää omaa versiotaan. Ajan myötä putket ajautuvat erilleen
— toisessa on `shell: bash`, toisessa ei; toinen käyttää `set -o pipefail`,
toinen kadottaa exit-koodin `tee`:hen.
Tämä kirjasto on se mitä kopioidaan. Se tarjoaa valmiit, testatut, Jenkins on kuitenkin raskas ylläpitää Kubernetesissa, ja organisaatio on siirtymässä Giteaan. Tavoitteena on **sama toiminnallisuus, pienemmällä ylläpitotaakalla**, hyödyntäen Gitea Actionsin natiiveja ominaisuuksia.
dokumentoidut rakennuspalikat joista jokainen tiimi kokoaa oman putkensa.
Palikat ovat Gitea Actionsin `uses:`-direktiivillä kutsuttavia reusable Kirjasto ei ole Jenkins-migraatiotyökalu. Se on Gitea Actions -natiivi
workflow'ta — ei asennusta, ei runtime-riippuvuutta, ei versiopäivityksiä uudelleensuunnittelu, joka säilyttää Jenkins-version todistetut patternit
projekteihin. mutta hylkää ne osat, jotka olivat sidottuja Jenkinsin arkkitehtuuriin.
Gitea Actionsin ja Gitean natiiveja ominaisuuksia hyödynnetään aina kun
mahdollista — uutta kilpailevaa toteutusta ei kirjoiteta, jos toimiva
ratkaisu on jo olemassa.
--- ---
## Suunnitteluperiaatteet ## Suunnitteluperiaatteet
### 1. Palikka-arkkitehtuuri: pieniä, vaihdettavia, yhden vastuun workflow'ta ### 1. Hyödynnä natiivia
Jokainen provider-workflow tekee yhden asian: Gitea Actionsin ja Gitean natiiveja ominaisuuksia käytetään aina kun ne
riittävät. Uutta kilpailevaa toteutusta ei kirjoiteta, jos toimiva ratkaisu
on jo olemassa.
| Workflow | Vastuu | **Miksi:** Oma toteutus on aina ylläpidettävä, testattava ja
|---|---| dokumentoitava. Natiivi ominaisuus tulee ilmaiseksi, kehittyy alustan
| `config-provider.yml` | Lataa ja validoi konfiguraatio | mukana ja on käyttäjälle tuttu.
| `check-version.yml` | Tarkistaa onko commit buildattu, laskee version |
| `docker-build-push.yml` | Buildaa, puskea ja tagittaa kontin |
Mikään workflow ei kutsu toista provider-workflowta. Consumer **Esimerkkejä:**
— siis mikropalvelun oma pipeline-tiedosto — on ainoa paikka joka - Gitea Actions näyttää jobien statuksen automaattisesti — omaa
tietää mitä palikoita tarvitaan ja missä järjestyksessä. commit-status API -kutsua ei tarvita jokaiselle vaiheelle
- Gitea organization secrets/variables korvaa erillisen credential-hallinnan
- Reusable workflow -mekanismi korvaa custom action -runtimen
**Miksi:** Tämä on sama periaate kuin Unix-putkissa tai mikropalveluissa: ### 2. Git-commit on universaali statusnäkymä
pieniä, itsenäisiä komponentteja jotka tekevät yhden asian hyvin.
Consumer voi vaihtaa yhden palikan toiseen — esimerkiksi Docker-buildin
tilalle Maven-paketoinnin — ilman että muut palikat muuttuvat.
Ratkaisu ei ole se että kaikki ajetaan, vaan se että jokainen tiimi
valitsee mitä tarvitsee. Monoliittinen "kaikki yhdessä" -workflow
pakottaisi jokaisen tiimin ajamaan tarpeettomia vaiheita.
### 2. Gitea ensin — hyödynnä alustaa, älä taistele sitä vastaan Buildin jokainen vaihe raportoi tilansa Git-committiin. Kehittäjä näkee yhdellä silmäyksellä, missä vaiheessa build on — ei tarvitse navigoida CI-järjestelmän UI:hun.
Gitea Actions tarjoaa kolme asiaa ilmaiseksi: **Miksi:** Jenkins-versio osoitti, että commit-statusviestit poistavat tarpeen CI-dashboardille. Kehittäjä työskentelee Gitissä, joten status kuuluu Gitiin. Statusviestien `url`-kenttä linkittää suoraan raportteihin — Cucumber-tulokset, SonarQube-tulokset, Docker-rekisteri — ilman että URL tarvitsee etsiä erikseen.
1. **Jobien visuaalinen status** — jokainen jobi näkyy automaattisesti **Mitä tarkoittaa käytännössä:** Gitea Actions näyttää jobien tilan
commit-näkymässä checkmarkilla, spinnerillä tai ristillä. (checkmark/risti/spinner) commit-näkymässä automaattisesti. API:a
2. **Cross-job riippuvuudet**`needs` hoitaa virheiden propagointin: (`/api/v1/repos/{owner}/{repo}/statuses/{sha}`) käytetään vain
jos edeltävä jobi feilaa, riippuvat jobit skipataan. custom-raporttilinkin välittämiseen. ADR 0004.
3. **Reusable workflow -jakelu**`uses: org/repo/.gitea/workflows/file.yml@v1`
on natiivisti versioitu, skopattu ja välimuistitettu.
Kirjasto käyttää näitä kaikkia. Ei omaa tilakonetta, ei custom ### 3. Reusable workflow — ei omaa runtimea
action -runtimea, ei ulkoista orkestraattoria.
**Esimerkki:** Tool-jobit eivät kutsu commit-status API:a lainkaan. Kirjasto jaetaan Gitea Actionsin reusable workflow -mekanismilla. Ei Docker-pohjaisia custom actioneita, ei erillistä ajonaikaista palvelinta.
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 **Miksi:** Reusable workflow on Gitea Actionsin natiivein tapa jakaa CI-logiikkaa. Se on kevein (ei ylimääräistä runtimea), läpinäkyvin (workflow-tiedosto on luettavissa sellaisenaan) ja teknisesti kestävin (Gitea huolehtii versioinnista ja jakelusta). Custom actionit otetaan käyttöön vain jos reusable workflow'n rajat tulevat vastaan.
Kehitjä työskentelee Gitissä. `git log`, `git blame`, PR-näkymä — **Mitä mä ei ole:** Tämä ei ole monorepo-työkalu, joka asennetaan projekteihin. Tämä on joukko `.gitea/workflows/`-tiedostoja, joihin mikropalvelut viittaavat `uses:`-direktiivillä.
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: ### 4. Konfiguraatio kuuluu repoon
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 Projektikohtainen konfiguraatio (`ci-flow-values.yaml`-tyyppinen tiedosto) asuu mikropalvelun omassa repossa. Reusable workflow lukee sen, ei toisinpäin.
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 **Miksi:** Mikropalvelun kehittäjä omistaa buildinsa. Hän tietää mitä Dockefileä käytetään, mitä SonarQube-projektia, mitä testi-steppejä tarvitaan. Jos konfiguraatio hajautetaan useaan repoon, muutokset vaativat koordinaatiota, ja yhden totuuden lähteen periaate rikkoutuu.
CI-putken jokaisen `run`-stepin onnistuminen määräytyy **vain ja **Poikkeus:** Infra-tason asetukset (git-pages host, Gitea-instanssin URL)
ainoastaan** exit-koodin perusteella. Ei tiedoston olemassaolon, ei ovat organisaatiotasolla Gitean organization secrets/variables
stdout-tulosteen, ei arvauksen. `0` = ok, kaikki muu = ei ok. -mekanismissa. Ne eivät ole repokohtaisia.
Tämä kuulostaa itsestään selvältä, mutta YAML-pipelineissa se rikkoutuu ### 5. Deterministinen testigraafi, vaiheittainen suoritus
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 Test flow on tunnettu ennen buildin alkua, ja testit ajetaan yksi kerrallaan. Jos steppi epäonnistuu, koko flow pysähtyy.
`$?`-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 **Miksi:** Rinnakkainen suoritus aiheuttaa resurssikilpailua (erityisesti suorituskykytestit) ja piilottaa virheitä. Kun integraatiotesti epäonnistuu, e2e-testien ajaminen on turhaa — konttia ei viedä tuotantoon, eikä kukaan lue niitä tuloksia. Vaiheittainen suoritus on deterministinen, debuggattava ja säästää CI-minuutteja.
Jokainen ylimääräinen riippuvuus on ylimääräinen vikaantumispiste. **Miten:** Orkestroiva workflow käyttää Gitea REST API:a workflow-dispatchiin ja pollaa ajettavan workflow'n tilaa synkronisesti (`GET /api/v1/repos/{owner}/{repo}/actions/runs/{id}`). Tämä vastaa Jenkinsin `buildJob()`-kutsun semantiikkaa, mutta toteutetaan curl + pollaus -silmukalla.
Kirjaston ainoat riippuvuudet:
- Gitea Actions (alusta) ### 6. Raporttien hallinta erillisellä palvelulla
- `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). Raporttien selailtavuudesta ja elinkaaresta vastaa erillinen palvelu, joka
Ei tietokantaa. Ei ulkoista tilanhallintaa. Kirjasto on joukko asennetaan git-pages Helm-chartilla. Raportit ovat julkisia URL:lla
YAML-tiedostoja ja shell-skriptejä — samat työkalut jotka jokainen (osoite tunnettava). URL linkitetään Git-committiin.
devops-ihminen jo osaa.
### 6. Konfiguraatio repoon, salaisuudet Giteaan **Miksi:** Jenkins-versiossa linkki Cucumber-raporttiin oli kriittinen
feature. Gitea Actionsin artifact-järjestelmä ei tue HTML-selailtavuutta
(vain ZIP-lataus). Erillinen palvelu mahdollistaa hallitun retention ja
pääsyn ilman CI-alustan rajoitteita.
Projektikohtainen konfiguraatio (`.gitea/workflows/gitea-env.conf`) ### 7. Yksi CI-alusta, yksi integraatiopiste
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, Kirjasto tukee vain Giteaa. Ei GitLab-, BitBucket- tai GitHub-abstraktioita.
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`) **Miksi:** Jenkins-versio tuki neljää Git-alustaa, koska Jenkins itsessään ei tarjonnut commit-statusraportointia. Gitea Actionsissa tilanne on päinvastainen — Gitea on sekä CI-että Git-alusta. Multi-platform-tuesta tulisi pelkkää ylimääräistä abstraktiota ilman konkreettista tarvetta.
ovat Gitean organization secrets/variables -mekanismissa. Ne eivät
ole repokohtaisia.
### 7. Consumer omistaa orkestroinnin, provider tarjoaa palikat **Mitä tarkoittaa tulevaisuudessa:** Jos toinen alusta tulee ajankohtaiseksi, Gitea-versiota käytetään joko pohjana redesignille tai mallina erilliselle toteutukselle. Rajapintoja ei suunnitella etukäteen alustariippumattomiksi — se on ennenaikaista optimointia.
Tämä on kirjaston tärkein arkkitehtuurinen päätös (ADR 0005). ### 8. Cross-repo commit traceability
Provider (`gitea-ci-library`) ei tiedä mitä testejä ajetaan, missä Kun build-ketju ylittää reporajat (mikropalvelu → deployment → integraatiotestit → e2e-testit), jokainen vaihe raportoi kahteen suuntaan: omaan committiinsa ja takaisin root-committiin, josta ketju käynnistyi.
järjestyksessä, tai millä branchilla. Se tarjoaa kolme reusable
workflow'ta ja joukon skriptejä.
Consumer (mikropalvelun `example-feature.yml` / `example-main.yml`) **Miksi:** Kehittäjän ei pidä arvailla mikä versio on missäkin ympäristössä. Kun mikropalvelun commitista näkee koko ketjun — buildattu, deployattu stagingiin, integraatiotestit ajettu, e2e hyväksytty — virheenjäljitys on suora polku commitista ympäristöön. Vastaavasti Helm-repon commit kertoo mikä konttiversio sinne deployattiin ja kenen mikropalvelu-commitista se tuli. Tämä on Jenkins-version **eniten arvoa tuottanut ominaisuus**.
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 **Mekanismi:**
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 ```mermaid
%%{init: {'theme': 'base'}}%%
sequenceDiagram
participant MR as Mikropalvelu-repo
participant HR as Helm-repo
participant TR as Testi-repo
participant GA as Gitea API
Eri brancheilla on eri tavoite: Note over MR: commit abc123
Note over MR: build kontti v1.2.3
- **Feature-haara:** Onko koodi laadukasta? → testit, validointi MR->>GA: POST dispatch deploy-workflow
- **Main-haara:** Onko tästä versiosta jo artifakti? Jos ei → GA->>HR: workflow käyntiin
testit + build + push + tag. Jos on → ei tehdä mitään (tai Note over HR: commit def456
jatketaan klusteritesteihin). Note over HR: container.version = 1.2.3
Tämä logiikka elää consumerin pipeline-tiedostossa, ei providerissa. HR->>GA: POST status "deployed by abc123"
Se on puhdasta `if`-ehtoa ja `needs`-ketjutusta — ei skriptausta, GA->>MR: Status: deployed to staging
ei monimutkaisia ehtoja providerin sisällä. Note right of MR: URL → def456
### 9. Raportit erillisellä palvelulla, linkit commitissa HR->>GA: POST status "from abc123"
GA->>HR: Status: from abc123
Note right of HR: URL → abc123
Gitea Actionsin artifact-järjestelmä on binääriarkisto — ZIP-lataus, MR->>GA: POST dispatch integraatiotestit
ei HTML-selailtavuutta. Testiraportit (Cucumber HTML, Bats-coverage) GA->>TR: workflow käyntiin
on voitava avata selaimessa yhdellä klikkauksella. Note over TR: commit ghi789
Ratkaisu: git-pages Helm-chartti, joka tarjoaa staattista TR->>GA: POST status "integration OK"
tiedostohostingia HTTP:llä. `publish-git-pages.sh` vie raportit GA->>MR: Status: integration OK
sinne; `report-status.sh` linkittää commit-statuksen suoraan Note right of MR: URL → ghi789
raporttiin. Retention hoitaa git-pagesin sidecar automaattisesti.
Tulevaisuudessa `GITHUB_STEP_SUMMARY` (Gitea 1.27+) tarjoaa TR->>GA: POST status "tested v1.2.3"
vaihtoehtoisen kanavan: jobin Summary-välilehdelle renderöityvä GA->>HR: Status: tested v1.2.3
Markdown-taulukko kaikista raporttilinkeistä. Note right of HR: URL → def456
### 10. Vain Gitea — ei monialustatukea ilman tarvetta TR->>GA: POST status "tested abc123"
GA->>MR: Status: tested abc123 (root)
Note right of MR: URL → abc123
```
Yhden alustan tukeminen kunnolla on vaikeampaa kuin kolmen tukeminen **Mitä tarkoittaa käytännössä:** Jokaisella workflow'lla on kaksi build-referenssiä: `current` (oma commit) ja `root` (mikropalvelun commit, josta ketju alkoi). Molempiin POSTataan statusviestit. Root-build kulkee workflow-dispatchin `inputs`-parametrina koko ketjun läpi. Deployment-job raportoi sekä Helm-repon committiin ("from abc123") että mikropalvelun committiin ("deployed to staging → def456"). Testi-job raportoi omaan committiinsa, mikropalvelun committiin ja Helm-repon committiin.
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.
--- ---
@@ -197,31 +152,23 @@ on devopsissa yhtä haitallista kuin ohjelmistosuunnittelussa.
### Mitä kirjasto EI tee ### Mitä kirjasto EI tee
- **Ei ulkoista orkestraattoria.** Pipeline-ohjaus on Gitea Actionsin - **Ei ulkoista orkestraattoria.** Test flow -ketjutus perustuu Gitean REST APIin ja workflowhin itseensä. Ei erillistä palvelinta, joka hallinnoi tilaa.
`needs`-ketjuissa ja consumerin `if`-ehdoissa. - **Ei Jenkins-migraatiota.** Vanhaa Jenkinsfileä ei voi ajaa Gitea Actionsissa. Tämä on uusi kirjasto uudella konfiguraatioformaatilla.
- **Ei custom actioneita.** Reusable workflow on kevyempi, versioitu - **Ei reaaliaikaista build-seurantaa.** Commit-statusviestit ovat pollattavia, eivät push-pohjaisia. Gitean UI hoitaa reaaliaikaisuuden.
ja jaeltu Gitean oman mekanismin kautta. - **Ei multi-repo-monorepo-konfiguraatiota.** Jokainen mikropalvelu omistaa oman `ci-flow-values.yaml`:nsa. Jaettua konfiguraatiota ei ole projektitasolla.
- **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 ## Mitä tietoisesti hylättiin
## Mitä tietoisesti hylättiin
| Hylätty | Syy | | Hylätty | Syy |
|---|---| |---------|-----|
| Monoliittinen "kaikki yhdessä" -workflow | Pakottaa kaikille samat vaiheet. Palikka-arkkitehtuuri antaa jokaiselle tiimille vain mitä se tarvitsee | | Multi-Git-platform-tuki (GitLab, BitBucket) | Vain Gitea on relevantti. Abstraktointi ilman tarvetta on turhaa kompleksisuutta |
| Oma orkestraattoripalvelin | Ylimääräinen ylläpidettävä. Gitean `needs` ja `if` riittävät | | Gitea Packages raporttien hostingiin | Ei tue HTML-selailtavuutta — vain binääriartefaktien lataus |
| Docker-pohjaiset custom actionit | Tuovat riippuvuuden Docker-rekisteriin. Reusable workflow on natiivimpi | | Gitea Releases raporttien hostingiin | Saastuttaa release-historian. Satoja CI-raportteja oikeiden julkaisujen seassa |
| Commit-status API jokaiselle vaiheelle | Duplikointia — Gitea näyttää job-statuksen automaattisesti. API vain custom-linkeille | | Gitea Pages + reports-branch | Race condition rinnakkaisten buildien pushissa samaan branchiin |
| `tee`-putki debug-näkyvyyteen | Syö exit-koodin. stdout ohjataan tiedostoon `>` ilman pipeä | | Ulkoinen orkestraattoripalvelin | Ylimääräinen ylläpidettävä. Gitean oma API riittää |
| Multi-Git-platform-tuki | Ennenaikaista optimointia ilman tarvetta | | Docker-pohjaiset custom actionit | Tuovat riippuvuuden Docker-rekisteriin ja monimutkaistavat jakelua. Otetaan käyttöön vain pakon edessä |
| Gitea Packages raporttien hostingiin | Ei HTML-selailtavuutta — vain binäärilataus | | `repository_dispatch` (webhook) test flow -ketjutukseen | Lisää konfiguraatiota vastaanottaviin repoihin. Suora REST API -kutsu on eksplisiittisempi ja debuggattavampi |
| Gitea Pages + reports-branch | Race condition rinnakkaisten pushien kanssa |
| `repository_dispatch` ketjutukseen | Lisää konfiguraatiota vastaanottaviin repoihin. Suora API-kutsu eksplisiittisempi |
+100 -57
View File
@@ -1,6 +1,8 @@
# Vaatimukset — Gitea Actions CI -kirjasto # Vaatimukset — Gitea Actions CI -kirjasto
> Funktionaaliset vaatimukset käyttäjän näkökulmasta. Muoto: käyttötapaukset (use cases). > Funktionaaliset vaatimukset käyttäjän näkökulmasta. Muoto: käyttötapaukset (use cases).
>
> Linkittyy: [design-rationale.md](design-rationale.md), [architecture.md](architecture.md), [report-hosting.md](report-hosting.md)
--- ---
@@ -11,89 +13,130 @@
**Main success:** **Main success:**
- Kehittäjä avaa commitin Giteassa - Kehittäjä avaa commitin Giteassa
- Näkee job-statukset automaattisesti: spinner (käynnissä), checkmark (ok), risti (feilasi) - Näkee statusviestit: "Building...", "Unit tests OK", "Docker build OK", "Docker pushed"
- Testijobit näyttävät statuksen linkillä: "unit-tests Link to Bats reports", "acc-tests Link to Cucumber reports" - Jokainen statusviesti on klikattavissa → vie buildin sivuun tai raporttiin
- Klikkaamalla testistatusta kehittäjä pääsee suoraan HTML-raporttiin git-pagesissa - Epäonnistunut steppi näkyy punaisella — kehittäjä klikkaa ja näkee mikä meni vikaan
- Docker-build näyttää linkin konttirekisteriin
**Poikkeukset:** **Poikkeukset:**
- Statusviesti puuttuu (workflow kaatui ennen raportointia) → commitissa näkyy timeout/error - Statusviesti puuttuu (workflow kaatui ennen raportointia) → commitissa näkyy timeout/error
- Useampi workflow samalle commitille → statukset erottuvat `context`-avaimella - Useampi workflow samalle commitille → statukset erottuvat `key`-arvolla
--- ---
## UC2: Kehittäjä lukee testiraportteja selaimessa ## UC2: Kehittäjä lukee testiraportteja selaimessa
**Actor:** Kehittäjä **Actor:** Kehittäjä
**Precondition:** Build on valmistunut, raportit julkaistu git-pagesiin **Precondition:** Build on valmistunut, raportit pushattu Minioon
**Main success:** **Main success:**
- Kehittäjä klikkaa commitin status-kuvaketta (esim. "unit-tests") - Kehittäjä klikkaa commitin statusviestin URL:ää ("Unit tests OK" → URL)
- Selain avautuu suoraan HTML-raporttiin git-pagesissa - Selain avautuu, OIDC-kirjautuminen (Gitea-tunnuksilla)
- Bats-raportissa näkyy: testitulokset, code coverage - Cucumber-raportti renderöityy HTML:nä selaimessa
- Cucumber-raportissa näkyy: mitkä testit menivät läpi, mitkä epäonnistuivat, stack tracet - Raportissa näkyy: mitkä testit menivät läpi, mitkä epäonnistuivat, stack tracet
- Yläreunassa linkki "← Back to build" → palaa buildin raportti-indeksiin
**Poikkeukset:** **Poikkeukset:**
- Raporttia ei ole (testit skipattiin, workflow kaatui ennen julkaisua) → 404 - Raporttia ei ole (testit skipattiin, workflow kaatui ennen pushausta) → 404
- OIDC-sessio vanhentunut → uudelleenohjaus kirjautumiseen
--- ---
## UC3: Kehittäjä vertailee kahden buildin raportteja ## UC3: Kehittäjä selaa projektin build-historiaa
**Actor:** Kehittäjä
**Precondition:** Projektilla on vähintään yksi build
**Main success:**
- Kehittäjä menee `{MINIO_BASE}/{repo_slug}/index.html`
- Näkee listan kaikista buildeista aikajärjestyksessä (uusin ensin)
- Jokaisella buildilla: commitin 8-merkkinen hash, päivämäärä, branch, status (✅/❌)
- Klikkaa buildia → siirtyy `{commit_short}/index.html` — buildin raporttilistaukseen
- Buildin sivulla: lista kaikista raporteista (Cucumber, JaCoCo, Maven Site) linkkeinä
- "← Back to builds" → palaa projektin build-indeksiin
**Poikkeukset:**
- Projekti poistettu / siivottu retention policyn mukaan → 404
- Indeksitiedosto puuttuu (ensimmäinen build kesken) → 404, generoituu seuraavalla pushauksella
---
## UC4: Kehittäjä jäljittää kontin koko ketjun commitista
**Actor:** Kehittäjä
**Precondition:** Mikropalvelun commitista on ajettu vähintään deployment
**Main success:**
- Kehittäjä avaa mikropalvelun commitin abc123
- Näkee statusviestit: "Build OK", "Deployed to staging → def456", "Integration tests OK → ghi789"
- Klikkaa "Deployed to staging → def456" → siirtyy Helm-repon committiin def456
- Helm-repon commitissa näkyy: "from abc123", "tested v1.2.3", "tested abc123"
- Klikkaa "tested abc123" → palaa mikropalvelun committiin
- Koko ketju on navigoitavissa edestakaisin commit-statuslinkkien kautta
**Poikkeukset:**
- Välivaiheen commit siivottu → statusviesti jää, mutta linkki vie 404:ään
- Deploytty versio ei vastaa odotettua → statusviestissä näkyy ristiriita
---
## UC5: Kehittäjä näkee deployatun version ympäristössä
**Actor:** Kehittäjä
**Precondition:** Deployment on suoritettu, Helm-repon commit tehty
**Main success:**
- Kehittäjä avaa Helm-repon commitin def456
- Näkee: "container.version = 1.2.3", "Deployed by abc123"
- Tietää heti mikä konttiversio on missäkin ympäristössä
- Voi verrata mikropalvelun uusimpaan commitin — onko ympäristö ajan tasalla?
**Poikkeukset:**
- `doNotDowngrade` esti deploymentin → statusviesti "Skipped: newer version already deployed"
---
## UC6: Testi-insinööri näkee mitä konttia testattiin
**Actor:** Testi-insinööri
**Precondition:** Integraatio- tai e2e-testit on ajettu
**Main success:**
- Avaa testi-repon commitin ghi789
- Näkee: "Tested v1.2.3" (mikä kontti), "Tested abc123" (mikä mikropalvelun commit)
- Klikkaa testiraporttiin → näkee tulokset
- Näkee myös mitkä tagit olivat käytössä (`@smoke and not @slow`)
- Voi todentaa että testattiin oikeaa versiota
**Poikkeukset:**
- Version check epäonnistui (haluttu versio ei ollut ympäristössä) → status: "Version mismatch"
- Testit keskeytyivät timeoutiin → status: timeout, osittaiset tulokset raportissa
---
## UC7: Kehittäjä vertailee kahden buildin raportteja
**Actor:** Kehittäjä **Actor:** Kehittäjä
**Precondition:** Projektilla on vähintään kaksi buildia **Precondition:** Projektilla on vähintään kaksi buildia
**Main success:** **Main success:**
- Kehittäjä avaa kaksi buildia Gitean Actions-näkymässä eri välilehtiin - Kehittäjä avaa projektin build-indeksin `{MINIO_BASE}/{repo}/index.html`
- Näkee viimeisimmät buildit vierekkäin
- Avaa kaksi buildia eri välilehtiin
- Voi verrata Cucumber-tuloksia: "build #42 vs #41 — mikä testi meni rikki?" - Voi verrata Cucumber-tuloksia: "build #42 vs #41 — mikä testi meni rikki?"
--- **Poikkeukset:**
- Vanha build siivottu → ei näy indeksissä
## UC4: Kehittäjä jäljittää kontin koko ketjun commitista (tuleva)
**Actor:** Kehittäjä
**Precondition:** Mikropalvelun commitista on ajettu deployment ja klusteritestit
**Main success:**
- Kehittäjä avaa mikropalvelun commitin
- Näkee statusviestit: "Build OK", "Deployed to staging", "Integration tests OK"
- Klikkaamalla statusta siirtyy toisen repon committiin
- Koko ketju on navigoitavissa edestakaisin commit-statuslinkkien kautta
---
## UC5: Kehittäjä näkee deployatun version ympäristössä (tuleva)
**Actor:** Kehittäjä
**Precondition:** Deployment on suoritettu
**Main success:**
- Avaa Helm-repon commitin
- Näkee suoraan mikä konttiversio on deployattu
- Voi verrata mikropalvelun uusimpaan commitiin — onko ympäristö ajan tasalla?
---
## UC6: Kehittäjä saa Gitea 1.27+ Summary-näkymän raporttilinkeistä (tuleva)
**Actor:** Kehittäjä
**Precondition:** Gitea 1.27+ ja päivitetty runner
**Main success:**
- Avaa workflow'n Gitea Actionsissa
- "Report Summary" -jobin Summary-välilehdellä näkyy taulukko linkeillä kaikkiin raportteihin
- Yhdellä silmäyksellä näkee mitä testejä ajettiin ja pääsee klikkaamalla raportteihin
--- ---
## Ei-toiminnalliset vaatimukset ## Ei-toiminnalliset vaatimukset
| Vaatimus | Toteutus | | Vaatimus | Toteutus |
|---|---| |----------|----------|
| Raportit selailtavissa HTML:nä | git-pages static hosting | | Raportit selailtavissa HTML:nä | MinIO static web hosting |
| Linkki commitista suoraan raporttiin | Commit-status API:n `target_url` | | Linkki commitista suoraan raporttiin | Statusviestin `url`-kenttä |
| Raporttien pysyvyys | git-pages retention sidecar | | Build-indeksi per projekti | Generoitu `index.html` Minioon |
| Virheiden propagointi | Gitea Actions `needs`-ketju | | Navigaatio raporttien välillä | "Back to build" / "Back to builds" — linkit indeksisivuilla |
| Pipeline-pysäytys virhetilanteessa | `needs` automaattinen skip | | Cross-repo-navigaatio | Statusviestit linkittävät repoja ristiin |
| Exit-koodi ainoa totuus | ADR 0008 | | Raporttien pysyvyys | ConfigMap-pohjainen retention policy |
| Statusraportointi vain raporttilinkeille | ADR 0007 | | Autentikointi | OIDC (Traefik middleware, Gitea OAuth2) |
+11 -1
View File
@@ -18,7 +18,7 @@ Runnerilla on yksi vastuu: **suorittaa workflow-steppejä**. Kaikki runtime-ymp
## Kontit ja palvelut ## Kontit ja palvelut
Jokainen job voi määritellä käyttämänsä kontit. Eri jobeilla voi olla eri kontti: Jokainen job voi määritellä käyttämänsä kontit. Tämä vastaa Jenkinsin pod template -konseptia, mutta on yksinkertaisempi:
### `container:` — ajonaikainen ympäristö ### `container:` — ajonaikainen ympäristö
@@ -99,3 +99,13 @@ Eri labelit mahdollistavat erikoistuneet runnerit (ARM, GPU, Windows), mutta MVP
|------|-------|-------|--------------| |------|-------|-------|--------------|
| **Global** | Kaikki organisaatiot ja repot | Token-vuoto → hyökkääjä voi ajaa koodia missä tahansa | Jaettu infra, keskitetty hallinta | | **Global** | Kaikki organisaatiot ja repot | Token-vuoto → hyökkääjä voi ajaa koodia missä tahansa | Jaettu infra, keskitetty hallinta |
| **Organization** | Yhden organisaation repot | Rajoittuu yhteen orgiin | Per organisaatio, eristetty — **suositeltu** | | **Organization** | Yhden organisaation repot | Rajoittuu yhteen orgiin | Per organisaatio, eristetty — **suositeltu** |
## Jenkins-vertailu
| Jenkins | Gitea Actions |
|---------|--------------|
| Pod template (YAML) määrittelee kontit | `container:` + `services:` per job |
| Jokaiselle jobille oma pod | Jokaiselle jobille omat konttimääritykset |
| DinD sidecar-podissa | `services: docker:dind` samassa jobissa |
| Agentti = erillinen JVM-prosessi | Runner = kevyt Go-binääri tai K8s-pod |
| Labelit Jenkins-nodessa | Labelit runner-rekisteröinnissä |
+140 -105
View File
@@ -1,125 +1,68 @@
# Jaetut skriptit # Jaetut skriptit
> Provider-skriptit asuvat `scripts/`-hakemistossa. Consumer-skriptit > ⚠️ **POC-vaihe.** Osa kuvatuista skripteistä (push-reports.sh, tag-commit.sh)
> asuvat `.gitea/scripts/`-hakemistossa. ADR 0006. > on suunniteltu mutta ei toteutettu. Toteutetut: `publish-git-pages.sh`,
> `report-status.sh`, `dispatch-workflow.sh` (POC-taso).
>
> Uudelleenkirjoitus odottaa: skriptien määrä ja rajapinnat voivat muuttua.
Skriptit asuvat `gitea-ci-library/scripts/`-hakemistossa.
--- ---
## `report-status.sh` ## `report-status.sh`
POSTaa commit-statuksen Gitea REST APIin. Käytetään **vain** kun tarvitaan POSTaa build-statuksen Gitea-commitin REST APIin.
custom-linkki (testiraportti, Docker registry). Tool-jobit luottavat
Gitean natiiviin job-statukseen. ADR 0007.
### Rajapinta ### Rajapinta
```bash ```bash
report-status.sh <state> <description> <context> [suite] [custom_url] report-status.sh <state> <description> <url> [key] [root_commit] [root_repo]
``` ```
| Parametri | Pakollinen | Kuvaus | | Parametri | Pakollinen | Kuvaus |
|---|---|---| |-----------|------------|--------|
| `state` | Kyllä | `pending`, `success`, `failure` | | `state` | Kyllä | `pending`, `success`, `failure`, `error` |
| `description` | Kyllä | Ihmisluettava kuvaus | | `description` | Kyllä | Ihmisluettava kuvaus (esim. "Unit tests passed") |
| `context` | Kyllä | Uniikki avain (`unit-tests`, `acc-tests`, `ci-docker-build-push`) | | `url` | Kyllä | Linkki buildiin tai raporttiin |
| `suite` | Ei | Julkaistun raportin suite-nimi → linkki git-pagesiin | | `key` | Ei | Uniikki avain. Oletus: `commit-{sha_short}` |
| `custom_url` | Ei | Oma URL (ohittaa oletus-URL:n generoinnin) | | `root_commit` | Ei | Root-buildin commit-hash (cross-repo-raportointia varten) |
| `root_repo` | Ei | Root-buildin repo (cross-repo-raportointia varten) |
### Kutsuesimerkkejä ### Kutsuesimerkkejä
```bash ```bash
# Testijobi, linkki git-pages-raporttiin # Buildin aloitus
report-status.sh success "Link to Bats reports" unit-tests bats report-status.sh pending "Building..." "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
# Docker build, custom URL registryyn # Testivaihe valmis, linkki raporttiin
report-status.sh success "Docker build & push 1.2.0 OK" ci-docker-build-push "" \ report-status.sh success "Unit tests OK" "$MINIO_BASE_URL/$GITHUB_REPOSITORY/${GITHUB_SHA::8}/cucumber/overview-features.html" "unit-test"
"https://gitea.example.com/org/-/packages/container/app/1.2.0"
# Deployment valmis, cross-repo: raportoi takaisin mikropalvelun committiin
report-status.sh success "Deployed to staging" "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA" "deploy-staging" "$ROOT_COMMIT" "$ROOT_REPO"
``` ```
### URL-generointi
- Jos `suite` annettu → URL: `${GIT_PAGES_URL}/${repo}/reports/${sha8}/${suite}/`
- Jos `custom_url` annettu → käytetään sellaisenaan
- Muuten → URL: `${GITEA_API_URL}/${repo}/actions/runs/${run_id}` (Gitea Actions -loki)
### Gitea API -kutsu ### Gitea API -kutsu
```bash ```bash
curl -X POST "$GITEA_API_URL/api/v1/repos/$REPO/statuses/$COMMIT" \ curl -X POST "$GITEA_API_URL/api/v1/repos/$REPO/statuses/$COMMIT" \
-H "Authorization: token $GITEA_TOKEN" \ -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"state\":\"$STATE\",\"target_url\":\"$URL\",\"description\":\"$DESCRIPTION\",\"context\":\"$CONTEXT\"}" -d "{
\"state\": \"$STATE\",
\"target_url\": \"$URL\",
\"description\": \"$DESCRIPTION\",
\"context\": \"$KEY\"
}"
``` ```
--- Status-arvot mapataan Gitean skeemaan: `pending` (INPROGRESS), `success` (SUCCESS), `failure` (FAILURE), `error` (STOPPED).
## `publish-git-pages.sh`
Julkaisee raporttihakemiston git-pages-palveluun PATCH-tar:na.
### Rajapinta
```bash
publish-git-pages.sh <suite>
```
| Parametri | Pakollinen | Kuvaus |
|---|---|---|
| `suite` | Kyllä | Raporttihakemiston nimi (`bats`, `cucumber`, `junit`, ...) |
### Toiminta
1. Lukee raportit hakemistosta `reports/${SHA8}/${suite}/`
2. Pakkaa tar:ksi ja PATCHaa git-pagesiin BasicAuthilla
3. Tulostaa raportin base-URL:n stdoutiin
### Vaaditut env-muuttujat
| Muuttuja | Lähde |
|---|---|
| `GITEA_API_URL` | `env_json` → workflow `env:` |
| `GIT_PAGES_URL` | `env_json` → workflow `env:` |
| `GIT_PAGES_PUBLISH_TOKEN` | Gitea secret → `env:` |
| `GITHUB_REPOSITORY` | Automaattinen |
| `GITHUB_SHA` | Automaattinen |
---
## `ci-validate.sh`
Validoi `.conf`-tiedoston ja tarkistaa että pakolliset secretit on asetettu.
Kutsutaan `config-provider.yml`:stä osana konfiguraation latausta.
### Rajapinta
```bash
ci-validate.sh
```
Lukee tiedoston polun `CI_CONF_FILE`-env-muuttujasta (oletus: `.gitea/workflows/gitea-env.conf`).
### Validointisäännöt
- `.conf`-tiedosto on olemassa
- 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
--- ---
## `dispatch-workflow.sh` ## `dispatch-workflow.sh`
Dispatchaa workflow'n toisessa repossa ja pollaa sen valmistumista synkronisesti. Dispatchaa workflow'n toisessa repossa ja pollaa sen valmistumista synkronisesti.
Käytetään GitOps-deploymentissa ja klusteritestien ketjutuksessa (tuleva).
Generoi automaattisesti `dispatch_id`-tunnisteen, lisää sen dispatch-
inputteihin ja tunnistaa workflow-runin kohdereposta `display_title`-
kentän perusteella. Toimii luotettavasti vaikka samassa repossa olisi
useita samanaikaisia ajoja.
**Kohde-workflow'ssa on oltava `dispatch_id`-input ja `run-name`-kenttä
`display_title`-matchausta varten.** Katso `skills/gitops-update/SKILL.md`.
### Rajapinta ### Rajapinta
@@ -130,33 +73,125 @@ dispatch-workflow.sh <target_repo> <workflow_file> <ref> <inputs_json> <gitea_ap
| Parametri | Pakollinen | Kuvaus | | Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------| |-----------|------------|--------|
| `target_repo` | Kyllä | `owner/repo` | | `target_repo` | Kyllä | `owner/repo` |
| `workflow_file` | Kyllä | Workflow-tiedosto (esim. `ci-main.yml`) | | `workflow_file` | Kyllä | Workflow-tiedoston nimi (esim. `test.yml`) |
| `ref` | Kyllä | Branch | | `ref` | Kyllä | Branch |
| `inputs_json` | Kyllä | JSON-objekti dispatch-inputteina | | `inputs_json` | Kyllä | JSON-objekti input-parametreina |
| `gitea_api_url` | Kyllä | Gitean API-URL | | `gitea_api_url` | Kyllä | Gitean API-URL (esim. `https://gitea.example.com`) |
| `gitea_token` | Kyllä | Gitea API -token (write kohderepoon) | | `gitea_token` | Kyllä | Gitea API -token |
| `timeout_minutes` | Ei | Aikakatkaisu (oletus 360) | | `timeout_minutes` | Ei | Oletus: 360 (6 tuntia) |
### Toiminta ### Toiminta
1. **Generoi `dispatch_id`** — 8-hex uniikki tunniste 1. **Dispatch:** `POST /api/v1/repos/{target_repo}/actions/workflows/{workflow_file}/dispatches`
2. **Injektoi** `dispatch_id` inputteihin 2. **Etsi run:** `GET /api/v1/repos/{target_repo}/actions/runs?status=running` → etsi uusin (aikaleimasta)
3. **Dispatch:** `POST /api/v1/repos/{target_repo}/actions/workflows/{workflow_file}/dispatches` 3. **Poll:** `GET /api/v1/repos/{target_repo}/actions/runs/{run_id}` 10s välein
4. **Etsi run:** pollaa rinnakkaisia `workflow_dispatch`-runeja, matchaa `display_title` sisältää `dispatch_id`:n 4. **Lopeta:** Kun `status == "completed"` → palauta `conclusion` (`success`/`failure`/`cancelled`)
5. **Poll:** `GET /api/v1/repos/{target_repo}/actions/runs/{run_id}` — odota valmistumista 5. **Timeout:** Jos kestää yli `timeout_minutes` → palauta `timeout`
6. **Palauta:** exit 0 (success), exit 1 (failure), exit 124 (timeout)
### Kutsuesimerkki
```bash
dispatch-workflow.sh "tests/integration" "test.yml" "main" \
'{"version":"1.2.3","tags":"@smoke","root_commit":"abc123","root_repo":"services/temperature-store"}' \
"https://gitea.example.com" "gtp_abc123"
```
---
## `push-reports.sh` (vanhentunut — korvattu `publish-git-pages.sh`:lla)
Puskaa raporttihakemiston git-pagesiin.
### Rajapinta
```bash
push-reports.sh <report_type> <source_dir> [index_title]
```
| Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------|
| `report_type` | Kyllä | Raportin tyyppi (`cucumber`, `jacoco`, `junit`, `site`) |
| `source_dir` | Kyllä | Paikallinen hakemisto, jossa raporttitiedostot |
| `index_title` | Ei | Näkyvä nimi indeksisivulla (esim. "Cucumber Reports") |
### Toiminta
1. Kopioi raportit: `mc cp --recursive {source_dir} minio/reports/{repo}/{commit_short}/{report_type}/`
2. Päivitä `/reports/{repo}/{commit_short}/index.html` — lisää linkki tähän raporttiin
3. Päivitä `/reports/{repo}/index.html` — varmista että tämä build on listalla
4. Palauta URL: `{MINIO_BASE_URL}/{repo}/{commit_short}/{report_type}/index.html`
### Indeksisivut
**Projektin build-indeksi** (`/reports/{repo}/index.html`):
- Lista buildeista aikajärjestyksessä (uusin ensin)
- Jokainen rivi: commit hash (linkki), päivämäärä, status (✅/❌), branch
**Buildin raportti-indeksi** (`/reports/{repo}/{commit_short}/index.html`):
- Lista raporteista linkkeinä
- Linkki "← Back to builds" → projektin build-indeksiin
Molemmat generoidaan uudestaan jokaisen pushauksen yhteydessä. Staattinen HTML, ei vaadi palvelinpuolen logiikkaa.
### Kutsuesimerkki
```bash
push-reports.sh cucumber target/cucumber-report "Cucumber Reports"
# → https://reports.example.com/temperature-store/abc12345/cucumber/overview-features.html
push-reports.sh jacoco target/jacoco-report "JaCoCo Coverage"
# → https://reports.example.com/temperature-store/abc12345/jacoco/index.html
```
---
## `tag-commit.sh`
Tagittaa commitin versiolla Gitea REST API:n kautta.
### Rajapinta
```bash
tag-commit.sh <version>
```
### Toiminta
```bash
curl -X POST "$GITEA_API_URL/api/v1/repos/$GITHUB_REPOSITORY/tags" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"tag_name\": \"$VERSION\",
\"message\": \"Build #$GITHUB_RUN_NUMBER\",
\"target\": \"$GITHUB_SHA\"
}"
```
### Kutsu
```bash
tag-commit.sh "1.2.3.$GITHUB_RUN_NUMBER"
```
Tagataan vain onnistuneen buildin ja pushin jälkeen. Tämän jälkeen `isContainerBuilt()` palauttaa `true` samalle commitille.
--- ---
## Muuttujat, joita skriptit olettavat ## Muuttujat, joita skriptit olettavat
Skriptit lukevat nämä Gitea Actionsin ympäristömuuttujat:
| Muuttuja | Lähde | Käyttäjä | | Muuttuja | Lähde | Käyttäjä |
|---|---|---| |----------|-------|----------|
| `GITEA_API_URL` | `env_json` | `report-status.sh`, `ci-validate.sh` | | `GITEA_API_URL` | Org variable | `report-status.sh` |
| `GIT_PAGES_URL` | `env_json` | `publish-git-pages.sh`, `report-status.sh` | | `GITEA_TOKEN` | Org secret | `report-status.sh`, `tag-commit.sh` |
| `GITEA_TOKEN` | Gitea secret | `report-status.sh`, `check-version.yml`, `docker-build-push.yml` | | `MINIO_BASE_URL` | Org variable | `push-reports.sh` |
| `GIT_PAGES_PUBLISH_TOKEN` | Gitea secret | `publish-git-pages.sh` | | `MINIO_ACCESS_KEY` | Org secret | `push-reports.sh` |
| `MINIO_SECRET_KEY` | Org secret | `push-reports.sh` |
| `GITHUB_REPOSITORY` | Automaattinen | Kaikki skriptit | | `GITHUB_REPOSITORY` | Automaattinen | Kaikki skriptit |
| `GITHUB_SHA` | Automaattinen | Kaikki skriptit | | `GITHUB_SHA` | Automaattinen | Kaikki skriptit |
| `GITHUB_RUN_ID` | Automaattinen | `report-status.sh` | | `GITHUB_SERVER_URL` | Automaattinen | `report-status.sh` |
| `GITHUB_RUN_NUMBER` | Automaattinen | `docker-build-push.yml` (tag-commit) | | `GITHUB_RUN_ID` | Automaattinen | `report-status.sh`, `tag-commit.sh` |
| `GITHUB_RUN_NUMBER` | Automaattinen | `tag-commit.sh` |
| `GITHUB_ACTOR` | Automaattinen | Docker-labelit |
+9 -11
View File
@@ -1,6 +1,7 @@
# Tech Stack — Gitea Actions CI -kirjasto # Tech Stack — Gitea Actions CI -kirjasto
> Katso myös `git-pages/docs/tech-stack.md`. > ⚠️ POC-vaihe. Osa teknologiavalinnoista voi muuttua uudelleenkirjoituksen
> myötä. Katso myös `git-pages/docs/tech-stack.md`.
--- ---
@@ -20,22 +21,19 @@ Raportit hostataan git-pages-palvelulla (`git-pages/`-Helm-chartti).
Julkaisu: `scripts/publish-git-pages.sh` → PATCH tar. Tarkemmat Julkaisu: `scripts/publish-git-pages.sh` → PATCH tar. Tarkemmat
teknologiavalinnat: `git-pages/docs/tech-stack.md`. teknologiavalinnat: `git-pages/docs/tech-stack.md`.
Tulevaisuus: `GITHUB_STEP_SUMMARY` (Gitea 1.27+) tarjoaa Summary-näkymän
suoraan Gitea UI:ssa.
## Tuetut ulkoiset palvelut ## Tuetut ulkoiset palvelut
| Palvelu | Rajapinta | Käyttötarkoitus | | Palvelu | Rajapinta | Käyttötarkoitus |
|---|---|---| |---|---|---|
| **Gitea REST API** | `/api/v1/` | Commit-status, git-tagit, workflow-dispatch | | **Gitea REST API** | `/api/v1/` | Commit-status, workflow-dispatch, branch-listaus (retention) |
| **git-pages** | HTTP (PATCH tar) | Raporttien hostaus | | **git-pages** | HTTP | Raporttien hostaus |
| **Gitea Packages** | Container registry API | Docker-imagen push | | **Gitea Packages** | Container registry API | Docker-imagen push |
## Mitä EI tueta ## Mitä EI tueta (verrattuna Jenkins-versioon)
| Teknologia | Syy | | Teknologia | Syy |
|---|---| |---|---|
| **Multi-Git-platform** | Vain Gitea — yksi alusta kunnolla (periaate 10) | | **MinIO** | Korvattu git-pagesilla |
| **Custom actionit** | Reusable workflow on kevyempi ja natiivimpi (periaate 2) | | **Multi-Git-platform** | Vain Gitea |
| **Ulkoinen orkestraattori** | Gitean `needs` + `if` hoitaa ohjauksen | | **Jenkins** (shared library, plugins) | Gitea Actions korvaa |
| **Artifactory/Nexus** | Build & push toimii Docker-standardilla. UI-tason linkitys (`report-summary`) vaatii Nexus/Artifactory-spesifin URL-rakenteen — ei vielä toteutettu, toteutetaan tarvittaessa | | **Artifactory/Nexus** | MVP:ssä ei, factory/adapter-pattern valmiina |
+313 -221
View File
@@ -1,298 +1,390 @@
# Reusable workflowt # Reusable workflowt
> Provider-workflowt tarjoavat ydintoiminnallisuuden. Consumer kokoaa ne > ⚠️ **POC-vaihe.** Toteutettu: `quality-gate.yml`. Suunnitteilla:
> haluamakseen pipelineksi. Esimerkkitoteutus: `example-*`-tiedostot. > `ci-master.yml`, `deploy.yml`, `test.yml`.
--- ---
## Yhteiset konventiot ## Yhteiset konventiot
Kaikki workflowt: Kaikki workflowt:
- Käyttävät `concurrency:`-ryhmää estämään saman branchin rinnakkaiset ajot - Käyttävät `concurrency:`-ryhmää estämään saman branchin rinnakkaiset ajot (vastaa Jenkins `disableConcurrentBuilds()`)
- Provider-workflowt lukevat konfiguraation inputtina (`env_json`) - Lukevat konfiguraation `ci-flow-values.yaml`:sta
- Statusraportointi: tool-jobit natiivilla, test-jobit API:lla raporttilinkin takia (ADR 0007) - Raportoivat jokaisen vaiheen Gitea-commitin statukseen `report-status.sh`:lla
- Exit-koodi aina ylös, ei pipeä (ADR 0008) - Käyttävät projektilta saatuja `with:`-parametreja konttien määrittelyyn (kirjasto ei pakota konttiversioita)
--- ---
## Provider-workflowt ## `quality-gate.yml` — Merge-portti
### `config-provider.yml` — Konfiguraation lataus ja validointi **Trigger:** `workflow_call`consumer kutsuu `uses:`-direktiivillä
**Trigger:** `workflow_call` **Rooli:** Laatuportti, joka ajetaan branch protection -sääntönä ennen PR:n
sulkemista mainiin. Pipeline on ajettava (`run > 1`) eikä yhtään jobia
saa failata.
**Inputs:** **Provider-Consumer-malli (ADR 0005):** Provider tarjoaa orkestroinnin
(validointi, raporttien julkaisu, commit-status). Consumer omistaa
pipeline-stepit — valitsee testityökalunsa, mahdolliset laatu- ja
tietoturva-analyy sit sekä niiden järjestyksen. Alla oleva esimerkki
kuvaa tyypillistä Java-mikropalvelua Mavenilla; consumer korvaa nämä
omalla tekniikkapinollaan.
| Parametri | Pakollinen | Kuvaus | ### Inputs (providerin rajapinta)
|-----------|------------|--------|
| `config_path` | Kyllä | Polku `.conf`-tiedostoon |
**Secrets:** | Parametri | Pakollinen | Tyyppi | Kuvaus |
|-----------|------------|--------|--------|
| `env_json` | Kyllä | string | JSON-muotoiset ympäristömuuttujat (`GITEA_API_URL`, `GIT_PAGES_URL`) |
| `*` | — | — | Consumer lisää omat parametrinsa (`maven-image`, `docker-image`, jne.) |
### Secrets
| Secret | Pakollinen | Kuvaus | | Secret | Pakollinen | Kuvaus |
|--------|------------|--------| |--------|------------|--------|
| `GITEA_TOKEN` | Kyllä | Validointia varten | | `GITEA_TOKEN` | Kyllä | Gitea API-kutsuihin (commit-status) |
| `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | Validointia varten | | `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | Raporttien julkaisuun git-pagesiin |
**Outputs:** ### Steppi-kaavio (Java-esimerkki)
| Output | Kuvaus | ```mermaid
|--------|--------| %%{init: {'theme': 'base', 'flowchart': {'arrowheadScale': 2}}}%%
| `env_json` | JSON-muotoiset ympäristömuuttujat | flowchart TD
| `config_path` | Sama polku takaisin (DRY downstream-käyttöön) | VAL["validate
provider: tarkista
CI-konfiguraatio"] --> TEST["test
consumer: mvn test
→ testiraportit + coverage"]
**Steppi-kaavio:** VAL --> AI_SCAN["ai-scan \[optional\]
``` consumer: tietoturva-
checkout → validate CI config → parse conf to JSON tai laatu-skannaus"]
TEST --> SONAR["sonarqube \[optional\]
consumer: mvn sonar:sonar
→ laatupoikkeamat"]
TEST --> PUB["publish-reports
provider: vie raportit
git-pagesiin"]
SONAR --> PUB
AI_SCAN --> PUB
PUB --> STATUS["commit-status
provider: aseta status
linkillä raporttiin"]
FAIL("fail") -. "if: always()" .-> PUB
style VAL fill:#2563eb,color:#ffffff
style TEST fill:#059669,color:#ffffff
style SONAR fill:#7c3aed,color:#ffffff
style AI_SCAN fill:#7c3aed,color:#ffffff
style PUB fill:#0891b2,color:#ffffff
style STATUS fill:#f59e0b,color:#111827
style FAIL fill:#dc2626,color:#ffffff
linkStyle default stroke:#9ca3af,stroke-width:3px
``` ```
### `check-version.yml` — Version ja artifactin tarkistus Consumerin omat stepit (test, sonarqube, ai-scan) ovat esimerkki.
Vastaava rakenne toimii millä tahansa kielellä tai työkalulla.
**Trigger:** `workflow_call` — käytetään vain main-haarassa ### Optionaaliset laatu- ja tietoturvaskannaukset
**Inputs:** `env_json` Consumer voi lisätä pipelineen omia skannaussteppejä testien rinnalle.
Nämä ajetaan rinnakkain `validate`-vaiheen jälkeen ja syöttävät
raporttinsa providerin `publish-reports`-palveluun. Jokainen skannaus
on oma Gitea Actions -jobinsa.
**Outputs:** `artifact_exists` (true/false), `version` (string) ```mermaid
%%{init: {'theme': 'base', 'flowchart': {'arrowheadScale': 2}}}%%
flowchart LR
VAL["validate"] --> SAST["sast
semgrep / codeql"]
VAL --> SCA["sca
snyk / owasp dc"]
VAL --> SECRETS["secret-scan
gitleaks"]
VAL --> LICENSE["license
fossa / scancode"]
VAL --> AI_REVIEW["ai-review
code quality"]
**Steppi-kaavio:** SAST --> PUB
``` SCA --> PUB
checkout → laske versio package.json + git-tageista → output SECRETS --> PUB
LICENSE --> PUB
AI_REVIEW --> PUB
PUB["publish-reports + commit-status"]
style VAL fill:#2563eb,color:#ffffff
style SAST fill:#7c3aed,color:#ffffff
style SCA fill:#7c3aed,color:#ffffff
style SECRETS fill:#7c3aed,color:#ffffff
style LICENSE fill:#7c3aed,color:#ffffff
style AI_REVIEW fill:#7c3aed,color:#ffffff
style PUB fill:#0891b2,color:#ffffff
linkStyle default stroke:#9ca3af,stroke-width:3px
``` ```
### `docker-build-push.yml` — Docker build & push | Kategoria | Esimerkki | Kuvaus |
|-----------|-----------|--------|
| **SAST** | Semgrep, CodeQL | Staattinen analyysi — bugit ja haavoittuvuudet koodista |
| **SCA** | Snyk, OWASP Dependency-Check | Riippuvuuksien tunnetut haavoittuvuudet |
| **Secret scan** | Gitleaks, TruffleHog | API-avaimet, tokenit ja salasanat repossa |
| **Lisenssit** | FOSSA, ScanCode | Riippuvuuksien lisenssien yhteensopivuus |
| **AI review** | — | Automaattinen koodikatselmointi |
**Trigger:** `workflow_call` ### Error handling
**Inputs:** Providerin julkaisu- ja status-stepit käyttävät `if: always()`-ehtoa,
jotta raportit ja commit-status päivittyvät myös failaavista ajoista.
Consumerin omat stepit voivat vapaasti päättää `continue-on-error`- tai
`if: failure()`-logiikastaan. Provider ei määrittele virheidenkäsittelyä
consumerin pipelineen.
| Parametri | Pakollinen | Kuvaus | ### Merge-portti
|-----------|------------|--------|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
| `version` | Kyllä | Version string (check-version output) |
**`env_json`-avaimet:** Branch protection -säännössä Giteassa vaaditaan ennen PR:n sulkemista:
- **Pipeline on ajettu** (`run > 1`, ei "never run" -tila)
- **Kaikki commit-statukset vihreitä** — validate, testit, laatuportit
- Jos joku steppi failaa, status asettuu `failure`-tilaan ja PR:n
sulkeminen estyy
| Avain | Pakollinen | Kuvaus | ### Optionaalinen PR-ympäristö (preview app)
|-------|------------|--------|
| `DOCKER_REGISTRY` | Kyllä | Registry (esim. `gitea.app.keskikuja.site/niko`) |
| `DOCKER_IMAGE_NAME` | Kyllä | Kuvan nimi ilman registry-polkua |
| `DOCKER_UI_URL` | Ei | Registry UI -linkki raportointia varten |
| `DOCKERFILE` | Ei | Dockerfile-polku, oletus `Dockerfile` |
| `GITEA_API_URL` | Kyllä | Gitean API-URL |
| `GIT_TAG_PREFIX` | Ei | Tag-prefix (esim. `docker/`) |
**Secrets:** `GITEA_TOKEN`, `DOCKER_USERNAME`, `DOCKER_PASSWORD` Consumer voi halutessaan buildata kontin ja deployata sen väliaikaiseen
PR-ympäristöön. Tämä on optionaalinen continuation-haara, joka
aktivoituu ehdolla:
**Steppi-kaavio:** - PR:ssä on tietty label (esim. `preview`)
``` - Commit message sisältää triggerisanan (esim. `[preview]`)
build-push (build + push, labelit: commit+date) → tag-commit (git-tagin luonti)
**Elinkaari:**
```mermaid
%%{init: {'theme': 'base', 'flowchart': {'arrowheadScale': 2}}}%%
flowchart LR
QG["quality-gate
testit + skannaukset
ok"] --> BUILD["build-container
tag: pr-42"]
BUILD --> DEPLOY["deploy-pr-env
väliaikainen ympäristö"]
DEPLOY --> STATUS["commit-status
linkki PR-ympäristöön"]
PR_CLOSE["PR merged / closed"] --> CLEANUP["cleanup-pr-env
tuhoa ympäristö"]
style QG fill:#059669,color:#ffffff
style BUILD fill:#0891b2,color:#ffffff
style DEPLOY fill:#7c3aed,color:#ffffff
style STATUS fill:#f59e0b,color:#111827
style PR_CLOSE fill:#dc2626,color:#ffffff
style CLEANUP fill:#dc2626,color:#ffffff
linkStyle default stroke:#9ca3af,stroke-width:3px
``` ```
**Huomio:** Ei käytä `container:`-direktiiviä — ajaa suoraan runnerilla, 1. Quality-gate läpäisty (testit + skannaukset ok)
joten `actions/checkout` toimii ilman node-asennuksia. 2. Buildaa kontti, tagi sisältää PR-numeron (`pr-42`)
3. Deployaa PR-ympäristöön (preview/review app)
4. Asettaa commit-statuksen linkillä ympäristöön
5. **PR:n sulkeutuessa** (merge/close): cleanup-job tuhoaa ympäristön
Tämä on **consumerin vastuulla** — provider tarjoaa tarvittavat
skriptit (`publish-git-pages.sh`, `report-status.sh`), mutta
trigger-ehto, kontin buildaus ja ympäristön hallinta kuuluvat
consumerin pipelineen.
--- ---
### `helm-build-push.yml` — Helm chart build & push ## `ci-master.yml` — Main-branch build
**Trigger:** `workflow_call` **Trigger:** `workflow_call` — kutsutaan main-branchiin pushattaessa
**Inputs:** **Rooli:** Buildaa artifaktin (kontti, JAR, npm-paketti tms.) ja julkaisee
sen rekisteriin. Jos sama commit on jo buildattu (version tag on olemassa),
build skipataan ja siirrytään suoraan test flow'hun.
| Parametri | Pakollinen | Kuvaus | **Provider-Consumer-malli (ADR 0005):** Provider orkestroi idempotent
|-----------|------------|--------| build-logiikan (`isArtifactBuilt`-tarkistus), mutta consumer omistaa
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä | build-stepit — valitsee työkalut ja artifaktityypin.
| `version` | Kyllä | Version string (check-version output) |
| `chart_path` | Ei | Polku Chart.yaml-hakemistoon, oletus `.` |
**`env_json`-avaimet:** ### isArtifactBuilt-check
| Avain | Pakollinen | Kuvaus | Ennen buildia tarkistetaan, onko tälle commitille jo olemassa versiotagi:
|-------|------------|--------|
| `HELM_REGISTRY` | Kyllä | OCI-registry (esim. `gitea.app.keskikuja.site/niko`) |
| `HELM_UI_URL` | Ei | Registry UI -linkki raportointia varten |
| `GITEA_API_URL` | Kyllä | Gitean API-URL |
| `GIT_TAG_PREFIX` | Ei | Tag-prefix (esim. `helm/`) |
**Secrets:** `GITEA_TOKEN`, `HELM_USER`, `HELM_PASSWORD` ```bash
TAG=$(git tag --points-at HEAD | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
**Steppi-kaavio:** if [ -n "$TAG" ]; then
``` echo "artifact_already_built=true" >> $GITHUB_ENV
build-push (helm package → helm push OCI) → tag-commit (git-tagin luonti) echo "artifact_version=$TAG" >> $GITHUB_ENV
fi
``` ```
**Steppien kuvaus `build-push`-jobissa:** Jos tagi löytyy, build- ja push-stepit skipataan. Committia vastaan on
1. **Node.js-asennus**`apk add --no-cache nodejs` (vaaditaan `actions/checkout`-actionia varten) jo olemassa artifakti rekisterissä — uudelleenbuildaus aiheuttaisi
2. **Checkout** — sovellusrepo ja gitea-ci-library `.ci/`-polkuun versiokonflikteja ja tuhlaisi CI-aikaa.
3. **Package**`helm package` versiolla `$VERSION`
4. **Push OCI**`helm push` registryyn autentikoinnilla
5. **Report status** — commit-status + UI-linkki
**Kompromissi:** Kontti `alpine/helm` ei sisällä node.js:ää, mutta ### Steppi-kaavio
`actions/checkout@v4` on JavaScript-action ja vaatii sen. Siksi nodejs
asennetaan lennossa ennen checkouttia. Tämä vaatii internet-yhteyden ```mermaid
eikä toimi air gap -ympäristössä. Korvaa tarvittaessa custom-kontilla %%{init: {'theme': 'base', 'flowchart': {'arrowheadScale': 2}}}%%
(jossa helm + nodejs, ks. `skills/ci-container-build/SKILL.md`). flowchart TD
CHECK{"isArtifactBuilt?
git tag --points-at HEAD"}
CHECK -- "ei" --> QG["quality-gate
testit + skannaukset"]
QG --> BUILD["build-artifact
consumer: docker build /
mvn package / npm build"]
BUILD --> PUSH["push registry
gitea packages /
docker registry"]
PUSH --> TAG["tag-commit
tagittaa commitin
versiolla (esim. 1.2.3.${RUN})"]
CHECK -- "kyllä" --> K8S["continueToTestFlow
(future: K8s-testit
test plan -mukaan)"]
TAG --> K8S
FAIL("fail") -. "quality-gate
ei läpäisty" .-> END
K8S --> END(["end
commit-status"])
style CHECK fill:#f59e0b,color:#111827
style QG fill:#059669,color:#ffffff
style BUILD fill:#0891b2,color:#ffffff
style PUSH fill:#dc2626,color:#ffffff
style TAG fill:#f59e0b,color:#111827
style K8S fill:#7c3aed,color:#ffffff
style FAIL fill:#dc2626,color:#ffffff
style END fill:#2563eb,color:#ffffff
linkStyle default stroke:#9ca3af,stroke-width:3px
```
### Elinkaari
1. **isArtifactBuilt?** — tarkista onko tagi olemassa
2. **quality-gate** — jos ei tagia, aja `quality-gate.yml` (testit, skannaukset)
3. **build-artifact** — jos quality-gate läpäisty, buildaa artifakti
4. **push registry** — julkaise rekisteriin (Gitea Packages, Docker registry, jne.)
5. **tag-commit** — tagittaa commitin versiolla (esim. `1.2.3.<run_number>`)
6. **continueToTestFlow***(future)* aja K8s-testit test plan -mukaan
7. **commit-status** — aseta lopullinen status
### Concurrency
```yaml
concurrency:
group: master-${{ github.repository }}
cancel-in-progress: false
```
Vain yksi master-build kerrallaan per repo. Ei cancel-in-progress —
käynnissä olevan buildin annetaan valmistua.
--- ---
### `gitops-dispatch.yml` — GitOps-päivityksen dispatch ## `deploy.yml` — GitOps-deployment
**Trigger:** `workflow_call` **Trigger:** `workflow_dispatch` (aina dispatchataan toisesta workflow'sta)
**Inputit:** **Elinkaari:**
| Parametri | Pakollinen | Kuvaus |
|-----------|------------|--------|
| `env_json` | Kyllä | Konffi, josta luetaan `GITOPS_FILE`, `GITOPS_YQ_TPL`, `GITOPS_REPO`, `GIT_TAG_PREFIX` |
| `version` | Kyllä | Päivitettävä versio (check-version output) |
| `component` | Kyllä | `chart` tai `container` — tunniste summary-riville |
**Secretit:** `GITOPS_TOKEN`
**Outputit:** `summary` — pipe-formaatti: `{component}|{version}|{status}|{commit_sha}|{repo}`
**Steppi-kaavio:**
``` ```
checkout → gitops-dispatch.sh → dispatch-workflow.sh → GITOPS_SUMMARY output start → read-yaml → update-value → commit → push → report-cross-repo → end
```
### Inputs (dispatch-parametrit)
| Parametri | Kuvaus |
|-----------|--------|
| `environment` | Ympäristön nimi (korvaa `{.environment}`) |
| `version` | Uusi konttiversio |
| `root_commit` | Mikropalvelun commit josta deploy käynnistyi |
| `root_repo` | Mikropalvelun repo |
| `root_build_url` | URL mikropalvelun buildiin |
### Mitä deploy tekee
1. Lukee `{projectFolder}/{fileName}` YAML-tiedoston (korvaa `{.environment}``environment`)
2. Päivittää `{property}`-avaimen arvoksi `{version}`
3. `git add`, `git commit -m "deploy {version} to {environment}"`
4. `git push origin HEAD:master`
5. Raportoi statuksen:
- Helm-repon committiin: **"from {root_commit}"**, URL → root-build
- Mikropalvelun committiin (`root_commit`): **"deployed to {environment}"**, URL → Helm-commit
6. Palauttaa Helm-commitin hashin (`outputs.commit`)
### Concurrency
```yaml
concurrency:
group: deploy-${{ github.repository }}-${{ inputs.environment }}
cancel-in-progress: false
``` ```
--- ---
## Consumer-esimerkki (`example-*`) ## `test.yml` — Test flow -steppi
### `example-feature.yml` — Feature-haaran CI **Trigger:** `workflow_dispatch` (dispatchataan deploy-workflow'n jälkeen)
**Trigger:** `push` [branches-ignore: main] **Elinkaari:**
``` ```
load-config → bats + cucumber → report-summary (always) start → version-check → run-tests → push-reports → report-cross-repo → end
``` ```
### `example-main.yml` — Main-haaran CI ### Inputs (dispatch-parametrit)
**Trigger:** `push` [branches: main] | Parametri | Kuvaus |
|-----------|--------|
| `environment` | Testiympäristö |
| `version` | Testattava konttiversio |
| `tags` | Cucumber-tagit |
| `versionApiUrl` | URL version tarkistukseen |
| `versionCheckScript` | Polku version check -skriptiin |
| `root_commit` | Mikropalvelun commit |
| `root_repo` | Mikropalvelun repo |
| `deploy_commit` | Helm-repon commit (deployattu versio) |
| `deploy_repo` | Helm-repo |
``` ### Version check
load-config ───────────────────────────────────────────────────────┐
load-config-helm ───────────────────────────────────────────┐ │ Ennen testejä varmistetaan, että ympäristössä pyörii oikea versio:
│ │
check-version ←─────────────────────────────────────────────┘ │ ```yaml
│ │ - name: Check deployed version
└→ bats + cucumber │ if: inputs.versionCheckScript || inputs.versionApiUrl
├─ docker-build-push → gitops-container ─┐ │ run: |
└─ helm-build-push → gitops-chart ──────┤ │ if [ -n "${{ inputs.versionCheckScript }}" ]; then
├→ report-summary ←┘ bash "${{ inputs.versionCheckScript }}" "${{ inputs.versionApiUrl }}" "${{ inputs.version }}"
tag-maintenance ←────────────────────────┘ fi
``` ```
GitOps-jobit (`gitops-chart`, `gitops-container`) käyttävät Version check -skripti pollaa Fibonacci-backoffilla — ks. [config-model.md](config-model.md).
`gitops-dispatch.yml`-provider-workflowia. Kaksisuuntainen track:
dispatch-workflow.sh → GITOPS_COMMIT + GITOPS_SUMMARY.
Katso [skills/gitops-update/SKILL.md](../skills/gitops-update/SKILL.md).
### `example-bats-tests.yml` — Bats unit-testit ### Cross-repo-raportointi
**Trigger:** `workflow_call` Testien jälkeen raportoidaan kolmeen committiin:
Ajaa Bats-testit Docker-kontissa, generoi coveragen (`bashcov`), julkaisee 1. Testi-repon oma commit: testin status
raportit git-pagesiin, asettaa commit-statuksen linkillä raporttiin. 2. Mikropalvelun commit (`root_commit`): "testit OK/epäonnistui"
3. Helm-repon commit (`deploy_commit`): "testattu v{version}"
### `example-cucumber-tests.yml` — Cucumber hyväksymätestit ### Concurrency
**Trigger:** `workflow_call` ```yaml
concurrency:
Ajaa Cucumber-testit Node-kontissa, julkaisee raportit git-pagesiin, asettaa group: test-${{ inputs.environment }}
commit-statuksen linkillä raporttiin. cancel-in-progress: false
```
### `report-summary.yml` — Raporttien koontinäkymä
**Trigger:** `workflow_call` — ajetaan `if: always()` testien jälkeen
**Inputs:** `env_json`, `suites` (space-separated lista suite-nimistä), `gitops` (optional JSON array)
**GitOps-tuki:** Jos `gitops` input on annettu (JSON array objekteilla
`component`, `version`, `status`, `commit`, `repo`), workflow lisää
GitOps-päivitystaulukon testiraporttien perään. Jokaiselle riville
muodostuu linkki GitOps-repon committiin.
Generoi Markdown-taulukon `GITHUB_STEP_SUMMARY`:yn kaikista julkaistuista
raporteista. Renderöityy HTML:ksi Gitea 1.27+ Summary-välilehdellä.
Forward-compatibeli — ei haittaa vanhemmilla Gitea-versioilla.
---
## Provider-skriptit
### `gitops-update.sh` — GitOps-version päivitys
**Riippuvuudet:** `yq`, `scripts/report-status.sh`, `git`
Päivittää GitOps-repon konfiguraatiotiedoston versionumeron `yq`:lla,
committaa muutoksen ja asettaa commit-statuksen molempiin repoihin
(kaksisuuntainen track):
| Status | Mihin repo | Context | Linkki |
|---|---|---|---|
| ✅ | **GitOps-repo** | `source/{repo}` | Code-repon committiin |
| ✅ | **Code-repo** (dispatchin jälkeen) | `gitops/{repo} {RUN_ID}` | GitOps-repon committiin |
**Input-ympäristömuuttujat (ajetaan GitOps-repon workflow'ssa):**
| Muuttuja | Pakollinen | Kuvaus |
|---|---|---|
| `INPUT_FILE` | Kyllä | Tiedosto GitOps-repossa (esim. `dev/Chart.yaml`) |
| `YQ_TPL` | Kyllä | `yq`-lauseke `{{VERSION}}`-placeholderilla |
| `VERSION` | Kyllä | Uusi versio (esim. `0.2.3`) |
| `SOURCE_REPO` | Kyllä | Lähdekoodirepo (esim. `org/app`) |
| `SOURCE_COMMIT` | Kyllä | Lähdekoodin commit-SHA |
| `GITOPS_REPO` | Kyllä | GitOps-repo slug |
| `GITEA_API_URL` | Kyllä | Gitean API-URL |
| `GITEA_TOKEN` | Kyllä | Gitea API-token (write GitOps-repoon) |
| `GITOPS_BRANCH` | Ei | GitOps-repon branch (oletus `main`) |
| `GIT_TAG_PREFIX` | Ei | Komponentin tag-prefix status-nimeämiseen |
**Commit-status (GitOps-repoon):**
| Kenttä | Formaatti | Esimerkki |
|--------|-----------|-----------|
| Context | `source/{repo}` | `source/gitea-ci-library` |
| Description | `Install to {env} {version}` | `Install to dev 0.2.0` |
| Target URL | Linkki code-repon committiin | `/org/repo/commit/sha` |
`{env}` parsitaan `INPUT_FILE`:stä (`dev/Chart.yaml``dev`).
**Steppikuvaus:**
1. Korvaa `YQ_TPL`:n `{{VERSION}}` versiolla
2. Muodostaa `CLONE_URL` tokenilla ja hostilla
3. Kloonaa GitOps-repon
4. Ajaa `yq eval -i` päivittääkseen tiedoston
5. Jos muutoksia: commit + push `[skip ci]`, muuten status `— no change`
6. Asettaa commit-statuksen GitOps-repoon (source-konteksti, linkki code-repoon)
**Scriptiä ei ajeta code reposta.** Se ajaa GitOps-repon workflow'ssa.
### Code-repon commit-status (dispatchin jälkeen)
GitOps-päivityksen valmistuttua `dispatch-workflow.sh` tulostaa
`GITOPS_COMMIT=<sha>` (GitOps-repon commitin SHA). Code repo asettaa
oman commit-statusinsa linkillä GitOps-committiin:
| Kenttä | Formaatti | Esimerkki |
|--------|-----------|-----------|
| Context | `gitops/{repo} {RUN_ID}` | `gitops/gitea-ci-library 473` |
| Description | `Install to {env} {version}` | `Install to dev 0.2.0` |
| Target URL | Linkki GitOps-repon committiin | `/niko/gitea-ci-gitops-tests/commit/def456` |
### Loppuraportti (GITHUB_STEP_SUMMARY)
`report-summary.yml` (optio `gitops`-inputti) lisää GitOps-rivit
GITHUB_STEP_SUMMARYyn:
| Component | Version | Status | GitOps commit |
|---|---|---|---|
| helm | 0.2.0 | success | [link](...) |
Kokonainen esimerkki molemmista puolista: [skills/gitops-update/SKILL.md](../skills/gitops-update/SKILL.md)
ja [.gitea/workflows/example-main.yml](../.gitea/workflows/example-main.yml).
---
+28 -72
View File
@@ -176,90 +176,46 @@ if [ "${#TO_DELETE[@]}" -eq 0 ]; then
fi fi
echo "" echo ""
echo "=== Phase 4: full site rebuild ===" echo "=== Phase 4: whiteout deletion ==="
echo "Rebuilding site (${#TO_DELETE[@]} report(s) to delete)..." echo "Creating whiteout tar for ${#TO_DELETE[@]} report(s)..."
ARCHIVE_FILE=$(mktemp) WHITEOUT_TAR=$(mktemp)
SITE_DIR=$(mktemp -d) trap 'rm -f "$WHITEOUT_TAR"' EXIT
NEW_TAR=$(mktemp)
cleanup_phase4() {
rm -f "$ARCHIVE_FILE" "$NEW_TAR"
rm -rf "$SITE_DIR"
}
trap cleanup_phase4 EXIT
# Try archive.tar first python3 -c "
echo "Downloading archive.tar..." import tarfile, sys
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 tar = tarfile.open(name='${WHITEOUT_TAR}', mode='w')
echo "Extracting archive..."
tar -xf "$ARCHIVE_FILE" -C "$SITE_DIR"
for dir in "${TO_DELETE[@]}"; do dirs = set()
if [ -d "$SITE_DIR/$dir" ]; then for d in sys.argv[1:]:
echo " Removing: $dir" dirs.add(d.strip())
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) tarinfo = tarfile.TarInfo()
tarinfo.type = tarfile.CHRTYPE
tarinfo.devmajor = 0
tarinfo.devminor = 0
if [ -z "$ALL_PATHS" ]; then for d in sorted(dirs, key=len, reverse=True):
echo "ERROR: no files in manifest - cannot rebuild" >&2 info = tarinfo
exit 1 info.name = d
fi tar.addfile(info)
EXCLUDE_GREP="" tar.close()
for dir in "${TO_DELETE[@]}"; do " "${TO_DELETE[@]}"
EXCLUDE_GREP="${EXCLUDE_GREP}${EXCLUDE_GREP:+|}^${dir}/"
done
if [ -n "$EXCLUDE_GREP" ]; then echo "Patching ${PAGES_URL}/ with whiteout tar..."
KEEP_PATHS=$(echo "$ALL_PATHS" | grep -v -E "$EXCLUDE_GREP" || true) HTTP_CODE=$(curl_with_host -X PATCH "${PAGES_URL}/" \
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" \ -H "Content-Type: application/x-tar" \
--data-binary @"${NEW_TAR}" \ -H "Atomic: no" \
--data-binary @"${WHITEOUT_TAR}" \
-w "%{http_code}" \ -w "%{http_code}" \
-o /dev/null) -o /dev/null)
echo "HTTP ${HTTP_CODE}" echo "HTTP $HTTP_CODE"
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "204" ]; then if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then
echo "Site rebuild completed." echo "Retention cleanup finished."
else else
echo "ERROR: PUT HTTP ${HTTP_CODE}" >&2 echo "ERROR: retention HTTP ${HTTP_CODE}" >&2
exit 1 exit 1
fi fi
+1 -11
View File
@@ -6,7 +6,7 @@ metadata:
labels: labels:
{{- include "git-pages.componentLabels" . | nindent 4 }} {{- include "git-pages.componentLabels" . | nindent 4 }}
annotations: annotations:
"helm.sh/hook": post-install "helm.sh/hook": post-install, post-upgrade
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
spec: spec:
backoffLimit: 5 backoffLimit: 5
@@ -32,16 +32,6 @@ spec:
-H "Host: {{ .Values.ingress.host }}" \ -H "Host: {{ .Values.ingress.host }}" \
-o /dev/null "http://git-pages:3000/.git-pages/health" -o /dev/null "http://git-pages:3000/.git-pages/health"
do sleep 2; done 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..." echo "Init: creating placeholder site..."
WORK=$(mktemp -d) WORK=$(mktemp -d)
mkdir -p "$WORK/__init__" mkdir -p "$WORK/__init__"
-113
View File
@@ -1,113 +0,0 @@
# Helm Registry Setup (OCI)
Pipeline paketoi Helm chartin OCI-artefaktiksi ja pushee sen OCI-rekisteriin.
---
## 1. Konfiguroi `gitea-env.conf`
```
# HELM_REGISTRY on muotoa: registry.example.com/org
#
# host+org: registry.example.com/org
#
# Pipeline rakentaa OCI-refin: oci://${HELM_REGISTRY}/<chart-name>:${VERSION}
# (chart-name tulee Chart.yaml:n name-kentästä)
HELM_REGISTRY=gitea.app.keskikuja.site/niko # PAKOLLINEN — tyhjä ei käy
HELM_UI_URL= # valinnainen — tarkista Giteasta kontin oma UI-osoite, workflow liittää perään /chart-name/VERSION
GIT_TAG_PREFIX=git-pages/ # valinnainen — monorepo-tägäys
VERSION_FILE=git-pages/Chart.yaml # valinnainen — jos Chart.yaml ei rootissa
```
| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `HELM_REGISTRY` | **kyllä** | Registry host + owner (esim. `gitea.app.site/niko`). **Tyhjä pysäyttää workflow'n.** |
| `HELM_UI_URL` | ei | Base-URL OCI-paketin UI-sivulle (ilman chart-nimeä ja versiota). Osoite riippuu onko paketti linkitetty repoon vai ei — tarkista Giteasta. Workflow liittää perään `/chart-name/VERSION`. Jos tyhjä, commit-statusia ei erikseen aseteta. |
| `GIT_TAG_PREFIX` | ei | Etuliite git-tägille. Pakollinen monorepossa, jotta tagit eivät sekoitu muihin komponentteihin. |
| `VERSION_FILE` | ei | Polku version lähteeseen (Chart.yaml, package.json, VERSION). Oletus: juuren `Chart.yaml`. |
**OCI-ref = `oci://${HELM_REGISTRY}/<chart-name>:${VERSION}`**
Esim. `oci://gitea.app.keskikuja.site/niko/git-pages:1.2.3`
Chartin nimi (`<chart-name>`) määräytyy `Chart.yaml`-tiedoston `name`-kentästä.
---
## 2. Luo PAT (Personal Access Token) Giteassa
**Gitea → oma profiili (oikea yläkulma) → Settings → Applications → Manage Access Tokens → Generate New Token**
Valitse scope:
| Scope | Pääsy |
|---|---|
| `package` | **Read and Write** |
> Tämä token toimii salasanana `helm registry login` -komennossa. Muut scopet (kuten `repository`) eivät riitä — konttirekisteri vaatii nimenomaan `package`-scopen.
Tokenin arvo näytetään **vain kerran** luomisen yhteydessä. Kopioi se talteen.
---
## 3. Tallenna PAT repositoryn Secretsiin
Nämä ovat kaksi eri paikkaa:
- **Access Tokenit** (User Settings) = missä luot tokenin
- **Repository Secrets** (Repository Settings) = minne talletat sen workflow'n käyttöön
**Repository → Settings → Actions → Secrets → Add new secret**
| Secret | Arvo |
|---|---|
| `HELM_PASSWORD` | Edellisessä vaiheessa luotu PAT |
`HELM_USER`-secretiä **ei tarvita**. Workflow käyttää automaattisesti `${{ github.actor }}` (workflowin käynnistäjä).
Jos registry vaatii eri käyttäjätunnuksen kuin `github.actor` (esim. Harbor, Artifactory), lisää myös:
| Secret | Arvo |
|---|---|
| `HELM_USER` | Registryn käyttäjätunnus |
---
## 4. Tarkistuslista ennen ajoa
- [ ] `HELM_REGISTRY` asetettu `gitea-env.conf`issa
- [ ] (tarvittaessa) `HELM_UI_URL` asetettu — ilman tätä commit-statusia ei erikseen aseteta
- [ ] PAT luotu Giteassa scopella `package` Read and Write
- [ ] `HELM_PASSWORD`-secret tallennettu repositoryn Secretsiin (se PAT)
- [ ] (tarvittaessa) `HELM_USER`-secret — oletus `github.actor`
---
## 5. Esimerkkejä eri polkurakenteista
### 5a. Hosti + org — Gitea user-taso
```
HELM_REGISTRY=gitea.app.keskikuja.site/niko
```
- OCI-ref: `oci://gitea.app.keskikuja.site/niko/git-pages:1.2.3`
- Paketti käyttäjän `niko` alla. Linkitys repoon tehdään Gitean UI:sta: paketin sivulta (Package → Settings) → linkitä repositoryyn.
- `HELM_PASSWORD` = Gitea PAT scopella `package`
### 5b. Hosti + org — Harbor
```
HELM_REGISTRY=harbor.example.com/projekti
```
- `HELM_USER` = Harbor-käyttäjä
- `HELM_PASSWORD` = Harbor-token
### 5c. Artifactory
```
HELM_REGISTRY=artifactory.example.com/helm-local
```
- `HELM_USER` = service account
- `HELM_PASSWORD` = API-token
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "gitea-ci-library", "name": "gitea-ci-library",
"version": "0.2.0", "version": "0.1.0",
"description": "", "description": "",
"main": "cucumber.js", "main": "cucumber.js",
"directories": { "directories": {
-60
View File
@@ -1,60 +0,0 @@
#!/usr/bin/env bash
set -e
RAW_VERSION=""
if [ -n "${VERSION_FILE-}" ] && [ -f "${VERSION_FILE-}" ]; then
RAW_VERSION=$(tr -d "$(printf '\xef\xbb\xbf')" < "${VERSION_FILE}" | sed -n 's/^version:[[:space:]]*\([^[:space:]]*\).*/\1/p')
if [ -z "${RAW_VERSION}" ]; then
if echo "${VERSION_FILE}" | grep -q -E '\.json$'; then
RAW_VERSION=$(jq -r '.version' "${VERSION_FILE}")
else
RAW_VERSION=$(cat "${VERSION_FILE}" | tr -d '[:space:]')
fi
fi
fi
if [ -z "${RAW_VERSION}" ]; then
if [ -f VERSION ]; then
RAW_VERSION=$(cat VERSION | tr -d '[:space:]')
elif [ -f package.json ]; then
RAW_VERSION=$(jq -r '.version' package.json)
elif [ -f pom.xml ]; then
RAW_VERSION=$(grep -oP '<version>\K[^<]+' pom.xml | head -1)
elif [ -f Chart.yaml ]; then
RAW_VERSION=$(tr -d "$(printf '\xef\xbb\xbf')" < Chart.yaml | sed -n 's/^version:[[:space:]]*\([^[:space:]]*\).*/\1/p')
else
echo "ERROR: No version source found (VERSION_FILE, VERSION, package.json, pom.xml, Chart.yaml)" >&2
exit 1
fi
fi
BASE_VERSION=$(echo "$RAW_VERSION" | cut -d'.' -f1-2)
echo "gitea-ci-library - Tunnistettu Major.Minor versio: $BASE_VERSION"
TAGS_JSON=$(curl -s -f -H "Authorization: token ${GITEA_TOKEN}" \
"${SERVER_URL}/api/v1/repos/${REPO}/tags")
TAG=$(echo "$TAGS_JSON" | jq -r --arg prefix "${GIT_TAG_PREFIX-}" --arg sha "${SHA}" '
if type == "array" then
.[] | select(.commit.sha == $sha and (.name | startswith($prefix))) | .name
else empty end' | head -1)
mkdir -p /tmp/build-ctx
if [ -n "$TAG" ]; then
echo "ARTIFACT_EXISTS=true" > /tmp/build-ctx/build.env
echo "NEXT_VERSION=$TAG" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Artefakti löytyi jo tagilla: $TAG."
else
echo "ARTIFACT_EXISTS=false" > /tmp/build-ctx/build.env
HIGHEST_PATCH=$(echo "$TAGS_JSON" | jq -r --arg prefix "${GIT_TAG_PREFIX-}" --arg bv "${GIT_TAG_PREFIX-}${BASE_VERSION}." '
if type == "array" then .[] | .name | select(startswith($bv)) | sub($bv; "") | tonumber else empty end' | sort -rn | head -1)
if [ -z "$HIGHEST_PATCH" ]; then NEXT_PATCH=0; else NEXT_PATCH=$((HIGHEST_PATCH + 1)); fi
FULL_VERSION="${BASE_VERSION}.${NEXT_PATCH}"
echo "NEXT_VERSION=$FULL_VERSION" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Uusi vapaa versio: $FULL_VERSION"
fi
-110
View File
@@ -1,110 +0,0 @@
#!/usr/bin/env sh
set -eu
DESCRIPTION="${1:-}"
CONTEXT="${2:-}"
SUITE="${3:-}"
STATUS="${4:-success}"
[ -n "$DESCRIPTION" ] || { echo "ERROR: description argument required" >&2; exit 1; }
[ -n "$CONTEXT" ] || { echo "ERROR: context argument required" >&2; exit 1; }
[ -n "$SUITE" ] || { echo "ERROR: suite argument required" >&2; exit 1; }
REPORT_DIR="reports/${SUITE}"
if [ ! -d "$REPORT_DIR" ]; then
echo "ERROR: $REPORT_DIR not found" >&2
sh .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT"
exit 1
fi
FILE_COUNT=0
SUBDIR_COUNT=0
ENTRIES=""
for f in "$REPORT_DIR"/*; do
[ -f "$f" ] || continue
base=$(basename "$f")
[ "$base" = "index.html" ] && continue
FILE_COUNT=$((FILE_COUNT + 1))
ENTRIES="${ENTRIES}file:${base}
"
done
for d in "$REPORT_DIR"/*/; do
[ -d "$d" ] || continue
base=$(basename "$d")
[ -f "$d/index.html" ] || continue
SUBDIR_COUNT=$((SUBDIR_COUNT + 1))
ENTRIES="${ENTRIES}dir:${base}
"
done
TOTAL=$((FILE_COUNT + SUBDIR_COUNT))
if [ "$TOTAL" -eq 0 ]; then
echo "ERROR: no reportable items in $REPORT_DIR" >&2
sh .ci/scripts/report-status.sh failure "$DESCRIPTION" "$CONTEXT"
exit 1
fi
SHA8=$(echo "${GITHUB_SHA:-xxxxxxxx}" | cut -c1-8)
humanize() {
name="$1"
name=$(echo "$name" | sed -e 's/\.[^.]*$//' -e 's/[-_]/ /g')
first=$(echo "$name" | cut -c1 | tr '[:lower:]' '[:upper:]')
rest=$(echo "$name" | cut -c2-)
echo "${first}${rest}"
}
generate_index() {
{
echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
echo "<title>$DESCRIPTION</title>"
echo '<style>body{font-family:sans-serif;margin:2em;max-width:960px}h1{color:#1e293b}ul{list-style:none;padding:0}li{margin:.5em 0;padding:.5em;background:#f8fafc;border-radius:6px}a{color:#2563eb;text-decoration:none}a:hover{text-decoration:underline}</style>'
echo "</head><body><h1>$DESCRIPTION</h1><ul>"
echo "$ENTRIES" | while IFS= read -r entry; do
[ -z "$entry" ] && continue
entry_type=$(echo "$entry" | cut -d: -f1)
entry_name=$(echo "$entry" | cut -d: -f2-)
if [ "$entry_type" = "file" ]; then
echo "<li><a href=\"$entry_name\">$(humanize "$entry_name")</a></li>"
else
cap=$(echo "$entry_name" | sed 's/\(.\).*/\1/' | tr '[:lower:]' '[:upper:]')$(echo "$entry_name" | sed 's/.//')
echo "<li><a href=\"$entry_name/index.html\">${cap}</a></li>"
fi
done
echo '</ul></body></html>'
} > "$REPORT_DIR/index.html"
}
STAGED="reports/${SHA8}/${SUITE}"
mkdir -p "$STAGED"
if [ "$TOTAL" -eq 1 ]; then
cp -a "$REPORT_DIR/." "$STAGED/"
sh .ci/scripts/publish-git-pages.sh "$SUITE"
first_entry=$(echo "$ENTRIES" | head -1)
first_type=$(echo "$first_entry" | cut -d: -f1)
first_name=$(echo "$first_entry" | cut -d: -f2-)
if [ "$first_type" = "file" ]; then
SINGLE_ENTRY="$first_name"
else
SINGLE_ENTRY="${first_name}/index.html"
fi
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}/${SUITE}/${SINGLE_ENTRY}"
sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "" "$URL"
else
generate_index
cp -a "$REPORT_DIR/." "$STAGED/"
sh .ci/scripts/publish-git-pages.sh "$SUITE"
sh .ci/scripts/report-status.sh "$STATUS" "$DESCRIPTION" "$CONTEXT" "$SUITE"
fi
rm -rf "$STAGED"
+10 -32
View File
@@ -17,11 +17,6 @@ POLL_INTERVAL="${DISPATCH_POLL_INTERVAL:-10}"
[ -z "$GITEA_API_URL" ] && echo "ERROR: gitea_api_url argument is required" >&2 && exit 1 [ -z "$GITEA_API_URL" ] && echo "ERROR: gitea_api_url argument is required" >&2 && exit 1
[ -z "$GITEA_TOKEN" ] && echo "ERROR: gitea_token argument is required" >&2 && exit 1 [ -z "$GITEA_TOKEN" ] && echo "ERROR: gitea_token argument is required" >&2 && exit 1
# Generate unique dispatch_id for display_title matching
# Can be overridden via DISPATCH_ID env var (for tests)
DISPATCH_ID="${DISPATCH_ID:-$(xxd -l 4 -p /dev/urandom 2>/dev/null || openssl rand -hex 4 2>/dev/null || od -An -N4 -tx1 /dev/urandom | tr -d ' \n')}"
INPUTS_JSON=$(echo "$INPUTS_JSON" | jq --arg id "$DISPATCH_ID" '. + {dispatch_id: $id}')
DISPATCH_URL="$GITEA_API_URL/api/v1/repos/$TARGET_REPO/actions/workflows/$WORKFLOW_FILE/dispatches" DISPATCH_URL="$GITEA_API_URL/api/v1/repos/$TARGET_REPO/actions/workflows/$WORKFLOW_FILE/dispatches"
DISPATCH_BODY=$(jq -nc --arg ref "$REF" --argjson inputs "$INPUTS_JSON" '{ref: $ref, inputs: $inputs}') DISPATCH_BODY=$(jq -nc --arg ref "$REF" --argjson inputs "$INPUTS_JSON" '{ref: $ref, inputs: $inputs}')
@@ -37,30 +32,19 @@ if [ "$DISPATCH_CODE" != "201" ]; then
exit 1 exit 1
fi fi
# Poll: find dispatched run by display_title matching RUNS_URL="$GITEA_API_URL/api/v1/repos/$TARGET_REPO/actions/runs?status=running"
RUN_ID="" RUNS_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
-H "Authorization: token $GITEA_TOKEN" "$RUNS_URL")
RUN_ID=$(echo "$RUNS_RESP" | jq -r '.workflow_runs[0].id // empty')
if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
echo "ERROR: Could not find dispatched workflow run" >&2
exit 1
fi
TIMEOUT_SECONDS=$(awk "BEGIN {printf \"%.3f\", $TIMEOUT_MINUTES * 60}") TIMEOUT_SECONDS=$(awk "BEGIN {printf \"%.3f\", $TIMEOUT_MINUTES * 60}")
START_TIME=$(date +%s) START_TIME=$(date +%s)
while [ -z "$RUN_ID" ]; do
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
if awk -v e="$ELAPSED" -v t="$TIMEOUT_SECONDS" 'BEGIN { exit !(e >= t) }'; then
echo "ERROR: Timeout after ${TIMEOUT_MINUTES} minutes — run not found" >&2
exit 124
fi
RUNS_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
"$GITEA_API_URL/api/v1/repos/$TARGET_REPO/actions/runs?event=workflow_dispatch&limit=10" \
-H "Authorization: token $GITEA_TOKEN")
RUN_ID=$(echo "$RUNS_RESP" | jq -r --arg id "$DISPATCH_ID" \
'[.workflow_runs[] | select(.display_title | contains($id))] | .[0].id // empty')
[ -z "$RUN_ID" ] && sleep "$POLL_INTERVAL"
done
# Poll: wait for run to complete
while true; do while true; do
NOW=$(date +%s) NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME)) ELAPSED=$((NOW - START_TIME))
@@ -77,12 +61,6 @@ while true; do
if [ "$STATUS" = "completed" ]; then if [ "$STATUS" = "completed" ]; then
CONCLUSION=$(echo "$RUN_RESP" | jq -r '.conclusion // "failure"') CONCLUSION=$(echo "$RUN_RESP" | jq -r '.conclusion // "failure"')
if [ "$CONCLUSION" = "success" ]; then if [ "$CONCLUSION" = "success" ]; then
GITOPS_COMMIT=""
BRANCH_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
"$GITEA_API_URL/api/v1/repos/$TARGET_REPO/branches/$REF" \
-H "Authorization: token $GITEA_TOKEN") || true
GITOPS_COMMIT=$(echo "$BRANCH_RESP" | jq -r '.commit.id // empty')
echo "GITOPS_COMMIT=$GITOPS_COMMIT"
exit 0 exit 0
fi fi
echo "ERROR: Workflow completed with conclusion: $CONCLUSION" >&2 echo "ERROR: Workflow completed with conclusion: $CONCLUSION" >&2
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
: "${GITOPS_FILE:?}"
: "${GITOPS_YQ_TPL:?}"
: "${GITOPS_VERSION:?}"
: "${GITOPS_SOURCE_REPO:?}"
: "${GITOPS_SOURCE_COMMIT:?}"
: "${GITOPS_REPO:?}"
: "${GITOPS_WORKFLOW:?}"
: "${GITEA_API_URL:?}"
: "${GITEA_TOKEN:?}"
TIMEOUT="${GITOPS_DISPATCH_TIMEOUT:-30}"
INPUTS=$(jq -nc \
--arg file "$GITOPS_FILE" \
--arg yq_tpl "$GITOPS_YQ_TPL" \
--arg version "$GITOPS_VERSION" \
--arg source_repo "$GITOPS_SOURCE_REPO" \
--arg source_commit "$GITOPS_SOURCE_COMMIT" \
--arg git_tag_prefix "${GITOPS_TAG_PREFIX:-}" \
'{file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_tag_prefix}')
DIR="$(cd "$(dirname "$0")" && pwd)"
set +e
OUTPUT=$(bash "$DIR/dispatch-workflow.sh" \
"$GITOPS_REPO" "$GITOPS_WORKFLOW" "main" \
"$INPUTS" "$GITEA_API_URL" "$GITEA_TOKEN" "$TIMEOUT" 2>&1)
EXIT=$?
set -e
echo "$OUTPUT"
STATUS="failure"
GITOPS_SHA=""
if [ "$EXIT" = "0" ]; then
STATUS="success"
GITOPS_SHA=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2)
fi
COMPONENT="${GITOPS_TAG_PREFIX:-${GITOPS_FILE}}"
echo "GITOPS_SUMMARY=${COMPONENT}|${GITOPS_VERSION}|${STATUS}|${GITOPS_SHA}|${GITOPS_REPO}"
exit "$EXIT"
-114
View File
@@ -1,114 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_gitops_fail() {
local MSG="${1:-GitOps update failed}"
echo "[ERROR] ${MSG}" >&2
if [ -n "${GITOPS_REPO:-}" ] && [ -n "${GITOPS_SHA:-}" ] && \
[ -n "${SOURCE_REPO:-}" ] && [ -n "${SOURCE_COMMIT:-}" ] && \
[ -n "${GITEA_API_URL:-}" ] && [ -n "${GITEA_TOKEN:-}" ]; then
local env repo context
env=$(dirname "${INPUT_FILE}")
repo=$(basename "${SOURCE_REPO}")
context="${repo} ${GITHUB_RUN_ID:-unknown}"
[ -n "${GIT_TAG_PREFIX:-}" ] && context="${repo}/${GIT_TAG_PREFIX} ${GITHUB_RUN_ID:-unknown}"
local SOURCE_URL="${GITEA_API_URL}/${SOURCE_REPO}/commit/${SOURCE_COMMIT}"
ROOT_REPO="${GITOPS_REPO}" ROOT_COMMIT="${GITOPS_SHA}" \
GITEA_API_URL="${GITEA_API_URL}" GITEA_TOKEN="${GITEA_TOKEN}" \
bash "${SCRIPT_DIR}/report-status.sh" failure "Install to ${env} ${VERSION}" \
"${context}" "" "${SOURCE_URL}" 2>/dev/null || true
fi
exit 1
}
_gitops_validate() {
[ -n "${INPUT_FILE:-}" ] || _gitops_fail "INPUT_FILE is required"
[ -n "${YQ_TPL:-}" ] || _gitops_fail "YQ_TPL is required"
[ -n "${VERSION:-}" ] || _gitops_fail "VERSION is required"
[ -n "${SOURCE_REPO:-}" ] || _gitops_fail "SOURCE_REPO is required"
[ -n "${SOURCE_COMMIT:-}" ] || _gitops_fail "SOURCE_COMMIT is required"
[ -n "${GITOPS_REPO:-}" ] || _gitops_fail "GITOPS_REPO is required"
[ -n "${GITEA_TOKEN:-}" ] || _gitops_fail "GITEA_TOKEN is required"
[ -n "${GITEA_API_URL:-}" ] || _gitops_fail "GITEA_API_URL is required"
}
_gitops_success() {
local env repo context
env=$(dirname "${INPUT_FILE}")
repo=$(basename "${SOURCE_REPO}")
context="${repo} ${GITHUB_RUN_ID:-unknown}"
[ -n "${GIT_TAG_PREFIX:-}" ] && context="${repo}/${GIT_TAG_PREFIX} ${GITHUB_RUN_ID:-unknown}"
local SOURCE_URL="${GITEA_API_URL}/${SOURCE_REPO}/commit/${SOURCE_COMMIT}"
ROOT_REPO="${GITOPS_REPO}" ROOT_COMMIT="${GITOPS_SHA}" \
GITEA_API_URL="${GITEA_API_URL}" GITEA_TOKEN="${GITEA_TOKEN}" \
bash "${SCRIPT_DIR}/report-status.sh" success \
"Install to ${env} ${VERSION}" \
"${context}" "" "${SOURCE_URL}"
}
_gitops_nochange() {
local env repo context
env=$(dirname "${INPUT_FILE}")
repo=$(basename "${SOURCE_REPO}")
context="${repo} ${GITHUB_RUN_ID:-unknown}"
[ -n "${GIT_TAG_PREFIX:-}" ] && context="${repo}/${GIT_TAG_PREFIX} ${GITHUB_RUN_ID:-unknown}"
local SOURCE_URL="${GITEA_API_URL}/${SOURCE_REPO}/commit/${SOURCE_COMMIT}"
ROOT_REPO="${GITOPS_REPO}" ROOT_COMMIT="${GITOPS_SHA}" \
GITEA_API_URL="${GITEA_API_URL}" GITEA_TOKEN="${GITEA_TOKEN}" \
bash "${SCRIPT_DIR}/report-status.sh" success \
"Install to ${env} ${VERSION} — no change" \
"${context}" "" "${SOURCE_URL}"
}
_gitops_substitute() {
echo "$1" | sed "s/{{VERSION}}/$2/g"
}
_gitops_update() {
local CLONE_DIR="${GITOPS_TARGET_DIR:-$(mktemp -d)}"
if [ -n "${GITOPS_CLONE_URL:-}" ]; then
git clone "${GITOPS_CLONE_URL}" "${CLONE_DIR}" || _gitops_fail "Failed to clone GitOps repo"
else
git clone "${CLONE_URL}" "${CLONE_DIR}" || _gitops_fail "Failed to clone GitOps repo"
fi
cd "${CLONE_DIR}" || _gitops_fail "Failed to enter clone directory"
yq eval -i "${YQ_EXPR}" "${INPUT_FILE}" || _gitops_fail "Failed to update ${INPUT_FILE}"
git add "${INPUT_FILE}" || _gitops_fail "Failed to stage ${INPUT_FILE}"
if git diff --cached --quiet; then
echo "No changes — ${INPUT_FILE} already at ${VERSION}"
GITOPS_SHA="$(git rev-parse HEAD)"
_gitops_nochange
exit 0
fi
git -c user.name="gitea-ci-bot" \
-c user.email="ci@keskikuja.site" \
commit -m "[skip ci] gitops: update version to ${VERSION}" || _gitops_fail "Failed to commit"
GITOPS_SHA="$(git rev-parse HEAD)"
git push || _gitops_fail "Failed to push"
_gitops_success
}
_gitops_validate
YQ_EXPR=$(_gitops_substitute "${YQ_TPL}" "${VERSION}")
GITEA_HOST=$(echo "${GITEA_API_URL}" | sed 's|https://||' | sed 's|http://||')
CLONE_URL="${GITOPS_CLONE_URL:-https://${GITEA_TOKEN}@${GITEA_HOST}/${GITOPS_REPO}.git}"
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
_gitops_update
fi
+5 -58
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env sh #!/usr/bin/env bash
set -eu set -euo pipefail
SUITE_PATH="${1:-}" SUITE_PATH="${1:-}"
@@ -12,7 +12,7 @@ SUITE_PATH="${1:-}"
OWNER="${GITHUB_REPOSITORY%%/*}" OWNER="${GITHUB_REPOSITORY%%/*}"
REPO="${GITHUB_REPOSITORY##*/}" REPO="${GITHUB_REPOSITORY##*/}"
SHA8=$(echo "$GITHUB_SHA" | cut -c1-8) SHA8="${GITHUB_SHA:0:8}"
PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}" PAGES_USER="${GIT_PAGES_PUBLISH_USER:-publish}"
REPORT_DIR="reports/${SHA8}/${SUITE_PATH%/}" REPORT_DIR="reports/${SHA8}/${SUITE_PATH%/}"
REPORT_BASE="${GIT_PAGES_URL}/${OWNER}/${REPO}/reports/${SHA8}" REPORT_BASE="${GIT_PAGES_URL}/${OWNER}/${REPO}/reports/${SHA8}"
@@ -33,66 +33,13 @@ else
fi fi
mkdir -p "$TARGET" mkdir -p "$TARGET"
cp -a "$REPORT_DIR/." "$TARGET/" cp -a "$REPORT_DIR/." "$TARGET/"
cat > "$WORK/${OWNER}/${REPO}/reports/${SHA8}/.meta" <<EOF
if [ ! -f "$TARGET/index.html" ]; then
ITEM_LIST=""
ITEM_COUNT=0
for f in "$TARGET"/*; do
[ -f "$f" ] || continue
base=$(basename "$f")
[ "$base" = "index.html" ] && continue
ITEM_LIST="${ITEM_LIST}file:${base}
"
ITEM_COUNT=$((ITEM_COUNT + 1))
done
for d in "$TARGET"/*/; do
[ -d "$d" ] || continue
base=$(basename "$d")
[ -f "$d/index.html" ] || continue
ITEM_LIST="${ITEM_LIST}dir:${base}
"
ITEM_COUNT=$((ITEM_COUNT + 1))
done
if [ "$ITEM_COUNT" -gt 1 ]; then
{
echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
echo "<title>Test report ${SHA8}</title>"
echo '<style>body{font-family:sans-serif;margin:2em;max-width:960px}'
echo 'h1{color:#1e293b}ul{list-style:none;padding:0}'
echo 'li{margin:.5em 0;padding:.5em;background:#f8fafc;border-radius:6px}'
echo 'a{color:#2563eb;text-decoration:none}a:hover{text-decoration:underline}'
echo '</style></head><body>'
echo "<h1>Test report <code>${SHA8}</code></h1><ul>"
echo "$ITEM_LIST" | while IFS= read -r item; do
[ -z "$item" ] && continue
item_type=$(echo "$item" | cut -d: -f1)
item_name=$(echo "$item" | cut -d: -f2-)
label=$(echo "$item_name" | sed -e 's/\.[^.]*$//' -e 's/[-_]/ /g')
first=$(echo "$label" | cut -c1 | tr '[:lower:]' '[:upper:]')
rest=$(echo "$label" | cut -c2-)
if [ "$item_type" = "file" ]; then
echo "<li><a href=\"$item_name\">${first}${rest}</a></li>"
else
echo "<li><a href=\"$item_name/index.html\">${first}${rest}</a></li>"
fi
done
echo '</ul></body></html>'
} > "$TARGET/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)"} {"branch":"${GITHUB_REF_NAME:-}","sha":"${GITHUB_SHA}","published_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF EOF
find "$WORK/$OWNER" \( -type f -o -type l \) -print | sed "s|^${WORK}/||" | tar -cf "$TAR" -C "$WORK" -T - find "$WORK/$OWNER" \( -type f -o -type l \) -print | sed "s|^${WORK}/||" | tar -cf "$TAR" -C "$WORK" -T -
publish() { publish() {
method="$1" local method="$1"
curl -sS -X "$method" "$PUBLISH_SITE_URL" \ curl -sS -X "$method" "$PUBLISH_SITE_URL" \
-u "${PAGES_USER}:${GIT_PAGES_PUBLISH_TOKEN}" \ -u "${PAGES_USER}:${GIT_PAGES_PUBLISH_TOKEN}" \
-H "Content-Type: application/x-tar" \ -H "Content-Type: application/x-tar" \
+6 -6
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env sh #!/usr/bin/env bash
set -eu set -euo pipefail
# https://docs.gitea.com/api/next/#tag/repository/operation/repoCreateStatus
STATE="${1:-}" STATE="${1:-}"
DESCRIPTION="${2:-}" DESCRIPTION="${2:-}"
SHA8=$(echo "${GITHUB_SHA:-}" | cut -c1-8) KEY="${3:-commit-${GITHUB_SHA:0:8}}"
KEY="${3:-commit-${SHA8}}"
SUITE="${4:-}" SUITE="${4:-}"
CUSTOM_URL="${5:-}" CUSTOM_URL="${5:-}"
@@ -17,8 +18,7 @@ if [ -n "$CUSTOM_URL" ]; then
URL="$CUSTOM_URL" URL="$CUSTOM_URL"
elif [ -n "$SUITE" ]; then elif [ -n "$SUITE" ]; then
SUITE="${SUITE%/}/" SUITE="${SUITE%/}/"
SHA8_CUT=$(echo "$GITHUB_SHA" | cut -c1-8) URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/${SUITE}"
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8_CUT}/${SUITE}"
else else
URL="${GITEA_API_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" URL="${GITEA_API_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
fi fi
-227
View File
@@ -1,227 +0,0 @@
---
name: ci-container-build
description: |
Creating or modifying CI container build workflows. Activates when consumer
needs to build a custom CI container image (multi-tool image for tests,
linting, validation) that is pushed to a container registry for use in
other pipeline jobs.
activation-gate: |
User mentions CI container, custom CI image, ci-container-build, Dockerfile
for CI tools, multi-tool container, or needs to build/push a container that
other CI jobs will use as their runtime environment.
category: ci
impact: high
---
# CI Container Build — Template
Template jolla consumer luo oman CI-kontin build-workflown.
Kontti sisältää useamman työkalun yhdistelmän (esim. helm + kubeconform + xsltproc),
jota muut jobit käyttävät ajonaikaisena ympäristönä.
## Rakenne
Vain `workflow_dispatch`**ei automaattista buildausta koskaan**. Kontti buildataan
manuaalisesti Gitea Actions UI:sta. Tiedoston ilmestyessä main-haaraan workflow näkyy
välittömästi Actions-tabissa ajettavana.
Build-prosessi lataa ensin `config-provider.yml`:llä `DOCKER_REGISTRY`:n
conf-tiedostosta, sitten buildaa ja puskaa kontin.
Kun kontti on pushattu registryyn, se on muiden pipeline-jobien käytettävissä
`latest`-tägillä — rebuild = käyttöönotto. Mitään versioviittauksia ei tarvitse
päivittää.
## Offline-periaate (DoD)
CI-kontin **Definition of Done**:
> Kontti ei lataa mitään pipeline-vaiheessa (`workflow run` -stepit) eikä kontin
> runtime-prosessissa (`container:` / `docker run`). Kaikki riippuvuudet
> (kielikohtaiset paketit, työkalut, binäärit) on joko:
> - Pre-cachattu kontin **build-vaiheessa** Dockerfilessä, TAI
> - Kopioitu multi-stage buildilla toisesta imagesta (`COPY --from`)
>
> Ainoa sallittu lataushetki on `docker build`. Sen jälkeen kontti toimii
> ilman verkkoyhteyttä.
**Miksi:** Toistettavuus, air gap -yhteensopivuus, nopeus. Pipeline ei saa
epäonnistua sen takia että ulkoinen registry on alhaalla tai että `go mod download`
joutuu latamaan 100 modulia jokaisella testiajolla.
**Kielikohtaiset pre-cachet:** Jos kontissa ajetaan kielikohtaista testiä
(Go, Java, Node, Python, ...), kaikki kielikohtaiset riippuvuudet on
pre-cachattava Dockerfilessä build-vaiheessa:
- Go: `COPY go.mod go.sum ./``RUN go mod download`
- Java/Maven: `COPY pom.xml ./``RUN mvn dependency:go-offline`
- Node: `COPY package.json package-lock.json ./``RUN npm ci --omit=dev`
- Python: `COPY requirements.txt ./``RUN pip wheel --wheel-dir=/wheels -r requirements.txt``COPY --from` käyttöön
## Nimeäminen
CI-kontin build-workflow noudattaa samaa nimeämiskonventiota kuin muutkin
tiedostot `.gitea/workflows/`-kansiossa:
```
<komponentti>.ci-feature.yml ← feature-haaran reititin
<komponentti>.ci-main.yml ← main-haaran reititin
<komponentti>.<testityyppi>.yml ← yksittäinen testi tai operaatio
<komponentti>.ci-container-build-<kontti>.yml ← CI-kontin build-workflow
<komponentti>.gitea-env.conf ← komponenttikohtainen konfiguraatio
```
Single repossa `<komponentti>` jätetään pois — tiedostot ovat suoraan `ci-feature.yml`,
`ci-main.yml`, `<testityyppi>.yml`, `ci-container-build-<kontti>.yml`.
Monorepossa prefiksi pitää komponentin tiedostot yhdessä: `ls <komponentti>.*` löytää kaikki
kerralla. **Olemassaolevia prefiksejä ei saa poistaa.**
Esimerkkejä:
```
.gitea/workflows/chart.ci-container-build-helm.yml
.gitea/workflows/api.ci-container-build-node.yml
.gitea/workflows/ci-container-build-bats.yml ← single repo
```
## Template
> **Korvaa kaikki `__SUURAAKKOSET__`-placeholderit projektin todellisilla arvoilla.**
> Ainoastaan `${{ ... }}`-syntaksilla merkityt Gitea Actions -muuttujat ovat ajonaikaisia
> eikä niitä korvata.
Luo `.gitea/workflows/__KOMPONENTTI__.ci-container-build-__KONTTI__.yml`
(single repo: `ci-container-build-__KONTTI__.yml`):
```yaml
name: CI Container Build & Push
on:
workflow_dispatch:
inputs:
config_path:
required: true
type: string
description: 'Polku .gitea-env.conf-tiedostoon (esim. .gitea/workflows/chart.gitea-env.conf)'
dockerfile_path:
required: true
type: string
description: 'Polku Dockerfileen (esim. ci-helm.Dockerfile)'
image_name:
required: true
type: string
description: 'Kontin nimi ilman registry-polkua (esim. ci-helm)'
tag:
required: true
type: string
default: 'latest'
description: 'Image-tägi'
jobs:
load-config:
uses: __OWNER__/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
uses: __OWNER__/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
dockerfile_path: ${{ inputs.dockerfile_path }}
image_name: ${{ inputs.image_name }}
tag: ${{ inputs.tag }}
```
### Käyttö
**Gitea Actions UI:sta** (heti kun tiedosto on main-haarassa):
```
Gitea → Actions → CI Container Build & Push → Run workflow
config_path: .gitea/workflows/__KOMPONENTTI__.gitea-env.conf
dockerfile_path: ci-__TYÖKALU__.Dockerfile
image_name: ci-__TYÖKALU__
tag: latest
```
### Dockerfile
Dockerfile yhdistää tarvitut työkalut yhteen konttiin.
**Kaikki riippuvuudet ladataan build-vaiheessa — kontti on täysin itseriittoinen.**
```dockerfile
# Tapa A: COPY --from toisesta imagesta
FROM __BASE_IMAGE__:__VERSION__
COPY --from=__SOURCE_IMAGE__:__VERSION__ /path/to/binary /usr/local/bin/
RUN apk add --no-cache __PAKETIT__
# Tapa B: Build-vaiheen curl-lataus
FROM __BASE_IMAGE__:__VERSION__
RUN apk add --no-cache curl __PAKETIT__ && \
curl -fsSL __URL__/__BINARY__.tar.gz | tar xz -C /usr/local/bin && \
apk del curl
# Tapa C: Multi-stage + kielikohtainen pre-cache
FROM __BASE_IMAGE__:__VERSION__ AS deps
COPY go.mod go.sum ./
RUN go mod download
FROM deps AS build
COPY . .
RUN go test -c -o /tmp/test.bin ./...
FROM __BASE_IMAGE__:__VERSION__
COPY --from=deps /go/pkg/mod /go/pkg/mod
COPY --from=build /tmp/test.bin /usr/local/bin/test
```
`COPY --from` on kevyempi (ei curl-asennusta). `curl` (Tapa B) on sallittu
vain build-vaiheessa — `apk del curl` poistaa työkalun ennen runtimea.
Tapa C pre-cacheaa kielikohtaiset riippuvuudet ja tuottaa täysin
offline-runtime-kontin.
## Testaus ennen julkaisua
Konttia ei saa pushata registryyn ennen kuin se on validoitu.
### 1. Aja testit kontin sisällä
Testit on ajettava **kontin sisällä**, ei suoraan lokaalilla koneella.
```bash
# OIKEIN — kontin sisällä
docker build -t ci-tyokalu:test .
docker run --rm -v "$(pwd):/repo" -w /repo ci-tyokalu:test bash -c 'bats tests/'
# VÄÄRIN — lokaalit binäärit vs kontti
bats tests/ # eri bash/työkalut kuin kontissa
bashcov -- bats tests/ # eri ruby-versio kuin kontissa
```
Lokaali ympäristö (macOS, eri kirjastoversiot) poikkeaa aina kontista.
Testi voi mennä läpi lokaalissa mutta failata CI:ssä, tai päinvastoin.
### 2. Fragile-testien seulonta (10x ajo)
Aja koko testipaketti **10 kertaa peräkkäin** kontin sisällä ennen pushausta:
```bash
for i in $(seq 1 10); do
echo "=== RUN $i ==="
docker run --rm -v "$(pwd):/repo" -w /repo ci-tyokalu:test \
bash -c 'bats tests/' || exit 1
done
```
Jos yksikin ajo failaa, kontissa on fragile testi — korjaa ennen pushausta.
Fragile testit syövät devaukseen käytettyä aikaa turhilla uusinta-ajoilla.
## Mitä EI kannata tehdä
- Älä lisää `workflow_call`-triggariä — CI-konttia ei koskaan buildata automaattisesti
- Älä poista `<komponentti>.`-prefiksiä olemassaolevista tiedostoista — ne kuuluvat monorepo-nimeämiskonventioon
- Älä sisällytä CI-konttiin mitään sovelluskoodia — vain työkalut
- Älä koskaan lataa mitään pipeline- tai runtime-vaiheessa — kaikki lataukset kuuluvat `docker build` -vaiheeseen (Offline-periaate)
- Älä jätä kielikohtaisia riippuvuuksia pre-cachaamatta — `go mod download`, `npm install`, `mvn dependency:go-offline` jne. ajetaan Dockerfilessä, ei pipelinessä
-636
View File
@@ -1,636 +0,0 @@
# Consumer Pipelines — Reference
Mallipohjat, esimerkit ja konfiguraatiot. Katso säännöt `SKILL.md`:stä.
## Pre-cache-esimerkit (Offline Container)
Alla Dockerfile-esimerkit kielikohtaisista pre-cacheista. Kaikki ajetaan
build-vaiheessa — kontti on täysin itseriittoinen eikä lataa mitään
pipeline- tai runtime-vaiheessa.
### Go
```dockerfile
FROM golang:1.24-alpine AS deps
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
FROM deps AS test-build
COPY . .
RUN go test -c -o /tmp/test.bin ./...
FROM alpine:3.21
RUN apk add --no-cache git nodejs
COPY --from=deps /go/pkg/mod /go/pkg/mod
COPY --from=test-build /tmp/test.bin /usr/local/bin/test
```
### Node.js
```dockerfile
FROM node:22-alpine AS deps
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:22-alpine
RUN apk add --no-cache git
COPY --from=deps /build/node_modules /app/node_modules
COPY . /app
WORKDIR /app
```
### Java / Maven
```dockerfile
FROM maven:3.9-eclipse-temurin-21 AS deps
WORKDIR /build
COPY pom.xml ./
RUN mvn dependency:go-offline -B
FROM maven:3.9-eclipse-temurin-21 AS build
COPY --from=deps /root/.m2 /root/.m2
COPY . .
RUN mvn package -B -DskipTests
FROM eclipse-temurin:21-jre
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
COPY --from=build /build/target/*.jar /app/app.jar
WORKDIR /app
```
### Python
```dockerfile
FROM python:3.12-alpine AS deps
WORKDIR /build
COPY requirements.txt ./
RUN pip wheel --wheel-dir=/wheels -r requirements.txt
FROM python:3.12-alpine
RUN apk add --no-cache git
COPY --from=deps /build/wheels /wheels
COPY --from=deps /build/requirements.txt /
RUN pip install --no-index --find-links=/wheels -r /requirements.txt && rm -rf /wheels
COPY . /app
WORKDIR /app
```
### Helm + Node.js (korvaa helm-build-push.yml:n runtime-apk)
```dockerfile
FROM alpine/helm:3.16.0 AS helm-bin
FROM node:22-alpine
RUN apk add --no-cache git
COPY --from=helm-bin /usr/bin/helm /usr/local/bin/helm
```
Tämä kontti korvaa `helm-build-push.yml`:n `alpine/helm:3.19.0`-image-riippuvuuden
ja poistaa tarpeen asentaa node.js runtime-vaiheessa.
## Reititin — täydellinen esimerkki
```yaml
jobs:
load-config:
uses: <owner>/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
<test-1>:
needs: [load-config]
uses: ./.gitea/workflows/<component>.<test-1>.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
<test-2>:
needs: [load-config]
uses: ./.gitea/workflows/<component>.<test-2>.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
report-summary:
needs: [load-config, <test-1>, <test-2>]
if: always()
uses: <owner>/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: <suite-1> <suite-2>
```
## CI-kontin build — parametroitu workflow
CI-kontin build on `workflow_dispatch`-triggeröity job, joka näkyy Gitea Actionsissa kuten Jenkinsin
parametroitu job — käyttäjä antaa inputit UI:sta ennen ajoa.
```yaml
name: CI Container Build <työkalu>
on:
workflow_dispatch:
inputs:
config_path:
required: true
type: string
default: '.gitea/workflows/<komponentti>.gitea-env.conf'
description: 'Polku .gitea-env.conf-tiedostoon'
dockerfile_path:
required: true
type: string
default: '<komponentti>/Dockerfile.ci-<työkalu>'
description: 'Polku Dockerfileen'
image_name:
required: true
type: string
default: 'ci-<työkalu>'
description: 'Kontin nimi ilman registry-polkua'
tag:
required: true
type: string
default: 'latest'
description: 'Image-tägi'
jobs:
load-config:
uses: <owner>/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: ${{ inputs.config_path }}
build-push:
needs: [load-config]
uses: <owner>/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
dockerfile_path: ${{ inputs.dockerfile_path }}
image_name: ${{ inputs.image_name }}
tag: ${{ inputs.tag }}
```
### CI-kontin ajaminen testijobissa
**Ainoa sallittu tapa** consumer-puolella on `container:`-direktiivi. `docker run` komennolla
kontin käynnistäminen stepin sisällä on anti-pattern. `container:`-direktiivillä kaikki stepit
ajetaan samassa kontissa — tiedostot ovat suoraan filesystemillä eikä erillistä
volyyminhallintaa tarvita.
```yaml
jobs:
<työkalu>:
runs-on: ubuntu-latest
container:
image: ${{ inputs.<image-name> }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: <owner>/gitea-ci-library
path: .ci
- name: Run <työkalu>
shell: bash
run: |
mkdir -p "reports/<suite>"
<komento> > "reports/<suite>/results.txt" 2>&1
- name: Post-process reports
if: always()
run: |
<mahdollinen_raporttien_jälkikäsittely>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
```
Monorepossa context ja description sisältävät komponentin nimen:
```yaml
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
```
**Usean runnerin cache-ongelma:** Jos eri kerroilla käynnistyy eri runnereita,
niillä voi olla eri versio `latest`-imagen digesteistä. Ratkaisuja:
- Rebuildaa kontti ja aja `docker pull <image>` manuaalisesti kaikilla runnereilla
- Käytä versioitua tagia (`v2`, `v3`, ...) ja päivitä workflow'n default buildauksen jälkeen
**Mallit:**
- `example-cucumber-tests.yml` — ei post-processia
- `example-bats-tests.yml` — post-process coverage + report
## Raporttitasot — tarkat YAML-mallit
### Taso 1: Ei jälkikäsittelyä
Single repo:
```yaml
- name: Run tests
shell: bash
run: |
mkdir -p "reports/<suite>"
<testikomento>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
```
Monorepo:
```yaml
- name: Run tests
shell: bash
run: |
mkdir -p "reports/<suite>"
<testikomento>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
```
### Taso 2: Jälkikäsittely tarvitaan
Single repo:
```yaml
- name: Run tests
shell: bash
run: |
mkdir -p "reports/<suite>"
<testikomento> > "reports/<suite>/results.txt" 2>&1
- name: Post-process coverage
if: always()
run: <siirrä coverage-data reports/<suite>/coverage/-hakemistoon>
- name: Post-process test report
if: always()
run: <HTML-generointi raa'asta outputista>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
```
Monorepo:
```yaml
- name: Run tests
shell: bash
run: |
mkdir -p "reports/<suite>"
<testikomento> > "reports/<suite>/results.txt" 2>&1
- name: Post-process coverage
if: always()
run: <siirrä coverage-data reports/<suite>/coverage/-hakemistoon>
- name: Post-process test report
if: always()
run: <HTML-generointi raa'asta outputista>
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
```
### Väärin vs oikein — yksi asia per step
```yaml
# VÄÄRIN — helm template fail → kubeconform jää ajamatta, report jää tekemättä
- name: Run tests
run: |
helm template ... > /tmp/manifests.yaml
kubeconform ... > results.txt 2>&1
# OIKEIN — erilliset stepit
- name: Helm template
run: helm template platform-helm/ -f values.yaml > /tmp/manifests.yaml 2>&1
- name: Kubeconform
if: success()
run: |
mkdir -p reports/kubeconform
kubeconform ... > reports/kubeconform/results.txt 2>&1
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "Helm kubeconform" helm-test kubeconform ${{ job.status }}
```
Monorepossa:
```yaml
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: Helm kubeconform" <komponentti>.helm-test kubeconform ${{ job.status }}
```
### Väärin vs oikein — post-process
```yaml
# VÄÄRIN — jos coverage epäonnistuu, report jää generoimatta
- name: Post-process reports
run: |
bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
bash .ci/.gitea/scripts/bats-report.sh reports/bats
# OIKEIN — erilliset stepit if: always()
- name: Post-process coverage
if: always()
run: bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
- name: Post-process test report
if: always()
run: bash .ci/.gitea/scripts/bats-report.sh reports/bats
```
## Raportin julkaisukelpoisuus
`ci-report.sh` päättää onko raportti julkaisukelpoinen skannaamalla `reports/<suite>/`-hakemistoa.
### Mitä skannataan
| Mitä | Sääntö |
|---|---|
| **Tiedostot (FILES)** | Kaikki `reports/<suite>/`-juuressa olevat tiedostot paitsi `index.html` |
| **Alihakemistot (SUBDIRS)** | Vain ne, joissa on `index.html` |
### Julkaisukelpoisuus
| Tila | Seuraus |
|---|---|
| `FILES + SUBDIRS = 0` | **Failure**`ci-report.sh` palauttaa virheen, raporttia ei julkaista |
| `FILES + SUBDIRS = 1` | Suora linkki itemiin — ei generoi index-sivua |
| `FILES + SUBDIRS > 1` | Generoi `reports/<suite>/index.html`-sivun, linkit kaikkiin itemeihin |
### Hakemistorakenne
```
reports/<suite>/
├── results.txt ← testin stdout (skannataan FILES)
├── test-report.html ← generoitu HTML (skannataan FILES)
└── <mikä tahansa>/ ← alihakemisto (skannataan SUBDIRS)
└── index.html ← VAIN jos tämä on olemassa
```
### Esimerkki: coverage-näkymä
```
reports/<suite>/coverage/index.html ← on olemassa
```
Coverage-dataa ei siirretä automaattisesti. Testin tai post-process-stepin pitää
siirtää coverage `reports/<suite>/coverage/`-hakemistoon ja varmistaa että `index.html` on mukana.
**Provider vastuulla:** `ci-report.sh` (provider-skripti) hoitaa sekä hakemistorakenteen
skannauksen, `index.html`-generoinnin että julkaisun git-pagesiin. Consumer tuottaa
vain raakatiedostot `reports/<suite>/`-hakemistoon — `ci-report.sh` päättää
julkaisukelpoisuuden ja generoi tarvittavan navigaation.
## Debug-ohje: raportti ei näy
### 1. Aja lokaalisti samalla komennolla kuin CI
```bash
mkdir -p reports/bats
bashcov -- bats tests/ > reports/bats/results.txt 2>&1
echo "exit: $?"
ls -la reports/bats/
```
### 2. Lisää `echo "DEBUG: ..." >&2` ennen ja jälkeen kriittisen operaation
```bash
echo "DEBUG: coverage exists? $([ -d coverage ] && echo YES || echo NO)" >&2
echo "DEBUG: target/index.html exists? $([ -f reports/suite/coverage/index.html ] && echo YES || echo NO)" >&2
```
### 3. Tarkista kutsuparametrit
Yleisin virhe: skripti odottaa `$1` = X, mutta kutsuja antaa `$1` = Y ja `$2` = X.
### 4. Tarkista tiedostopolut
1. Onko lähdetiedosto olemassa ennen kopiointia?
2. Onko kohde olemassa kopioinnin jälkeen?
3. Onko `index.html` subdirissä (vaaditaan `ci-report.sh`:lle)?
### 5. Poista debug-echot kun ongelma on korjattu
### 6. Älä kokeile — debuggaa
Kokeilu = arvaus. Debuggaus = lisää echo, aja, lue logi, eristä ongelma. Vasta sitten korjaa.
## Konfiguraatiotiedosto (.gitea-env.conf)
Tiedosto on `key=value`-muotoinen (kuten `.env`). Kommentit ja tyhjät rivit sallittuja.
### Single repo
```ini
# .gitea/workflows/gitea-env.conf
GITEA_API_URL=https://gitea.example.com
GIT_PAGES_URL=https://reports.example.com
```
### Docker-artifaktin buildaavat projektit
```ini
DOCKER_REGISTRY=gitea.example.com/myorg
DOCKER_IMAGE_NAME=my-service
DOCKER_UI_URL=https://gitea.example.com/myorg/-/packages/container
#DOCKERFILE=Dockerfile.platform # valinnainen, oletus Dockerfile
```
`DOCKER_UI_URL` ei sisällä image-nimeä — se on puhdas container-registryn osoite.
Image-nimi lisätään automaattisesti URL:iin `docker-build-push.yml`:ssä.
### Helm-artifaktin buildaavat projektit
```ini
HELM_REGISTRY=gitea.example.com/myorg
GIT_TAG_PREFIX=git-pages/
VERSION_FILE=git-pages/Chart.yaml
```
| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `HELM_REGISTRY` | **kyllä** | Registry host + owner, esim. `gitea.example.com/myorg`. **Tyhjä pysäyttää workflow'n.** |
| `GIT_TAG_PREFIX` | ei | Etuliite git-tägille. Pakollinen monorepossa. |
| `VERSION_FILE` | ei | Polku version lähteeseen. Oletus: juuren `Chart.yaml`. |
### Salaisuudet (Gitea Settings → Secrets)
| Secret | Pakollinen |
|---|---|
| `GITEA_TOKEN` | Aina (Gitean sisäinen, automaattisesti saatavilla) |
| `GIT_PAGES_PUBLISH_TOKEN` | Aina |
| `DOCKER_USERNAME` | Vain jos buildaat kontteja |
| `DOCKER_PASSWORD` | Vain jos buildaat kontteja |
| `HELM_USER` | Vain jos pushaat Helm chartin OCI-rekisteriin (oletus `github.actor`) |
| `HELM_PASSWORD` | Vain jos pushaat Helm chartin OCI-rekisteriin |
## Monorepo
Monorepossa yhdessä repossa asuu useampi julkaistava komponentti. Jokaiselle komponentille
oma conf-tiedosto `.gitea/workflows/<komponentti>.gitea-env.conf`.
### Suositus: komponentit omiin juurihakemistoihin
On suositeltavaa sijoittaa jokaisen komponentin koko lähdekoodi omaan juuritason
hakemistoonsa (`api/`, `frontend/`, `shared/`). Tämä helpottaa `paths:`-filtteröintiä,
pitää komponentit selkeästi erillään, ja tekee repossa navigoinnista suoraviivaista.
### Ongelmat ja ratkaisut
| Ongelma | Ratkaisu |
|---|---|
| Monta komponenttia, yksi repo — mikä triggeröi? | `paths:`-filtteri: komponentin hakemisto + sen CI-workflow't ja conf-tiedosto |
| Jokaisella komponentilla oma versio | `VERSION_FILE=<komponentti>/package.json` confissa |
| Git-tägit sekaisin ellei nimiavaruutta | `GIT_TAG_PREFIX=<komponentti>/` confissa → tägi `<komponentti>/1.2.3` |
| Eri julkaisutahdit | Riippumattomat CI-triggerit, omat versiopolut |
### Komponenttikohtainen conf
```ini
# .gitea/workflows/<komponentti>.gitea-env.conf
GITEA_API_URL=https://gitea.example.com
GIT_PAGES_URL=https://reports.example.com
DOCKER_REGISTRY=gitea.example.com/myorg
DOCKER_IMAGE_NAME=<image-nimi>
DOCKER_UI_URL=https://gitea.example.com/myorg/-/packages/container
GIT_TAG_PREFIX=<komponentti>/
# Jompikumpi — JSON (.version-kenttä) tai plain text:
VERSION_FILE=<komponentti>/package.json
#VERSION_FILE=<komponentti>/VERSION
```
### Monorepo reititin
```yaml
name: CI <Komponentti> Main
on:
push:
branches:
- main
paths:
- <komponentti>/**
- .gitea/workflows/<komponentti>.*
jobs:
load-config:
uses: <owner>/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: .gitea/workflows/<komponentti>.gitea-env.conf
check-version:
needs: [load-config]
uses: <owner>/gitea-ci-library/.gitea/workflows/check-version.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
<testit>:
needs: [load-config, check-version]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: ./.gitea/workflows/<komponentti>.<testi>.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
build-push:
needs: [load-config, check-version, <testit>]
if: needs.check-version.outputs.artifact_exists != 'true'
uses: <owner>/gitea-ci-library/.gitea/workflows/docker-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
report-summary:
name: Report Summary
needs: [load-config, build-push]
if: always()
uses: <owner>/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: '<suite-1> <suite-2>'
```
**Commit status -kontekstit monorepossa:** Testiraporttien `ci-report.sh`-kutsussa
context ja description sisältävät komponentin nimen:
```yaml
- name: Report
if: always()
run: |
bash .ci/scripts/ci-report.sh "<Komponentti>: Unit test report" <komponentti>.unit-tests bats ${{ job.status }}
```
### Version elinkaari per komponentti
`GIT_TAG_PREFIX` takaa että eri komponenttien versiohistoria pysyy erillään.
Git-tägi `<komponentti>/0.2.3` ei sekoitu toisen komponentin tägeihin.
`check-version.yml` suodattaa ja laskee seuraavan patchin vain kyseisen
komponentin etuliitteellä. Idempotenttius toimii komponenttikohtaisesti:
jos commitilla on jo tägi, pipeline skipataan `if: artifact_exists != 'true'`.
### Mitä EI kannata tehdä monorepossa
- Älä aja kaikkia komponentteja samasta triggeristä — `paths:` pitää CI:t erillisinä
- Älä käytä samaa versionhallintatiedostoa usealle komponentille
- Älä anna monorepo-parametreja pipeline-overrideina — kaikki kuuluu conf-tiedostoon
- Älä rajaa `paths:` pelkkään komponentin hakemistoon — CI ei triggeröidy workflow- tai conf-muutoksista
## Versionhallinta
`check-version.yml` lukee version automaattisesti prioriteettijärjestyksessä:
| # | Lähde | Formaatti |
|---|---|---|
| 1 | `VERSION_FILE` confissa | Määritelty polku |
| 2 | `VERSION`-tiedosto (root) | Plain text |
| 3 | `package.json` (root) | `.version`-kenttä |
| 4 | `pom.xml` (root) | `<version>`-elementti |
`major.minor` otetaan tästä. Patch lasketaan automaattisesti git-tageista.
Esim. `VERSION` = `0.2`, tagit = `0.2.0`, `0.2.1` → seuraava `0.2.2`.
## Branch protection (PR-gate)
Gitean Settings → Branches → Add Rule:
- **Branch:** `main`
- **Enable Require Status Checks:** päälle
- **Status checks:** valitse testijobien nimet
## Provider-rajapinnat — referenssi
### Workflowt
| Workflow | Käyttötarkoitus |
|---|---|
| `config-provider.yml` | Lataa + validoi `.conf`, tuottaa `env_json` |
| `check-version.yml` | Tarkistaa onko commit buildattu, laskee version |
| `docker-build-push.yml` | Buildaa + puskea Docker-imagen, tagittaa commitin |
| `helm-build-push.yml` | Paketoi + puskea Helm chartin OCI-rekisteriin, tagittaa commitin |
| `report-summary.yml` | `GITHUB_STEP_SUMMARY`-taulukko raporttilinkeillä (Gitea 1.27+) |
### Skriptit (kutsutaan `.ci/scripts/`-polun kautta)
| Skripti | Käyttötarkoitus |
|---|---|
| `ci-report.sh` | Yhdistetty raportointi: julkaisee git-pagesiin ja asettaa commit-statuksen. Korvaa erilliset `publish-git-pages.sh` + `report-status.sh` -kutsut. Käyttö: `bash .ci/scripts/ci-report.sh "<kuvaus>" <context> <suite> ${{ job.status }}` |
| `report-status.sh` | POSTaa commit-statuksen linkillä (kutsutaan `ci-report.sh`:n sisältä) |
| `publish-git-pages.sh` | Julkaisee raporttihakemiston git-pagesiin (kutsutaan `ci-report.sh`:n sisältä) |
| `ci-validate.sh` | Validoi `.conf`-tiedoston (kutsutaan `config-provider.yml`:stä) |
-544
View File
@@ -1,544 +0,0 @@
---
name: consumer-pipelines
description: |
Creating or modifying consumer CI pipelines, .gitea/workflows/ files,
reusable test workflows, monorepo CI configuration, or CI routing files
(ci-feature.yml, ci-main.yml, ci-*.yml). Activates when the user asks to
build, fix, or change consumer-side Gitea Actions pipelines that use
gitea-ci-library providers.
activation-gate: |
User mentions consumer pipelines, ci-feature.yml, ci-main.yml, test
workflows, .gitea/workflows/ files, monorepo CI, routing files, or asks
to create/modify CI pipelines on top of gitea-ci-library.
category: ci
impact: high
---
# Consumer Pipelines — Pipeline Standards
Säännöt joilla consumer-projektit rakentavat CI-pipelinejä `gitea-ci-library`-kirjaston päälle.
Nämä eivät ole provider-kirjaston sääntöjä — ne kuvaavat miten consumerin kuuluu käyttää kirjastoa oikein.
Katso tarkat mallipohjat ja esimerkit `REFERENCE.md`:stä.
## 1. Reitittimen puhtaus
Reitittimet (`ci-feature.yml`, `ci-main.yml`) eivät sisällä `run:`-steppejä. Ne koostuvat vain:
```yaml
uses:
needs:
if:
secrets: inherit
with:
env_json:
<parametrit>:
```
Jokainen job vastaa yhtä loogista testiä tai operaatiota. Reititin on orkestraattori — kaikki suorittava
logiikka on omassa `workflow_call`-tiedostossaan.
Katso täydellinen esimerkki `REFERENCE.md`:stä.
## 2. Yksi asia per tiedosto
Ei monoliittista `ci-tests.yml`. Jokainen testityyppi tai operaatio on oma `workflow_call`-tiedostonsa.
**Miksi:**
- Testit ajetaan rinnakkain (ei keinotekoisia riippuvuuksia)
- Yhden testin fail ei estä muita
- Testattavissa itsenäisesti `workflow_dispatch`:llä
- Diff näyttää heti mitä testiä muutettiin
## 3. Exit-koodin käsittely
`set -e` on oletuksena käytössä Gitea Actions -stepeissä — ensimmäinen feilaava komento pysäyttää stepin
ja exit-koodi välittyy natiivisti. Ylimääräistä `EXIT=$?` + `echo >> GITHUB_ENV` -käärettä ei tarvita.
```yaml
- name: Run tests
shell: bash
run: |
<testikomento> > results.txt 2>&1
```
**Miksi ei pipeä (`| tee`):** `|` syö exit-koodin. Käytä redirectiä `>`.
**Yksi asia per step:** Älä koskaan niputa useaa komentoa samaan `run:`-blockiin. `bash -e` pysäyttää
koko stepin ensimmäisellä failaavalla komennolla, ja loput jäävät ajamatta. Sama pätee post-process-steppeihin.
## 4. Konttipolitiikka
1. **Julkiset registry-kontit kiinteällä versiolla**`alpine/helm:3.19.0`, `node:22`, `maven:3.9-eclipse-temurin-21`.
Toistettavuus ja turvallisuus eivät saa riippua ulkoisesta `latest`:sta.
2. **Projektin omat CI-kontit `latest`-tägillä** — buildattu `ci-container-build-<kontti>.yml`:llä.
Kontin build-pipeline päivittää `latest`:n automaattisesti. Rebuild = käyttöönotto
kaikissa pipelineissa ilman versioviittauksien päivittelyä.
3. **Ei koskaan `curl`-latauksia CI-ajon sisällä** — työkalujen asennus CI-stepeissä hidastaa,
epäluotettavaa, ja vaikeuttaa toistettavuutta.
4. **Konttikuva hallitaan workflow'ssa, ei kutsujassa** — jos workflow vaatii tietyn
konttikuvan, se määritellään oletuksena (`default:`) workflow'n inputissa.
Kutsujan ei tarvitse tietää eikä välittää image-nimeä ellei halua ylikirjoittaa.
CI-kontin build-workflow'n template: `skills/ci-container-build/SKILL.md`.
### 4.1 Offline Container -vaatimus (DoD)
CI-kontin (ja kaikkien pipeline-konttien) on oltava täysin itseriittoisia:
> Kontti ei lataa mitään pipeline-vaiheessa (`workflow run` -stepit) eikä
> kontin runtime-prosessissa (`container:` / `docker run`). Kaikki
> riippuvuudet pre-cachataan `docker build` -vaiheessa.
> Ainoa sallittu lataushetki on `docker build`.
**Esimerkkejä rikkomuksista:**
- `apk add`, `apt-get install`, `npm install`, `go mod download`, `pip install`
pipeline-stepissä
- `curl <url> | tar xz` runtime-vaiheessa
- Node.js-konttikuva ilman nodea (joudutaan asentamaan lennossa)
### 4.2 Kielikohtainen pre-cache
Kun kontissa testataan kielikohtaista koodia, kaikki riippuvuudet on
pre-cachattava Dockerfilessä, ei pipeline-stepissä:
| Kieli | Pre-cache Dockerfilessä |
|---|---|
| Go | `COPY go.mod go.sum ./``RUN go mod download` |
| Java/Maven | `COPY pom.xml ./``RUN mvn dependency:go-offline` |
| Node | `COPY package.json package-lock.json ./``RUN npm ci --omit=dev` |
| Python | `COPY requirements.txt ./``RUN pip install -r requirements.txt` |
Katso tarkat Dockerfile-esimerkit `REFERENCE.md`:stä.
### 4.3 CI-kontin ajaminen jobissa
Ainoa sallittu tapa on `container:`-direktiivi. `docker run` komennolla kontin
käynnistäminen stepin sisällä on anti-pattern.
Katso CI-kontin template `REFERENCE.md`:stä.
**Huomio `actions/checkout@v4`:stä:** `container:`-direktiivillä kaikki stepit
ajetaan kontin *sisällä* — myös `actions/checkout@v4`. Se on JavaScript-action
joka vaatii sekä `nodejs` että `git`. Varmista että CI-kontin Dockerfilessä on
molemmat — muuten checkout ei toimi ja pipeline failaa.
### 4.4 Build-konteksti, `.dockerignore` ja `COPY`
**Build-konteksti** on aina tiedoston (Dockerfile, Chart.yaml) oman hakemiston
juuri (`dirname "${DOCKERFILE}"` / `dirname "${CHART_FILE}"`). Kaikki
suhteelliset polut — ignore-tiedosto, `COPY`, `ADD` — ovat suhteessa tähän
kontekstiin.
| Tiedosto | Konteksti | Ignore-tiedosto | Käyttö |
|---|---|---|---|
| `Dockerfile` | `.` | `./.dockerignore` | `docker build` / `COPY src/ src/` |
| `api/Dockerfile` | `api/` | `api/.dockerignore` | `docker build` / `COPY src/ src/` |
| `Chart.yaml` (`VERSION_FILE`) | `.` | `./.helmignore` | `helm package` |
| `api/Chart.yaml` (`VERSION_FILE`) | `api/` | `api/.helmignore` | `helm package` |
Helm chartin polku luetaan confin `VERSION_FILE`-kentästä — sama rivi jota
`check-version.yml` käyttää version lähteenä. Yksi conf-rivi ohjaa molempia:
sekä versionlaskentaa että chartin sijaintia.
**Mitä ignore-tiedosto sisältää:** Kaikki mikä EI ole konttiin tai chart-pakettiin
tarkoitettua koodia tai resurssia, ON oltava ignore-tiedostossa:
- Git- ja CI-historia (`.git/`, `.gitea/`, `.github/`)
- Testikoodi, testidata, testiraportit (`tests/`, `reports/`, `coverage/`)
- Dokumentaatio (`docs/`, `guides/`, `*.md`, `CHANGELOG`, `README`)
- Editori- ja työkalukonfiguraatio (`.vscode/`, `.cursor/`, `.idea/`, `.DS_Store`)
- Riippuvuudet jotka asennetaan Dockerfilessä (`node_modules/`)
- Väliaikaistiedostot (`tmp/`, `*.log`)
- Projektikohtaiset konfiguraatiot (`.env`, `*.conf`, `CURRENT_PROVIDER_VERSION`)
**Miksi:** Build-kontekstin koko vaikuttaa suoraan `docker build` -nopeuteen.
Raskas konteksti (etenkin `.git/` ja `node_modules/`) hidastaa buildia ja
kuluttaa runnerin resursseja turhaan. Ylimääräiset tiedostot kontissa ovat
**tietoturvariski** — tokenit, `.env` ja sensitiivinen data voivat päätyä
kontin layeriin jos `.dockerignore` ei ole kattava.
### 4.5 `COPY`-kuri — kopioi vain tarvittava
`COPY . .` on kielletty. Jokainen `COPY` kopioi vain tarvittavat tiedostot
tai hakemistot:
```dockerfile
# VÄÄRIN
COPY . .
# OIKEIN
COPY package.json package-lock.json ./
COPY src/ src/
COPY public/ public/
```
**Miksi:**
- Layer-cache: `COPY . .` rikkoo välimuistin — mikä tahansa muutos
tiedostossa tyhjentää koko layerin
- Tietoturva: konttiin voi päätyä ylimääräisiä tiedostoja vaikka
`.dockerignore` olisi kattava (unohtunut ignore-rivi, uusi työkalu
joka luo tiedostoja build-kontekstiin)
- Luettavuus: `COPY . .` ei kerro mitä kontti todella sisältää
- Kontin koko: eksplisiittinen `COPY` pitää image-koon kurissa
### 4.6 `.helmignore` — pidä chart-paketti siistinä
`helm package` käyttää `.helmignore`-tiedostoa samalla periaatteella kuin
`docker build` käyttää `.dockerignore`a:
- Chart-hakemisto luetaan confin `VERSION_FILE`-kentästä (`dirname "${VERSION_FILE}"`)
- ignore-tiedosto luetaan chart-hakemiston juuresta (sama konteksti kuin
`Chart.yaml`, ks. 4.4)
- Kaikki turha (testit, docs, git, CI-konffit, kuvat) on poissuljettava
- Jos `.helmignore` puuttuu, `helm package` paketoi mukaan kaikki
chart-hakemiston tiedostot — turhaa bulkkia registryyn
**`.helmignore` on pakollinen** jokaiselle chartille. Minimisisältö:
```
.git/
.gitignore
tests/
docs/
*.md
.DS_Store
```
## 5. Raporttitasot
Testi tuottaa raportin `reports/<suite>/`-hakemistoon. Yksi `ci-report.sh`-kutsu hoitaa sekä
julkaisun että commit-statuksen.
### Taso 1: Ei jälkikäsittelyä
Kun testi tuottaa raportit suoraan (kuten `pytest --html` tai `cucumber-js --format html`):
- testi kirjoittaa `reports/<suite>/`-hakemistoon
- `ci-report.sh` julkaisee ja asettaa commit-statuksen
### Taso 2: Jälkikäsittely tarvitaan
Kun testi tuottaa raakadataa (stdout, coverage-tiedostot) joka pitää muuntaa tai siirtää
`reports/<suite>/`-hakemistoon. **Jokainen operaatio omassa stepissään** `if: always()`.
Tarkat YAML-mallit molemmista tasoista: `REFERENCE.md`.
**Subdir-sääntö:** Alihakemisto näkyy indexissä VAIN jos se sisältää `index.html`:n.
## 6. Nimeäminen
Tiedostonimet `.gitea/workflows/`-kansiossa noudattavat yhtenäistä rakennetta:
```
<komponentti>.ci-feature.yml ← feature-haaran reititin
<komponentti>.ci-main.yml ← main-haaran reititin
<komponentti>.<testityyppi>.yml ← yksittäinen testi tai operaatio
<komponentti>.ci-container-build-<kontti>.yml ← CI-kontin build-workflow
<komponentti>.gitea-env.conf ← komponenttikohtainen konfiguraatio
```
Single repossa `<komponentti>` jätetään pois.
Monorepossa prefiksi pitää komponentin tiedostot yhdessä.
### 6.1 Commit status -nimeäminen
`ci-report.sh`-kutsun `description` (2. argumentti) ja `context` (3. argumentti)
noudattavat seuraavaa kaavaa:
**Single repo:**
```
context: <testityyppi> (esim. unit-tests, acc-tests)
description: <Test type> test report (esim. Unit test report)
```
**Monorepo:**
```
context: <komponentti>.<testityyppi> (esim. library.unit-tests)
description: <Komponentti>: <Test type> test report (esim. Library: Unit test report)
```
> Gitea YAML: `run:` laita lainausmerkeillä `run: |`-blockiin — Gitea ei tue lainausmerkkejä yhden rivin `run:`-komennoissa.
>
> ```yaml
> - name: Report
> if: always()
> run: |
> bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
> ```
Build/push-status (Docker, Helm) on providerin hallussa — consumer ei vaikuta
niiden nimeämiseen.
## 7. Artifact-kuri
Gitea Actionsin `upload-artifact` jättää pysyvän tiedoston. Artifakteja ei käytetä
`workflow_call`:ien väliseen datan siirtoon ellei se ole teknisesti välttämätöntä.
**Ensisijainen ratkaisu:** jokainen testi tuottaa tarvitsemansa datan itse. Ei
`upload-artifact` + `download-artifact` -riippuvuuksia.
## 8. Report-Summary — pakollinen jokaisen pipelinen lopuksi
Jokaisen reitittimen (oli se `ci-main.yml`, `ci-feature.yml` tai mikä tahansa) viimeinen job on `report-summary`.
**Säännöt:**
- `needs:` sisältää **kaikki edeltävät jobit** — summary odottaa että kaikki on valmis (onnistui tai ei)
- `if: always()` — ajetaan aina, vaikka pipeline olisi keskeytetty tai joku jobi failannut
- `suites:` on välilyönnein eroteltu lista suiten nimistä (esim. `bats cucumber`). Tyhjä merkkijono sallittu jos testisuiteja ei ole.
- Provider (`report-summary.yml`) hoitaa summaryn logiikan — reititin vain kutsuu
**Miksi aina:**
- Gitea 1.27+ näyttää `GITHUB_STEP_SUMMARY`:n Actions UI:ssa. Ilman summarya pipeline näyttää epätäydelliseltä.
- Summaryyn voidaan myöhemmin lisätä muutakin kuin testilinkkejä (build-artefaktit, deploy-tiedot).
- Yhtenäinen rakenne jokaisessa pipeline-parissa vähentää kysymyksiä.
YAML-malli: `REFERENCE.md`.
## 9. ADR-yhteenveto — consumerin kannalta oleelliset säännöt
### Reititin ei sisällä suorittavaa koodia (ADR 0010)
`ci-feature.yml` ja `ci-main.yml` koostuvat **vain** `uses:`, `needs:` ja `if:`-avainsanoista.
Ei `run:`-komentoja, ei inline-skriptejä, ei `actions/checkout`.
### Yksi steppi = yksi workflow_call-tiedosto
Jokainen job reitittimessä on oma `workflow_call`-tiedostonsa.
Ei kahta eri komentoa samassa workflow'ssa.
### Provider-versio on `@v1` (ADR 0009)
Kaikki provider-viittaukset käyttävät `@v1`-tagia. `@main` on vain providerin oman repon
sisäiseen dogfood-käyttöön. Breaking changet kielletty — `v1`-rajapinta on pysyvä.
### Paikalliset `uses:` eivät käytä refiä
Gitea act runner v1.0.8 muodostaa paikallisista `uses: ./.gitea/workflows/*.yml@main`-viittauksista
epävalidin git-refin `main@<sha>`.
Paikallisista `uses:`-direktiiveistä EI koskaan käytetä `@main`- tai muuta ref-päätettä:
- `uses: ./.gitea/workflows/chart.helm-lint.yml` ← oikein
- `uses: ./.gitea/workflows/chart.helm-lint.yml@main` ← väärin
Ilman refiä runner käyttää workflow'ta triggeröivästä commitista.
### Exit-koodi on ainoa onnistumisen mittari (ADR 0008)
Ei pipeä (`|`) komennon perässä — se syö exit-koodin. Käytä redirectiä (`> file 2>&1`).
### Providerin checkout ei kuulu consumerille
Providerin scriptit haetaan `actions/checkout`-stepillä `.ci/`-polkuun.
Consumer ei kopioi eikä muokkaa providerin tiedostoja.
## 10. Build & Push -providerit
### `docker-build-push.yml` — Docker image build & push
Buildaa ja pushee Docker-imagen OCI-registryyn. Ajaa suoraan runnerilla
(ei `container:`-direktiiviä), joten `actions/checkout` toimii natiivisti.
**`env_json`-avaimet (pakolliset):**
```yaml
DOCKER_REGISTRY: gitea.app.keskikuja.site/niko
DOCKER_IMAGE_NAME: my-app
```
**Käyttö reitittimessä:**
```yaml
docker-build-push:
uses: OWNER/gitea-ci-library/.gitea/workflows/docker-build-push.yml@v1
needs: [check-version]
if: needs.check-version.outputs.artifact_exists == 'false'
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
```
Tarkka input/secret-lista: `docs/workflows.md`.
### `helm-build-push.yml` — Helm chart build & push
Pakkaa ja pushee Helm-chartin OCI-registryyn. Käyttää `alpine/helm`-konttia.
**`env_json`-avaimet (pakolliset):**
```yaml
HELM_REGISTRY: gitea.app.keskikuja.site/niko
VERSION_FILE: platform-helm/Chart.yaml # chart-hakemisto + versionlähde
```
**Käyttö reitittimessä:**
```yaml
helm-build-push:
uses: OWNER/gitea-ci-library/.gitea/workflows/helm-build-push.yml@v1
needs: [check-version]
if: needs.check-version.outputs.artifact_exists == 'false'
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
```
Chart-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/`.
**Yksittäisten Helm-UI-linkkien raportointi:** `HELM_UI_URL` on
tarkoitettu yleiselle registry UI:lle — provider muodostaa linkin
`${HELM_UI_URL}/${CHART_NAME}/${VERSION}` automaattisesti.
Tarkka input/secret-lista: `docs/workflows.md`.
## 11. Multi-artifact monorepo -komponentti
Yksi monorepo-komponentti voi tuottaa useita artefakteja (esim. Docker image
+ Helm chart). Kukin artefakti on **omassa reitittimessään** — ei yhtä
monoliittista pipelinea. Tämä on tietoinen arkkitehtuurivalinta:
- Reitittimet ovat itsenäisiä: eri `paths:`-triggerit, eri tagit, eri confit
- Yksi commit voi triggeröidä molemmat rinnakkain
- Yhden artefaktin build tai testi ei estä toista
### Esimerkki: `platform-helm` joka tuottaa Docker-imagen ja Helm chartin
```
.gitea/workflows/
├── platform-helm.ci-main.yml # Docker build & push
├── platform-helm.gitea-env.conf # Docker-konffi
├── platform-helm.helm-ci-main.yml # Helm build & push
├── platform-helm.helm-gitea-env.conf # Helm-konffi
├── platform-helm.helm-chart-lint.yml # Chart-testi
└── platform-helm.ci-container-build-helm.yml # CI-kontin build
```
### `platform-helm.gitea-env.conf` (Docker)
```ini
DOCKER_REGISTRY=gitea.app.keskikuja.site/niko
DOCKER_IMAGE_NAME=platform-helm
GIT_TAG_PREFIX=platform-helm/
```
### `platform-helm.helm-gitea-env.conf` (Helm)
```ini
HELM_REGISTRY=gitea.app.keskikuja.site/niko
VERSION_FILE=platform-helm/Chart.yaml
GIT_TAG_PREFIX=chart/
```
### Reitittimet
**`platform-helm.ci-main.yml`** — Docker-buildi, testit, oma tagi:
```yaml
name: platform-helm CI Main
on:
push:
branches: [main]
paths:
- platform-helm/**
- .gitea/workflows/platform-helm.*
jobs:
load-config:
uses: OWNER/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: .gitea/workflows/platform-helm.gitea-env.conf
check-version:
needs: [load-config]
uses: OWNER/gitea-ci-library/.gitea/workflows/check-version.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
test:
needs: [load-config, check-version]
uses: ./.gitea/workflows/platform-helm.sbom-lint.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
build-push:
needs: [load-config, check-version, test]
if: needs.check-version.outputs.artifact_exists == 'false'
uses: OWNER/gitea-ci-library/.gitea/workflows/docker-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
report-summary:
needs: [load-config, test, build-push]
if: always()
uses: OWNER/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: ''
```
**`platform-helm.helm-ci-main.yml`** — Helm-buildi, chart-testi, oma tagi:
```yaml
name: platform-helm Helm CI Main
on:
push:
branches: [main]
paths:
- platform-helm/**
- .gitea/workflows/platform-helm.helm*
jobs:
load-config:
uses: OWNER/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
secrets: inherit
with:
config_path: .gitea/workflows/platform-helm.helm-gitea-env.conf
check-version:
needs: [load-config]
uses: OWNER/gitea-ci-library/.gitea/workflows/check-version.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
chart-lint:
needs: [load-config, check-version]
uses: ./.gitea/workflows/platform-helm.helm-chart-lint.yml
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
helm-build-push:
needs: [load-config, check-version, chart-lint]
if: needs.check-version.outputs.artifact_exists == 'false'
uses: OWNER/gitea-ci-library/.gitea/workflows/helm-build-push.yml@v1
secrets: inherit
with:
env_json: ${{ needs.load-config.outputs.env_json }}
version: ${{ needs.check-version.outputs.version }}
report-summary:
needs: [load-config, chart-lint, helm-build-push]
if: always()
uses: OWNER/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: ''
```
### Säännöt
- Jokaisella artefaktilla on oma reititin, oma conf, omat testit
- Conf-tiedoston nimi erottaa artefaktit: `<komponentti>.gitea-env.conf` vs
`<komponentti>.helm-gitea-env.conf`
- `<komponentti>.helm-`-prefiksi erottaa Helm-artefaktin tiedostot
- `GIT_TAG_PREFIX` pitää tagit erillään: `platform-helm/1.2.3` vs `chart/1.2.3`
- Molemmat reitittimet voivat triggeröityä samasta commitista
-410
View File
@@ -1,410 +0,0 @@
---
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.
activation-gate: |
User mentions GitOps update, gitops-update, dispatch to another repo,
two-repo version bump, cross-repo deployment, or wiring build output to
config repo.
category: ci
impact: high
---
# GitOps Update — Provider-palvelu
`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.
## Arkkitehtuuri
Kaksi erillistä repoa, eristetyt oikeudet:
```
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
```
**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`:
```yaml
name: GitOps Update
run-name: "GitOps Service (${{ inputs.dispatch_id || 'manual' }})"
on:
workflow_dispatch:
inputs:
file:
required: true
type: string
yq_tpl:
required: true
type: string
version:
required: true
type: string
source_repo:
required: true
type: string
source_commit:
required: true
type: string
dispatch_id:
required: false
type: string
git_tag_prefix:
required: false
type: string
env:
INPUT_FILE: ${{ inputs.file }}
YQ_TPL: ${{ inputs.yq_tpl }}
VERSION: ${{ inputs.version }}
SOURCE_REPO: ${{ inputs.source_repo }}
SOURCE_COMMIT: ${{ inputs.source_commit }}
GITOPS_REPO: ${{ github.repository }}
GITOPS_BRANCH: ${{ github.ref_name }}
GITEA_API_URL: ${{ gitea.server_url }}
GIT_TAG_PREFIX: ${{ inputs.git_tag_prefix || '' }}
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: niko/gitea-ci-library
path: .ci
- name: Install yq
run: |
wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq
- name: Run GitOps update
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
bash .ci/scripts/gitops-update.sh
```
**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")
### Tulossa: custom CI-kontti
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
Code repo dispatchaa GitOps-repon workflown artifact buildin onnistuttua:
```yaml
gitops-update:
needs: [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: Dispatch GitOps update
id: dispatch
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
INPUTS=$(jq -nc \
--arg file "dev/Chart.yaml" \
--arg yq_tpl '(.dependencies[] | select(.name == "agent-platform-helm") | .version) = "{{VERSION}}"' \
--arg version "${{ needs.check-version.outputs.version }}" \
--arg source_repo "${{ github.repository }}" \
--arg source_commit "${{ github.sha }}" \
'{file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit}')
OUTPUT=$(bash .ci/scripts/dispatch-workflow.sh \
"niko/agent-platform-gitops" \
"ci-main.yml" \
"main" \
"$INPUTS" \
"${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }}" \
"${{ secrets.GITEA_TOKEN }}" \
"30")
echo "$OUTPUT"
GITOPS_COMMIT=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2)
echo "gitops_commit=$GITOPS_COMMIT" >> "$GITHUB_OUTPUT"
```
### Multi-artifact pipeline (kontti + helm)
Yksi main-haaran build tuottaa usein sekä Docker-imagen että Helm-chartin.
Kumpikin artefakti dispatchaa oman GitOps-päivityksensä rinnakkain:
```yaml
gitops-helm:
needs: [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"
```
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.
**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.
### Commit-status dispatchin perusteella
`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:
```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
```
`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)
Code-repon viimeinen job (`report-summary`) lisää GitOps-päivityksestä
rivin GITHUB_STEP_SUMMARYyn:
```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
| 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"
```
## Secretit ja tokenit
| 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 |
**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`
## Provider-skriptit
### `scripts/gitops-update.sh`
Ajaan GitOps-repon workflow'ssa. Päivittää konfiguraatiotiedoston yq:llä,
committaa ja pushaa. Asettaa commit-statuksen vain GitOps-repoon.
**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.
-182
View File
@@ -1,182 +0,0 @@
#!/usr/bin/env bats
source "$BATS_TEST_DIRNAME/helpers/mock-api.sh"
setup() {
export GITEA_TOKEN=test-token
export GIT_TAG_PREFIX=""
export SERVER_URL="http://localhost:18080"
export REPO="niko/test"
export SHA="abc123"
rm -rf /tmp/build-ctx
}
teardown() {
mock_stop 2>/dev/null || true
rm -rf /tmp/build-ctx
}
@test "VERSION_FILE=Chart.yaml extracts version from YAML" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/Chart.yaml"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "VERSION_FILE=VERSION extracts version from plain text" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "VERSION_FILE=package.json extracts version from JSON" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/package.json"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "VERSION_FILE=subdir/Chart.yaml extracts version from monorepo" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/subdir/Chart.yaml"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.4.0" ]
}
@test "no VERSION_FILE, root VERSION found" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
WORKDIR=$(mktemp -d)
cp "$BATS_TEST_DIRNAME/fixtures/check-version/VERSION" "$WORKDIR/VERSION"
SCRIPT="$PWD/scripts/check-version.sh"
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
rm -rf "$WORKDIR"
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "no VERSION_FILE, root Chart.yaml found" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
WORKDIR=$(mktemp -d)
cp "$BATS_TEST_DIRNAME/fixtures/check-version/Chart.yaml" "$WORKDIR/Chart.yaml"
SCRIPT="$PWD/scripts/check-version.sh"
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
rm -rf "$WORKDIR"
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "tag exists for commit sets ARTIFACT_EXISTS=true" {
mock_set_sequence '[{"code": 200, "body": [{"name": "0.3.0", "commit": {"sha": "abc123"}}]}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "true" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "tag with prefix filters correctly" {
mock_set_sequence '[{"code": 200, "body": [{"name": "git-pages/0.3.0", "commit": {"sha": "abc123"}}, {"name": "docker/0.3.0", "commit": {"sha": "abc123"}}]}]'
mock_start
export GIT_TAG_PREFIX="git-pages/"
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "true" ]
[ "$NEXT_VERSION" = "git-pages/0.3.0" ]
}
@test "no tag, new version calculated" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.0" ]
}
@test "highest patch calculated correctly" {
mock_set_sequence '[{"code": 200, "body": [{"name": "0.3.0", "commit": {"sha": "def456"}}, {"name": "0.3.1", "commit": {"sha": "def456"}}]}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
run bash scripts/check-version.sh
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
[ "$ARTIFACT_EXISTS" = "false" ]
[ "$NEXT_VERSION" = "0.3.2" ]
}
@test "VERSION_FILE=Chart-umbrella.yaml extracts only top-level version" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/Chart-umbrella.yaml"
run bash scripts/check-version.sh
echo "STATUS=$status"
echo "OUTPUT=$output"
[ "$status" -eq 0 ]
source /tmp/build-ctx/build.env
echo "NEXT_VERSION=$NEXT_VERSION"
[ "$NEXT_VERSION" = "0.1.0" ]
}
@test "no version source exits with error" {
mock_set_sequence '[{"code": 200, "body": []}]'
mock_start
WORKDIR=$(mktemp -d)
SCRIPT="$PWD/scripts/check-version.sh"
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
rm -rf "$WORKDIR"
[ "$status" -eq 1 ]
[[ "$output" == *"ERROR"* ]]
}
+19 -20
View File
@@ -3,7 +3,6 @@
setup() { setup() {
source tests/helpers/mock-api.sh source tests/helpers/mock-api.sh
export DISPATCH_POLL_INTERVAL="0.1" export DISPATCH_POLL_INTERVAL="0.1"
export DISPATCH_ID="test123"
} }
teardown() { teardown() {
@@ -13,7 +12,8 @@ teardown() {
@test "dispatch succeeds: POST 201, poll running x3 then success → exit 0" { @test "dispatch succeeds: POST 201, poll running x3 then success → exit 0" {
mock_set_sequence '[ mock_set_sequence '[
{"code":201}, {"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}}, {"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"running"}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}} {"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}}
@@ -26,7 +26,7 @@ teardown() {
@test "dispatch: poll returns failure conclusion → exit 1" { @test "dispatch: poll returns failure conclusion → exit 1" {
mock_set_sequence '[ mock_set_sequence '[
{"code":201}, {"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}}, {"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"running"}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"completed","conclusion":"failure"}} {"code":200,"body":{"id":1,"status":"completed","conclusion":"failure"}}
]' ]'
@@ -38,7 +38,7 @@ teardown() {
@test "dispatch: poll returns cancelled conclusion → exit 1" { @test "dispatch: poll returns cancelled conclusion → exit 1" {
mock_set_sequence '[ mock_set_sequence '[
{"code":201}, {"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}}, {"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"running"}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"id":1,"status":"completed","conclusion":"cancelled"}} {"code":200,"body":{"id":1,"status":"completed","conclusion":"cancelled"}}
]' ]'
@@ -47,18 +47,18 @@ teardown() {
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
} }
@test "timeout: no matching run found, exceeds timeout_minutes → exit 124" { @test "timeout: poll never completes, exceeds timeout_minutes → exit 124" {
mock_set_sequence '[ mock_set_sequence '[
{"code":201}, {"code":201},
{"code":200,"body":{"workflow_runs":[]}}, {"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"workflow_runs":[]}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"workflow_runs":[]}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"workflow_runs":[]}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"workflow_runs":[]}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"workflow_runs":[]}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"workflow_runs":[]}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"workflow_runs":[]}}, {"code":200,"body":{"id":1,"status":"running"}},
{"code":200,"body":{"workflow_runs":[]}} {"code":200,"body":{"id":1,"status":"running"}}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123" "0.001" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{"version":"1.2.3"}' "http://localhost:18080" "test-token-abc123" "0.001"
@@ -77,7 +77,7 @@ teardown() {
@test "POST dispatch is called with correct URL and payload" { @test "POST dispatch is called with correct URL and payload" {
mock_set_sequence '[ mock_set_sequence '[
{"code":201}, {"code":201},
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}}, {"code":200,"body":{"workflow_runs":[{"id":1,"status":"running"}]}},
{"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}} {"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}}
]' ]'
mock_start mock_start
@@ -91,7 +91,6 @@ teardown() {
[[ "$body" == *'"ref":"main"'* ]] [[ "$body" == *'"ref":"main"'* ]]
[[ "$body" == *'"inputs"'* ]] [[ "$body" == *'"inputs"'* ]]
[[ "$body" == *'"version":"1.2.3"'* ]] [[ "$body" == *'"version":"1.2.3"'* ]]
[[ "$body" == *'"dispatch_id":"test123"'* ]]
} }
@test "missing gitea_api_url argument → exit 1 with error message" { @test "missing gitea_api_url argument → exit 1 with error message" {
@@ -121,15 +120,15 @@ teardown() {
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
} }
@test "dispatch: no workflow run found after dispatch → exit 124 (timeout)" { @test "dispatch: no workflow run found after dispatch → exit 1" {
mock_set_sequence '[ mock_set_sequence '[
{"code":201}, {"code":201},
{"code":200,"body":{"workflow_runs":[]}} {"code":200,"body":{"workflow_runs":[]}}
]' ]'
mock_start mock_start
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{}' "http://localhost:18080" "test-token-abc123" "0.001" run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{}' "http://localhost:18080" "test-token-abc123"
[ "$status" -eq 124 ] [ "$status" -eq 1 ]
[[ "$output" == *"ERROR"* || "$output" == *"Timeout"* ]] [[ "$output" == *"ERROR"* ]]
} }
@test "missing inputs_json argument → exit 1" { @test "missing inputs_json argument → exit 1" {
-42
View File
@@ -1,42 +0,0 @@
Feature: GitOps update
As a GitOps repository
I want to update version references and report results to the caller
So that the deployment chain is traceable from source to GitOps commit
Background:
Given a project repository exists in Gitea
And a commit has been pushed to the repository
@mock
Scenario: Not enough env vars — script fails, no status set
Given insufficient environment variables are provided for the GitOps update
When the GitOps update script runs
Then the script exits with error
@mock
Scenario: GitOps job fails — no status set (SHA not yet known)
Given the GitOps repository clone will fail
When the GitOps update script runs
Then the script exits with error
@mock
Scenario: Everything succeeds — GitOps repo gets success status with link to caller
Given a valid GitOps update dispatch
When the GitOps update script runs
Then the script exits successfully
And the GitOps repo commit shows a success status with a link to the caller commit
@mock
Scenario: GitOps push fails — GitOps repo gets failure status
Given the GitOps repo push will fail after the version is committed
When the GitOps update script runs
Then the script exits with error
And the GitOps repo commit shows a failure status linking to the caller commit
@mock
Scenario: No changes — GitOps repo gets "no change" status
Given the version file already has the target version
When the GitOps update script runs
Then the script exits successfully
And the GitOps repo commit shows a "no change" status
And no Git commit or push was performed
@@ -6,7 +6,7 @@ const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh'); const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh');
Before({ tags: '@mock' }, function () { Before({ tags: '@mock' }, function () {
const out = execSync(`bash -c 'source "${MOCK_SCRIPT}" && mock_start && sleep 1 && curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health/check'`, { const out = execSync(`bash -c 'source "${MOCK_SCRIPT}" && mock_start && sleep 0.3 && curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health/check'`, {
cwd: PROJECT_ROOT, cwd: PROJECT_ROOT,
encoding: 'utf-8', encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
@@ -1,172 +0,0 @@
const { spawnSync, execSync } = require('child_process');
const { Before, After, Given, When, Then } = require('@cucumber/cucumber');
const path = require('path');
const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh');
const GITOPS_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'gitops-update.sh');
const MOCK_HELPERS = path.join(PROJECT_ROOT, 'tests', 'helpers');
const REQ_FILE = '/tmp/gitops-mock-requests.log';
const BASE_ENV = {
INPUT_FILE: 'dev/Chart.yaml',
YQ_TPL: '(.version) = "{{VERSION}}"',
VERSION: '0.2.3',
SOURCE_REPO: 'niko/app',
SOURCE_COMMIT: 'abc123def456',
GITOPS_REPO: 'niko/app-gitops',
GITEA_API_URL: 'http://localhost:18080',
GITEA_TOKEN: 'test-token',
};
Before({ tags: '@mock' }, function () {
process.env.PATH = `${MOCK_HELPERS}:${process.env.PATH}`;
try { execSync('rm -f /tmp/gitops-mock-requests.log', { stdio: 'ignore' }); } catch (_) {}
// Restart mock with known request file path
const result = spawnSync('bash', ['-c', `
source "${MOCK_SCRIPT}"
mock_stop 2>/dev/null
MOCK_REQUEST_FILE="${REQ_FILE}"
mock_start
sleep 0.5
curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health
`], {
cwd: PROJECT_ROOT, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
});
const code = result.stdout.trim();
if (!code.startsWith('2') && !code.startsWith('4')) {
throw new Error(`GitOps mock restart failed: ${result.stderr.substring(0,200)}`);
}
});
After({ tags: '@mock' }, function () {
spawnSync('bash', ['-c', `source "${MOCK_SCRIPT}" && mock_stop 2>/dev/null`], { stdio: 'ignore' });
try { execSync('rm -f /tmp/gitops-mock-requests.log /tmp/gitops-git-calls.log', { stdio: 'ignore' }); } catch (_) {}
});
function bash(cmd) {
const result = spawnSync('bash', ['-c', cmd], {
cwd: PROJECT_ROOT,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
return { status: result.status, stdout: result.stdout || '', stderr: result.stderr || '' };
}
function getFirstBody() {
return bash(`grep -A1 '^POST ' "${REQ_FILE}" 2>/dev/null | head -2 | tail -1 || echo ""`).stdout.trim();
}
function getFirstPath() {
return bash(`grep '^POST ' "${REQ_FILE}" 2>/dev/null | head -1 | awk '{print $2}' || echo ""`).stdout.trim();
}
function getLastBody() {
return bash(`grep -A1 '^POST ' "${REQ_FILE}" 2>/dev/null | grep -v '^POST ' | tail -1 || echo ""`).stdout.trim();
}
function getLastPath() {
return bash(`grep '^POST ' "${REQ_FILE}" 2>/dev/null | tail -1 | awk '{print $2}' || echo ""`).stdout.trim();
}
function requestCount() {
return parseInt(bash(`grep -c '^POST ' "${REQ_FILE}" 2>/dev/null || echo 0`).stdout.trim(), 10) || 0;
}
function gitCalls() {
const callsFile = process.env.GIT_CALLS_FILE || '/dev/null';
const out = bash(`cat "${callsFile}" 2>/dev/null || echo ""`).stdout;
return out.split('\n').filter(l => l.length > 0);
}
function runScript(envOverrides) {
const env = { ...BASE_ENV, ...envOverrides };
const scriptPath = `/tmp/gitops-run-${Date.now()}.sh`;
const exports = Object.entries(env)
.map(([k, v]) => `export ${k}="${v.replace(/"/g, '\\"')}"`)
.join('\n');
require('fs').writeFileSync(scriptPath, `${exports}\nexport PATH="${MOCK_HELPERS}:$PATH"\nset -euo pipefail\nbash "${GITOPS_SCRIPT}"\nsync\n`, 'utf8');
try {
return bash(`bash "${scriptPath}"`);
} finally {
require('fs').unlinkSync(scriptPath);
}
}
Given('insufficient environment variables are provided for the GitOps update', function () {
this.envOverrides = { INPUT_FILE: '' };
});
Given('the GitOps repository clone will fail', function () {
this.envOverrides = { GIT_MOCK_FAIL: '1' };
});
Given('a valid GitOps update dispatch', function () {
this.envOverrides = {};
});
Given('the GitOps repo push will fail after the version is committed', function () {
this.envOverrides = { GIT_MOCK_FAIL_PUSH: '1' };
});
Given('the version file already has the target version', function () {
this.envOverrides = {
GIT_MOCK_DIFF_NO_CHANGES: '1',
GIT_CALLS_FILE: '/tmp/gitops-git-calls.log',
};
});
When('the GitOps update script runs', function () {
this.result = runScript(this.envOverrides || {});
});
Then('the script exits with error', function () {
if (this.result.status === 0) throw new Error(`Expected non-zero exit, got 0. stderr: ${this.result.stderr.substring(0,200)}`);
});
Then('the script exits successfully', function () {
if (this.result.status !== 0) throw new Error(`Expected exit 0, got ${this.result.status}: ${this.result.stderr.substring(0,200)}`);
});
Then('the GitOps repo commit shows a success status with a link to the caller commit', function () {
const count = requestCount();
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
const body = getFirstBody();
if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`);
if (!body.includes('"context":"app ')) throw new Error(`Expected context "app unknown", body: ${body.substring(0,200)}`);
if (!body.includes('"description":"Install to dev 0.2.3"')) throw new Error(`Expected description, body: ${body.substring(0,200)}`);
if (!body.includes('niko/app/commit/abc123def456')) throw new Error(`Expected link to caller commit, body: ${body.substring(0,200)}`);
const pathStr = getFirstPath();
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
});
Then('the GitOps repo commit shows a failure status linking to the caller commit', function () {
const count = requestCount();
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
const body = getFirstBody();
if (!body.includes('"state":"failure"')) throw new Error(`Expected failure state, body: ${body.substring(0,200)}`);
if (!body.includes('"context":"app ')) throw new Error(`Expected context "app unknown", body: ${body.substring(0,200)}`);
if (!body.includes('"description":"Install to dev 0.2.3"')) throw new Error(`Expected description, body: ${body.substring(0,200)}`);
if (!body.includes('niko/app/commit/abc123def456')) throw new Error(`Expected link to caller commit, body: ${body.substring(0,200)}`);
const pathStr = getFirstPath();
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
});
Then('the GitOps repo commit shows a "no change" status', function () {
const count = requestCount();
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
const body = getFirstBody();
if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`);
if (!body.includes('"description":"Install to dev 0.2.3 \u2014 no change"')) {
throw new Error(`Expected "no change" description, body: ${body.substring(0,200)}`);
}
const pathStr = getFirstPath();
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
});
Then('no Git commit or push was performed', function () {
const calls = gitCalls();
if (calls.some(l => l.includes(' commit ') || l.includes(' push '))) {
throw new Error(`Expected no commit or push, got: ${calls.join(', ')}`);
}
});
@@ -15,7 +15,7 @@ function bash(cmd) {
encoding: 'utf-8', encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
}); });
return { status: 0, stdout: out, stderr: '' }; return { status: 0, stdout: out };
} catch (e) { } catch (e) {
return { status: e.status, stdout: e.stdout || '', stderr: e.stderr || '' }; return { status: e.status, stdout: e.stdout || '', stderr: e.stderr || '' };
} }
@@ -54,7 +54,7 @@ function setupMock(seqJson) {
} }
function runDispatch(args) { function runDispatch(args) {
return bash(`export DISPATCH_ID="test123"; export DISPATCH_POLL_INTERVAL="0.1"; bash "${DISPATCH_SCRIPT}" ${args}`); return bash(`export DISPATCH_POLL_INTERVAL="0.1"; bash "${DISPATCH_SCRIPT}" ${args}`);
} }
Given('a deployment has completed in the target environment', function () { Given('a deployment has completed in the target environment', function () {
@@ -66,7 +66,7 @@ Given('the test project repository exists with test definitions', function () {
When('a test workflow is dispatched to a test project', function () { When('a test workflow is dispatched to a test project', function () {
setupMock(JSON.stringify([ setupMock(JSON.stringify([
{ code: 201 }, { code: 201 },
{ code: 200, body: { workflow_runs: [{ id: 1, display_title: 'Workflow (test123)', run_number: 42, status: 'running' }] } }, { code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } },
{ code: 200, body: { id: 1, status: 'completed', conclusion: 'success' } }, { code: 200, body: { id: 1, status: 'completed', conclusion: 'success' } },
])); ]));
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"'); const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"');
@@ -84,7 +84,7 @@ Then('the pipeline continues only after receiving a success result', function ()
When('a test workflow is dispatched and the tests fail', function () { When('a test workflow is dispatched and the tests fail', function () {
setupMock(JSON.stringify([ setupMock(JSON.stringify([
{ code: 201 }, { code: 201 },
{ code: 200, body: { workflow_runs: [{ id: 1, display_title: 'Workflow (test123)', run_number: 42, status: 'running' }] } }, { code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } },
{ code: 200, body: { id: 1, status: 'completed', conclusion: 'failure' } }, { code: 200, body: { id: 1, status: 'completed', conclusion: 'failure' } },
])); ]));
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"'); const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123"');
@@ -98,19 +98,15 @@ Then('the calling pipeline reports failure', function () {
When('a test workflow is dispatched but does not finish within the allowed time', function () { When('a test workflow is dispatched but does not finish within the allowed time', function () {
setupMock(JSON.stringify([ setupMock(JSON.stringify([
{ code: 201 }, { code: 201 },
{ code: 200, body: { workflow_runs: [] } }, { code: 200, body: { workflow_runs: [{ id: 1, status: 'running' }] } },
{ code: 200, body: { workflow_runs: [] } }, { code: 200, body: { id: 1, status: 'running' } },
{ code: 200, body: { workflow_runs: [] } }, { code: 200, body: { id: 1, status: 'running' } },
{ code: 200, body: { workflow_runs: [] } }, { code: 200, body: { id: 1, status: 'running' } },
{ code: 200, body: { workflow_runs: [] } },
])); ]));
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123" "0.05"'); const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123" "0.001"');
this.dispatchResult = r.status; this.dispatchResult = r.status;
this.dispatchStderr = r.stderr;
}); });
Then('the calling pipeline reports a timeout error', function () { Then('the calling pipeline reports a timeout error', function () {
if (this.dispatchResult !== 124) { if (this.dispatchResult !== 124) throw new Error(`Expected timeout exit 124, got ${this.dispatchResult}`);
throw new Error(`Expected timeout exit 124, got ${this.dispatchResult}. stderr: ${(this.dispatchStderr || '').substring(0,300)}`);
}
}); });
-12
View File
@@ -1,12 +0,0 @@
apiVersion: v2
name: agent-platform
description: Agent Platform umbrella chart
type: application
version: 0.1.0
dependencies:
- name: vikunja
version: "0.1.0"
repository: oci://registry.example.com
- name: langfuse
version: "0.2.0"
repository: oci://registry.example.com
-6
View File
@@ -1,6 +0,0 @@
apiVersion: v2
name: test-chart
description: Test chart for version extraction
type: application
version: 0.3.0
appVersion: "1.0.0"
-1
View File
@@ -1 +0,0 @@
0.3.0
-1
View File
@@ -1 +0,0 @@
{"version": "0.3.0"}
-1
View File
@@ -1 +0,0 @@
<project><version>0.3.0</version></project>
-6
View File
@@ -1,6 +0,0 @@
apiVersion: v2
name: subdir-chart
description: Chart in subdirectory for monorepo testing
type: application
version: 0.4.0
appVersion: "1.0.0"
-176
View File
@@ -1,176 +0,0 @@
#!/usr/bin/env bats
setup() {
export INPUT_FILE=dev/Chart.yaml
export YQ_TPL='version = "{{VERSION}}"'
export VERSION=1.0.0
export SOURCE_REPO=niko/app
export SOURCE_COMMIT=abc123def456
export GITOPS_REPO=niko/app-gitops
export GITEA_TOKEN=test-token
export GITEA_API_URL=http://localhost:18080
}
teardown() {
if type mock_stop &>/dev/null 2>&1; then
mock_stop 2>/dev/null || true
fi
}
@test "missing GITEA_API_URL causes exit 1" {
unset GITEA_API_URL
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"GITEA_API_URL"* ]]
}
@test "missing GITEA_TOKEN causes exit 1" {
unset GITEA_TOKEN
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"GITEA_TOKEN"* ]]
}
@test "missing INPUT_FILE causes exit 1" {
unset INPUT_FILE
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"INPUT_FILE"* ]]
}
@test "missing YQ_TPL causes exit 1" {
unset YQ_TPL
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"YQ_TPL"* ]]
}
@test "missing VERSION causes exit 1" {
unset VERSION
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"VERSION"* ]]
}
@test "missing SOURCE_REPO causes exit 1" {
unset SOURCE_REPO
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"SOURCE_REPO"* ]]
}
@test "missing SOURCE_COMMIT causes exit 1" {
unset SOURCE_COMMIT
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"SOURCE_COMMIT"* ]]
}
@test "_gitops_substitute replaces {{VERSION}}" {
run bash -c '
source scripts/gitops-update.sh >/dev/null 2>&1
_gitops_substitute "(.version) = \"{{VERSION}}\"" "0.2.3"
'
[ "$status" -eq 0 ]
[[ "$output" == '(.version) = "0.2.3"' ]]
}
@test "CLONE_URL is constructed correctly from GITEA_API_URL" {
export GITEA_API_URL=https://gitea.app.keskikuja.site
export GITEA_TOKEN=secret123
export GITOPS_REPO=niko/app-gitops
run bash -c '
source scripts/gitops-update.sh >/dev/null 2>&1
echo "$CLONE_URL"
'
[ "$status" -eq 0 ]
[ "$output" = "https://secret123@gitea.app.keskikuja.site/niko/app-gitops.git" ]
}
@test "CLONE_URL works with http:// URL" {
export GITEA_API_URL=http://localhost:18080
export GITEA_TOKEN=token
export GITOPS_REPO=owner/repo
run bash -c '
source scripts/gitops-update.sh >/dev/null 2>&1
echo "$CLONE_URL"
'
[ "$status" -eq 0 ]
[ "$output" = "https://token@localhost:18080/owner/repo.git" ]
}
@test "_gitops_substitute handles multiple {{VERSION}} occurrences" {
run bash -c '
source scripts/gitops-update.sh >/dev/null 2>&1
_gitops_substitute "version = \"{{VERSION}}\"; tag = \"v{{VERSION}}\"" "1.2.3"
'
[ "$status" -eq 0 ]
[[ "$output" == 'version = "1.2.3"; tag = "v1.2.3"' ]]
}
@test "git flow: clone yq add commit push" {
source tests/helpers/mock-api.sh
mock_set_sequence '[
{"code":201},
{"code":201}
]'
mock_start
export GIT_CALLS_FILE=$(mktemp)
export YQ_CALLS_FILE=$(mktemp)
export PATH="${BATS_TEST_DIRNAME}/helpers:$PATH"
export INPUT_FILE=dev/Chart.yaml
export YQ_TPL='(.version) = "{{VERSION}}"'
export VERSION=0.2.3
export SOURCE_REPO=niko/app
export SOURCE_COMMIT=abc123def456
export GITOPS_REPO=niko/app-gitops
export GITEA_API_URL=http://localhost:18080
export GITEA_TOKEN=test-token
run bash scripts/gitops-update.sh
[ "$status" -eq 0 ]
git_calls=$(cat "$GIT_CALLS_FILE")
[[ "$git_calls" == *"clone"* ]]
[[ "$git_calls" == *"add"* ]]
[[ "$git_calls" == *"commit"* ]]
[[ "$git_calls" == *"push"* ]]
yq_calls=$(cat "$YQ_CALLS_FILE")
[[ "$yq_calls" == *"eval -i"* ]]
rm -f "$GIT_CALLS_FILE" "$YQ_CALLS_FILE"
mock_stop
}
@test "one commit-status call: gitops-repo only" {
source tests/helpers/mock-api.sh
mock_set_sequence '[
{"code":201}
]'
mock_start
export GIT_CALLS_FILE=$(mktemp)
export YQ_CALLS_FILE=$(mktemp)
export PATH="${BATS_TEST_DIRNAME}/helpers:$PATH"
export INPUT_FILE=dev/Chart.yaml
export YQ_TPL='(.version) = "{{VERSION}}"'
export VERSION=0.2.3
export SOURCE_REPO=niko/app
export SOURCE_COMMIT=abc123def456
export GITOPS_REPO=niko/app-gitops
export GITEA_API_URL=http://localhost:18080
export GITEA_TOKEN=test-token
run bash scripts/gitops-update.sh
[ "$status" -eq 0 ]
path=$(mock_get_first_request_path)
body=$(mock_get_first_request_body)
[[ "$path" == *"/repos/niko/app-gitops/statuses/"* ]]
[[ "$body" == *'"context":"app '* ]]
[[ "$body" == *'"description":"Install to dev 0.2.3"'* ]]
[[ "$body" == *'"state":"success"'* ]]
rm -f "$GIT_CALLS_FILE" "$YQ_CALLS_FILE"
mock_stop
}
@test "missing GITOPS_REPO causes exit 1" {
unset GITOPS_REPO
run bash scripts/gitops-update.sh
[ "$status" -eq 1 ]
[[ "$output" == *"GITOPS_REPO"* ]]
}
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env bash
echo "git $*" >> "${GIT_CALLS_FILE:-/dev/null}"
[ -z "${GIT_MOCK_FAIL:-}" ] || { echo "git: mock forced failure" >&2; exit 1; }
if [ "${1:-}" = "push" ] && [ -n "${GIT_MOCK_FAIL_PUSH:-}" ]; then
echo "git: mock push failure" >&2
exit 1
fi
# Skip -c config arguments
while [ "${1:-}" = "-c" ]; do
shift 2
done
case "$1" in
clone)
TARGET_DIR="${@: -1}"
mkdir -p "${TARGET_DIR}/$(dirname "$INPUT_FILE")"
echo 'version: 0.1.0' > "${TARGET_DIR}/${INPUT_FILE}"
echo "Cloning into '$TARGET_DIR'..."
;;
add|commit|push|config|init)
;;
diff)
# Default: exit 1 = has changes → proceed to commit
# GIT_MOCK_DIFF_NO_CHANGES=1 → exit 0 = no changes → "no change" path
if [ -n "${GIT_MOCK_DIFF_NO_CHANGES:-}" ]; then
exit 0
fi
exit 1
;;
rev-parse)
echo "mock-sha-9876543210fedcba9876543210fedcba98765432"
;;
*)
echo "git: unknown command: $*" >&2
exit 1
;;
esac
+3 -13
View File
@@ -12,10 +12,8 @@ MOCK_CONFIG_FILE=""
_kill_port() { _kill_port() {
local pids local pids
pids=$(lsof -ti ":$MOCK_PORT" 2>/dev/null) || true pids=$(lsof -ti ":$MOCK_PORT" 2>/dev/null) || true
if [ -n "$pids" ]; then [ -n "$pids" ] && kill -9 $pids 2>/dev/null || true
kill -9 $pids 2>/dev/null || true
sleep 0.5 sleep 0.5
fi
} }
_wait_port_free() { _wait_port_free() {
@@ -26,14 +24,6 @@ _wait_port_free() {
done done
} }
_wait_port_ready() {
local i=0
while ! lsof -ti ":$MOCK_PORT" >/dev/null 2>&1 && [ $i -lt 30 ]; do
sleep 0.2
i=$((i + 1))
done
}
mock_set_sequence() { mock_set_sequence() {
MOCK_SEQUENCE_FILE=$(mktemp) MOCK_SEQUENCE_FILE=$(mktemp)
echo "$1" | jq -c '.' > "$MOCK_SEQUENCE_FILE" echo "$1" | jq -c '.' > "$MOCK_SEQUENCE_FILE"
@@ -46,7 +36,7 @@ mock_clear_sequence() {
mock_start() { mock_start() {
MOCK_RESPONSE_CODE="${MOCK_RESPONSE_CODE:-201}" MOCK_RESPONSE_CODE="${MOCK_RESPONSE_CODE:-201}"
MOCK_REQUEST_FILE="${MOCK_REQUEST_FILE:-$(mktemp)}" MOCK_REQUEST_FILE=$(mktemp)
echo "$MOCK_REQUEST_FILE" > "$MOCK_STATE_FILE" echo "$MOCK_REQUEST_FILE" > "$MOCK_STATE_FILE"
MOCK_CONFIG_FILE=$(mktemp) MOCK_CONFIG_FILE=$(mktemp)
@@ -65,7 +55,7 @@ mock_start() {
nohup python3 "$(dirname "${BASH_SOURCE[0]}")/mock-server.py" "$MOCK_PORT" "$MOCK_CONFIG_FILE" "$MOCK_REQUEST_FILE" \ nohup python3 "$(dirname "${BASH_SOURCE[0]}")/mock-server.py" "$MOCK_PORT" "$MOCK_CONFIG_FILE" "$MOCK_REQUEST_FILE" \
</dev/null >/dev/null 2>&1 & </dev/null >/dev/null 2>&1 &
MOCK_PID=$! MOCK_PID=$!
_wait_port_ready sleep 0.5
} }
mock_stop() { mock_stop() {
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env bash
echo "yq $*" >> "${YQ_CALLS_FILE:-/dev/null}"