Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bcac84f2fd | |||
| db9d6daebb | |||
| ba16e9e4eb | |||
| 6463dad6d7 | |||
| 21a6ef7ab1 | |||
| 84978784fe | |||
| f58497f5e8 | |||
| fa57a152e4 | |||
| a0cdf377f6 | |||
| 028fd748a6 | |||
| 7f53e2c303 | |||
| ec22d49039 | |||
| 13493de7b2 | |||
| 47df5a8017 | |||
| e84e37c9f8 | |||
| 9105675591 | |||
| 1385afcca6 | |||
| 5c9df73a66 | |||
| 86e73d87d3 | |||
| a5947551d4 | |||
| 4910565547 | |||
| 2bef079d03 | |||
| 4f20f5ae2f | |||
| bd93ef2f8f | |||
| f06cb112d8 | |||
| d57f56f196 | |||
| 277c0f882d | |||
| cc7f4f0976 | |||
| 6621b3f336 | |||
| c0012ba6fa | |||
| 908aee9f8b | |||
| ad4c2cd570 | |||
| 1e10633e60 | |||
| 737b2fc3f2 | |||
| 65d385f9b9 | |||
| 2e5a4dca3a | |||
| 29fde14445 | |||
| f17ea7936e | |||
| 153205be40 | |||
| f3a54d6ed3 | |||
| 8622b6fc4e | |||
| dd9ad9e2c8 | |||
| ffc0734b65 | |||
| 14a411e340 | |||
| f7b2353eb9 |
@@ -0,0 +1,20 @@
|
|||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
node_modules/
|
||||||
|
reports/
|
||||||
|
coverage/
|
||||||
|
.ai/
|
||||||
|
.cursor/
|
||||||
|
.vscode/
|
||||||
|
tmp/
|
||||||
|
.DS_Store
|
||||||
|
*.md
|
||||||
|
docs/
|
||||||
|
guides/
|
||||||
|
.simplecov
|
||||||
|
cucumber.js
|
||||||
|
package-lock.json
|
||||||
|
package.json
|
||||||
|
CURRENT_PROVIDER_VERSION
|
||||||
|
README.md
|
||||||
|
AGENTS.md
|
||||||
@@ -1,37 +1,29 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
WORKSPACE_VOLUME="${1:-}"
|
REPORT_DIR="${1:-}"
|
||||||
REPORT_DIR="${2:-}"
|
COVERAGE_DIR="${REPORT_DIR}/coverage"
|
||||||
|
|
||||||
[ -n "$WORKSPACE_VOLUME" ] || { echo "ERROR: workspace volume name required" >&2; exit 1; }
|
|
||||||
[ -n "$REPORT_DIR" ] || { echo "ERROR: report directory required" >&2; exit 1; }
|
[ -n "$REPORT_DIR" ] || { echo "ERROR: report directory required" >&2; exit 1; }
|
||||||
|
|
||||||
HAS_COVERAGE=false
|
if [ -d coverage ]; then
|
||||||
COVERAGE_SRC=""
|
mkdir -p "$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
|
cp -a coverage/. "$COVERAGE_DIR/"
|
||||||
COVERAGE_SRC="/data/coverage"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$COVERAGE_SRC" ]; then
|
if [ -d "$COVERAGE_DIR" ] && [ ! -f "$COVERAGE_DIR/index.html" ]; then
|
||||||
mkdir -p "$REPORT_DIR/coverage"
|
SHA8="${GITHUB_SHA:0:8}"
|
||||||
docker run --rm -v "$WORKSPACE_VOLUME":/data alpine tar c -C "$COVERAGE_SRC" . | tar x -C "$REPORT_DIR/coverage"
|
{
|
||||||
HAS_COVERAGE=true
|
echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
|
||||||
|
echo "<title>Coverage report ${SHA8}</title>"
|
||||||
|
echo '<style>body{font-family:sans-serif;margin:2em}h1{color:#1e293b}ul{list-style:none;padding:0}li{margin:.5em 0}a{color:#2563eb}</style>'
|
||||||
|
echo "</head><body><h1>Coverage report <code>${SHA8}</code></h1><ul>"
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
base=$(basename "$f")
|
||||||
|
name="${base%.*}"
|
||||||
|
name="${name//-/ }"
|
||||||
|
echo "<li><a href=\"${base}\">${name^}</a></li>"
|
||||||
|
done < <(find "$COVERAGE_DIR" -maxdepth 1 -type f \( -name '*.html' -o -name '*.txt' \) ! -name index.html -print0 2>/dev/null || true)
|
||||||
|
echo '</ul></body></html>'
|
||||||
|
} > "$COVERAGE_DIR/index.html"
|
||||||
fi
|
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"
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
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:
|
|
||||||
check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
artifact_exists: ${{ steps.check.outputs.artifact_exists }}
|
|
||||||
version: ${{ steps.check.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Check existing artifact
|
|
||||||
id: check
|
|
||||||
run: |
|
|
||||||
VERSION=$(jq -r '.version' package.json)
|
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
TAG=$(curl -s "$GITEA_API_URL/api/v1/repos/$GITHUB_REPOSITORY/tags" | \
|
|
||||||
jq -r '.[] | select(.commit.sha == "'"$GITHUB_SHA"'") | .name' | head -1)
|
|
||||||
if [ -n "$TAG" ]; then
|
|
||||||
echo "artifact_exists=true" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Commit already tagged as $TAG, skipping build"
|
|
||||||
else
|
|
||||||
echo "artifact_exists=false" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
quality-gate:
|
|
||||||
needs: [check]
|
|
||||||
if: needs.check.outputs.artifact_exists == 'false'
|
|
||||||
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:
|
|
||||||
needs: [check, quality-gate]
|
|
||||||
if: needs.check.outputs.artifact_exists == 'false'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build container
|
|
||||||
run: |
|
|
||||||
docker build -t "minimal:${{ needs.check.outputs.version }}" .
|
|
||||||
mkdir -p /tmp/image
|
|
||||||
docker save "minimal:${{ needs.check.outputs.version }}" -o /tmp/image/artifact.tar
|
|
||||||
|
|
||||||
- name: Save Docker image for next job
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: docker-image
|
|
||||||
path: /tmp/image/artifact.tar
|
|
||||||
|
|
||||||
push:
|
|
||||||
needs: [check, build]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Load saved Docker image
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: docker-image
|
|
||||||
path: /tmp/image
|
|
||||||
|
|
||||||
- name: Push to Gitea Packages
|
|
||||||
run: |
|
|
||||||
VERSION="${{ needs.check.outputs.version }}"
|
|
||||||
docker load -i /tmp/image/artifact.tar
|
|
||||||
REGISTRY=$(echo "$GITEA_API_URL" | sed 's|https://||')
|
|
||||||
IMAGE="$REGISTRY/niko/gitea-ci-library/minimal:$VERSION"
|
|
||||||
docker tag "minimal:$VERSION" "$IMAGE"
|
|
||||||
docker login "$REGISTRY" -u niko -p "$GITEA_TOKEN"
|
|
||||||
docker push "$IMAGE"
|
|
||||||
docker logout "$REGISTRY"
|
|
||||||
|
|
||||||
tag-commit:
|
|
||||||
needs: [check, push]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Create git tag
|
|
||||||
run: |
|
|
||||||
VERSION="${{ needs.check.outputs.version }}"
|
|
||||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -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\"
|
|
||||||
}")
|
|
||||||
|
|
||||||
if [ "$HTTP_CODE" = "201" ]; then
|
|
||||||
echo "Tag $VERSION created"
|
|
||||||
elif [ "$HTTP_CODE" = "409" ]; then
|
|
||||||
echo "Tag $VERSION already exists (parallel build won), skipping"
|
|
||||||
else
|
|
||||||
echo "Failed to create tag: HTTP $HTTP_CODE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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"
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
name: CI Container Build & Push
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
env_json:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
dockerfile_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
image_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
tag:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'latest'
|
||||||
|
secrets:
|
||||||
|
DOCKER_USERNAME:
|
||||||
|
required: false
|
||||||
|
DOCKER_PASSWORD:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-push:
|
||||||
|
runs-on: 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"
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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'
|
|
||||||
needs: [load-config]
|
|
||||||
uses: niko/gitea-ci-library/.gitea/workflows/build_publish-artifact.yml@main
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
env_json: ${{ needs.load-config.outputs.env_json }}
|
|
||||||
bats-image: bats/bats:latest
|
|
||||||
cucumber-node-image: node:22
|
|
||||||
@@ -1,21 +1,41 @@
|
|||||||
name: Config Provider Library
|
name: Config Provider
|
||||||
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: |
|
||||||
@@ -29,3 +49,6 @@ 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"
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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 }}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: CI Container Build Bats
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
config_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: '.gitea/workflows/example-gitea-env.conf'
|
||||||
|
description: 'Polku .gitea-env.conf-tiedostoon'
|
||||||
|
dockerfile_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'Dockerfile.ci-bats'
|
||||||
|
description: 'Polku Dockerfileen'
|
||||||
|
image_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'ci-bats'
|
||||||
|
description: 'Kontin nimi ilman registry-polkua'
|
||||||
|
tag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'latest'
|
||||||
|
description: 'Image-tägi'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
load-config:
|
||||||
|
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
config_path: ${{ inputs.config_path }}
|
||||||
|
|
||||||
|
build-push:
|
||||||
|
needs: [load-config]
|
||||||
|
uses: niko/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
env_json: ${{ needs.load-config.outputs.env_json }}
|
||||||
|
dockerfile_path: ${{ inputs.dockerfile_path }}
|
||||||
|
image_name: ${{ inputs.image_name }}
|
||||||
|
tag: ${{ inputs.tag }}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: CI Container Build Cucumber
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
config_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: '.gitea/workflows/example-gitea-env.conf'
|
||||||
|
description: 'Polku .gitea-env.conf-tiedostoon'
|
||||||
|
dockerfile_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'Dockerfile.ci-cucumber'
|
||||||
|
description: 'Polku Dockerfileen'
|
||||||
|
image_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'ci-cucumber'
|
||||||
|
description: 'Kontin nimi ilman registry-polkua'
|
||||||
|
tag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'latest'
|
||||||
|
description: 'Image-tägi'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
load-config:
|
||||||
|
uses: niko/gitea-ci-library/.gitea/workflows/config-provider.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
config_path: ${{ inputs.config_path }}
|
||||||
|
|
||||||
|
build-push:
|
||||||
|
needs: [load-config]
|
||||||
|
uses: niko/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
env_json: ${{ needs.load-config.outputs.env_json }}
|
||||||
|
dockerfile_path: ${{ inputs.dockerfile_path }}
|
||||||
|
image_name: ${{ inputs.image_name }}
|
||||||
|
tag: ${{ inputs.tag }}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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 }}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
GITEA_API_URL=https://gitea.app.keskikuja.site
|
||||||
|
GIT_PAGES_URL=https://ci-reports.helm-dev.keskikuja.site
|
||||||
|
DOCKER_REGISTRY=gitea.app.keskikuja.site/niko
|
||||||
|
DOCKER_IMAGE_NAME=gitea-ci-library-test-image
|
||||||
|
DOCKER_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container
|
||||||
|
#DOCKERFILE=Dockerfile.platform
|
||||||
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
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 }}
|
||||||
|
|
||||||
|
helm-build-push:
|
||||||
|
name: Build & Push Helm
|
||||||
|
needs: [load-config, check-version, bats, cucumber]
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
docker-gitops:
|
||||||
|
name: Update docker
|
||||||
|
needs: [docker-build-push]
|
||||||
|
uses: niko/gitea-ci-library/.gitea/workflows/gitops-dispatch.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
env_json: ${{ needs.load-config.outputs.env_json }}
|
||||||
|
version: ${{ needs.check-version.outputs.version }}
|
||||||
|
GITOPS_FILE: dev/values.yaml
|
||||||
|
GITOPS_YQ_TPL: '.service.tag = "{{VERSION}}"'
|
||||||
|
GITOPS_REPO: niko/gitea-ci-gitops-tests
|
||||||
|
|
||||||
|
report-summary:
|
||||||
|
name: Report Summary
|
||||||
|
needs: [load-config, 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-build-push, helm-build-push]
|
||||||
|
if: success()
|
||||||
|
uses: niko/gitea-ci-library/.gitea/workflows/tag-maintenance.yml@main
|
||||||
|
secrets: inherit
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
name: Hello World (Manual)
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
# branches: [ main ] # Tai [ feature/ci-container ]
|
||||||
|
branches: [feature/ci-container]
|
||||||
|
jobs:
|
||||||
|
greet:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Hello"
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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 }}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
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
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
GITEA_API_URL=https://gitea.app.keskikuja.site
|
|
||||||
GIT_PAGES_URL=https://ci-reports.helm-dev.keskikuja.site
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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"
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
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
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
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: Validate CI config
|
|
||||||
run: bash .ci/scripts/ci-validate.sh
|
|
||||||
|
|
||||||
bats:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: niko/gitea-ci-library
|
|
||||||
path: .ci
|
|
||||||
|
|
||||||
- name: Run bats tests
|
|
||||||
id: bats-tests
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
docker volume create bats-workspace
|
|
||||||
tar c . | docker run --rm -i -v bats-workspace:/data alpine tar x -C /data
|
|
||||||
mkdir -p "reports/${GITHUB_SHA:0:8}/bats"
|
|
||||||
set +e
|
|
||||||
docker run --rm \
|
|
||||||
-v bats-workspace:/data \
|
|
||||||
--entrypoint bash ${{ inputs.bats-image }} \
|
|
||||||
-c 'apk add -q lsof python3 jq curl ruby && cd /data && gem install bashcov -v 3.3.0 2>&1 | tail -1 && bashcov -- bats tests/' \
|
|
||||||
> "reports/${GITHUB_SHA:0:8}/bats/results.txt" 2>&1
|
|
||||||
BATS_EXIT=$?
|
|
||||||
bash .ci/.gitea/scripts/bats-coverage.sh bats-workspace "reports/${GITHUB_SHA:0:8}/bats"
|
|
||||||
docker volume rm bats-workspace > /dev/null 2>&1
|
|
||||||
bash .ci/.gitea/scripts/bats-report.sh "reports/${GITHUB_SHA:0:8}/bats"
|
|
||||||
echo "BATS_EXIT=${BATS_EXIT}" >> "${GITHUB_ENV}"
|
|
||||||
exit ${BATS_EXIT}
|
|
||||||
|
|
||||||
- name: Publish bats reports
|
|
||||||
if: always()
|
|
||||||
run: bash .ci/scripts/publish-git-pages.sh bats
|
|
||||||
|
|
||||||
- name: Set bats commit status
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
if [ "${BATS_EXIT}" = "0" ]; then
|
|
||||||
bash .ci/scripts/report-status.sh success "Bats tests" 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: 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=$?
|
|
||||||
|
|
||||||
STATE="success"
|
|
||||||
[ "${CUCUMBER_EXIT}" != "0" ] && STATE="failure"
|
|
||||||
if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
|
|
||||||
bash .ci/scripts/report-status.sh "${STATE}" "Cucumber tests" ci-cucumber cucumber
|
|
||||||
else
|
|
||||||
bash .ci/scripts/report-status.sh "${STATE}" "Cucumber tests" ci-cucumber
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit ${CUCUMBER_EXIT}
|
|
||||||
|
|
||||||
- name: Publish cucumber reports
|
|
||||||
if: always()
|
|
||||||
run: bash .ci/scripts/publish-git-pages.sh cucumber
|
|
||||||
|
|
||||||
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: Generate report index
|
|
||||||
run: bash .ci/.gitea/scripts/generate-report-index.sh
|
|
||||||
|
|
||||||
- name: Set build commit status
|
|
||||||
run: bash .ci/scripts/report-status.sh success "Build complete" ci-build
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
name: Report Summary
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
env_json:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
suites:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: Space-separated suite names published to git-pages
|
||||||
|
gitops:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
description: 'Pipe-separated rows: component|version|status|commit_sha|repo'
|
||||||
|
|
||||||
|
env:
|
||||||
|
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
summary:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Generate report links
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
SHA8="${GITHUB_SHA:0:8}"
|
||||||
|
BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/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
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: Tag Maintenance
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
secrets:
|
||||||
|
GITEA_TOKEN:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
move-tag:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Read current provider version
|
||||||
|
id: version
|
||||||
|
run: echo "TAG=$(cat CURRENT_PROVIDER_VERSION | tr -d '[:space:]')" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Move tag to commit
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.version.outputs.TAG }}"
|
||||||
|
SHA="${{ github.sha }}"
|
||||||
|
|
||||||
|
curl -sf -X DELETE \
|
||||||
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
|
"${{ gitea.server_url }}/api/v1/repos/${{ github.repository }}/tags/${TAG}" || true
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${{ gitea.server_url }}/api/v1/repos/${{ github.repository }}/tags" \
|
||||||
|
-d "{\"tag_name\": \"${TAG}\", \"message\": \"Release ${TAG}\", \"target\": \"${SHA}\"}")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ]; then
|
||||||
|
echo "${TAG} tag moved to ${SHA}"
|
||||||
|
else
|
||||||
|
echo "ERROR: failed to move ${TAG} tag (HTTP $HTTP_CODE)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -7,3 +7,4 @@ tmp/
|
|||||||
coverage/
|
coverage/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
reports/
|
reports/
|
||||||
|
.vscode/
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
v1
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
FROM bats/bats:1.11.0
|
||||||
|
RUN apk add --no-cache lsof python3 jq curl ruby nodejs git && \
|
||||||
|
gem install bashcov -v 3.3.0
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
FROM node:22
|
||||||
|
RUN apt-get update -qq && \
|
||||||
|
apt-get install -y -qq --no-install-recommends lsof jq python3 && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
npm install -g @cucumber/cucumber
|
||||||
|
ENV NODE_PATH=/usr/local/lib/node_modules
|
||||||
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
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
|
||||||
@@ -102,34 +111,57 @@ Act runner suorittaa Gitea Actions workflowt. **IaC-lähde:** alla oleva Helm-sn
|
|||||||
klusterin totuus — muutokset vain snippetiin, sitten `helm upgrade --install` (ei käsin muokattuja
|
klusterin totuus — muutokset vain snippetiin, sitten `helm upgrade --install` (ei käsin muokattuja
|
||||||
arvoja klusterissa).
|
arvoja klusterissa).
|
||||||
|
|
||||||
|
> HUOM! Gitea ei ole vielä kunnolla stabiilissa tilassa, ja chart default dind sekä runner versiot ovat tätä tehdessä olleet bugiset. Niistä on olemassa uudemmat versiot, mutta eivät ole chartissa. Tätyy seurata ja päivittää tarpeen tulle.
|
||||||
|
|
||||||
Asennus Kubernetes-klusteriin Helm chartilla:
|
Asennus Kubernetes-klusteriin Helm chartilla:
|
||||||
|
|
||||||
### 1. Rekisteröintitoken
|
### 1. Rekisteröi token
|
||||||
|
|
||||||
Hae token Giteasta:
|
Hae token Giteasta:
|
||||||
- **Organization-taso:** Org → Settings → Actions → Runners → Create new runner
|
- **Organization-taso:** Org → Settings → Actions → Runners → Create new runner
|
||||||
- **Globaali (site admin):** Site Admin → Actions → Runners → Create new runner
|
- **Globaali (site admin):** Site Admin → Actions → Runners → Create new runner
|
||||||
|
|
||||||
### 2. Asenna runner
|
### 2. variables
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GITEA_URL="https://<gitea-server-url>"
|
GITEA_URL="https://<gitea-server-url>"
|
||||||
GITEA_ACTIONS_TOKEN="<registration-token>"
|
|
||||||
GITEA_ACTIONS_NAMESPACE="gitea-actions"
|
GITEA_ACTIONS_NAMESPACE="gitea-actions"
|
||||||
|
GITEA_ACTIONS_TOKEN="<registration-token>"
|
||||||
|
```
|
||||||
|
|
||||||
helm repo add gitea https://dl.gitea.com/charts
|
### 3. Tee secret vain init install yhteydessä
|
||||||
helm repo update
|
|
||||||
|
|
||||||
|
```bash
|
||||||
kubectl create secret generic act-runner-token \
|
kubectl create secret generic act-runner-token \
|
||||||
--from-literal=token="$GITEA_ACTIONS_TOKEN" \
|
--from-literal=token="$GITEA_ACTIONS_TOKEN" \
|
||||||
--namespace "$GITEA_ACTIONS_NAMESPACE" \
|
--namespace "$GITEA_ACTIONS_NAMESPACE" \
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Helm install / upgrade
|
||||||
|
|
||||||
|
Menee samalla komennolla.
|
||||||
|
|
||||||
|
> Muista asettaa variables ennen ajoa.
|
||||||
|
|
||||||
|
Päivittää olemassa olevan installaation, käyttää olemassa olevaa secret
|
||||||
|
ja sitä kautta Gitea ei tarvitse päivityksessä mitään temppuja.
|
||||||
|
|
||||||
|
Päivityksen jälkeen muista tappaa pod (käynnistyy automaattisesti uudelleen), että lataa varmasti kaikki uudesta. Sillä ConfigMap tms eivät lataudu
|
||||||
|
mikäli pod jatkaa ajamista.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm repo add gitea https://dl.gitea.com/charts
|
||||||
|
helm repo update
|
||||||
|
|
||||||
helm upgrade --install act-runner gitea/actions \
|
helm upgrade --install act-runner gitea/actions \
|
||||||
--set enabled=true \
|
--set enabled=true \
|
||||||
--set giteaRootURL="$GITEA_URL" \
|
--set giteaRootURL="$GITEA_URL" \
|
||||||
--set existingSecret=act-runner-token \
|
--set existingSecret=act-runner-token \
|
||||||
--set existingSecretKey=token \
|
--set existingSecretKey=token \
|
||||||
|
--set statefulset.replicas=3 \
|
||||||
|
--set statefulset.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:
|
||||||
level: info
|
level: info
|
||||||
@@ -190,7 +222,9 @@ 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 sisäinen secret (`secrets.GITEA_TOKEN`), joka on automauttisesti saatavilla — sitä ei tarvitse erikseen luoda.
|
`GITEA_TOKEN` on Gitean automaattisesti jokaiselle workflow-runille generoima token (`secrets.GITEA_TOKEN`). Se on scopeutettu **siihen repoon**, jossa workflow ajaa — ei toimi toiseen repoon dispatchaukseen eikä toisen repon commit-statusin asettamiseen. Ei tarvitse erikseen luoda.
|
||||||
|
|
||||||
|
Jos workflow tarvitsee oikeuksia **toiseen** repoon (esim. dispatch GitOps-repoon), tarvitaan manuaalinen token. Katso [skills/gitops-update/SKILL.md](skills/gitops-update/SKILL.md).
|
||||||
|
|
||||||
### Config-tiedosto (`.gitea/workflows/gitea-env.conf`)
|
### Config-tiedosto (`.gitea/workflows/gitea-env.conf`)
|
||||||
|
|
||||||
@@ -226,6 +260,17 @@ 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 |
|
||||||
|
|||||||
@@ -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 pipeline |
|
| `.gitea/workflows/` | Sekoitettu | Providerin reusable workflowt + consumerin example-pipeline |
|
||||||
| `.gitea/workflows/gitea-env.conf` | Consumer | KEY=VALUE config |
|
| `.gitea/workflows/example-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`,
|
||||||
`build-feature.yml`) ovat samassa `.gitea/workflows/`-kansiossa consumerin
|
`check-version.yml`, `docker-build-push.yml`) ovat samassa `.gitea/workflows/`-kansiossa
|
||||||
pipeline-tiedostojen (`ci.yml`) kanssa.
|
consumerin esimerkkipipeline-tiedostojen (`example-*`) kanssa.
|
||||||
|
|
||||||
Erottelu on nimessä ja dokumentaatiossa, ei kansiorakenteessa:
|
Erottelu on nimessä ja dokumentaatiossa, ei kansiorakenteessa:
|
||||||
- `config-provider.yml`, `build-feature.yml` — providerin tarjoamia
|
- `config-provider.yml`, `check-version.yml`, `docker-build-push.yml` — providerin tarjoamia
|
||||||
- `ci.yml` — consumerin omistamia
|
- `example-feature.yml`, `example-main.yml`, `example-*.yml` — consumer-esimerkkejä
|
||||||
|
|
||||||
## 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/gitea-env.conf`
|
## Consumerin `.gitea/workflows/example-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,8 +61,7 @@ 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 `.gitea/workflows/ci.yml`,
|
- Consumer voi muuttaa `example-*.yml` ja `.gitea/scripts/` sisältöä
|
||||||
`.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)
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# 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.
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# 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}`.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# 9. Breaking changes kielletty
|
||||||
|
|
||||||
|
## Päätös
|
||||||
|
|
||||||
|
Providerin `v1`-tagin osoittamaa rajapintaa ei koskaan rikota.
|
||||||
|
Consumerin `uses:`-kutsut säilyvät yhteensopivina — uusi versiotagi
|
||||||
|
(`v2`, `v3`) luodaan VAIN jos taaksepäin yhteensopimaton muutos on
|
||||||
|
pakottava. Käytännössä: `v1` on pysyvä, ja sitä ylläpidetään
|
||||||
|
eteenpäin.
|
||||||
|
|
||||||
|
## Rajapinnan määritelmä
|
||||||
|
|
||||||
|
Providerin rajapinta = `config-provider.yml` ja `check-version.yml`
|
||||||
|
workflow_call-inputit ja -outputit:
|
||||||
|
|
||||||
|
- Inputtien nimet, tyypit ja required-arvot eivät muutu
|
||||||
|
- Outputtien nimet eivät katoa
|
||||||
|
- Secret-nimet eivät muutu
|
||||||
|
- Workflow-tiedoston nimi ja polku eivät muutu
|
||||||
|
|
||||||
|
Sisäinen toteutus (scriptit, checkout-logiikka, build-vaiheet) voi
|
||||||
|
muuttua vapaasti — consumer ei ole niistä riippuvainen.
|
||||||
|
|
||||||
|
## Versiotagin siirto
|
||||||
|
|
||||||
|
Tagi `v1` siirretään automaattisesti uusimpaan onnistuneeseen
|
||||||
|
main-commitin CI-ajoon (`tag-maintenance.yml`). Tagin nimi luetaan
|
||||||
|
tiedostosta `CURRENT_PROVIDER_VERSION`.
|
||||||
|
|
||||||
|
Jos breaking change joskus tulee pakottavaksi:
|
||||||
|
1. Päivitä `CURRENT_PROVIDER_VERSION` → `v2`
|
||||||
|
2. Luo uusi `v2`-tagi manuaalisesti (osoittaa uuteen rajapintaan)
|
||||||
|
3. `tag-maintenance.yml` alkaa ylläpitää `v2`:ta eteenpäin
|
||||||
|
4. `v1`-tagia **ei poisteta** — se jää osoittamaan viimeistä
|
||||||
|
v1-yhteensopivaa committia. `v1`:tä käyttävät consumerit
|
||||||
|
jatkavat toimintaansa ilman muutoksia.
|
||||||
|
5. Uudet consumerit ottavat käyttöön `@v2`:n.
|
||||||
|
|
||||||
|
Aktiivisesti ylläpidetään vain yhtä tagia (`CURRENT_PROVIDER_VERSION`).
|
||||||
|
Vanhat tagit säilyvät paikoillaan taaksepäin yhteensopivuutta varten.
|
||||||
|
|
||||||
|
## Perustelu
|
||||||
|
|
||||||
|
Yhden aktiivisen tagin ylläpito on yksinkertaisempaa kuin usean
|
||||||
|
rinnakkaisen version aktiivinen hallinta. Homelab-ympäristössä
|
||||||
|
consumerit ovat saman ylläpitäjän hallinnassa — eri tiimien
|
||||||
|
rinnakkaisia aktiiviversioita ei tarvita.
|
||||||
|
|
||||||
|
Breaking changen sattuessa vanha tagi säilyy — vanhat consumerit
|
||||||
|
eivät hajoa. Uusi tagi otetaan käyttöön uusissa consumereissa.
|
||||||
|
|
||||||
|
Breaking changen kielto pakottaa suunnittelemaan rajapinnat
|
||||||
|
huolellisesti etukäteen.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# 10. Pipeline-reititin — ei komentoja
|
||||||
|
|
||||||
|
## Päätös
|
||||||
|
|
||||||
|
CI-pipelinetiedostot (`ci-main.yml`, `ci-feature.yml`) ovat puhtaita
|
||||||
|
**reitittimiä**. Ne eivät saa sisältää `run:`-komentoja, inline-skriptejä
|
||||||
|
tai varsinaista build-/test-logiikkaa. Niiden ainoa sallittu sisältö on:
|
||||||
|
|
||||||
|
- `uses:` — viittaus reusable workflow'hun
|
||||||
|
- `needs:` — riippuvuus toisen jobin valmistumiseen
|
||||||
|
- `if:` — ehdollinen suoritus
|
||||||
|
- `secrets: inherit` — salaisuuksien välitys
|
||||||
|
|
||||||
|
## Mikä ei kuulu reitittimeen
|
||||||
|
|
||||||
|
- `run:`-komennot
|
||||||
|
- Inline-skriptit (shell, Python, tms.)
|
||||||
|
- `actions/checkout` (paitsi providerin sisäinen infrastruktuuri)
|
||||||
|
- Build-työkalut, testikomennot, Docker-komennot
|
||||||
|
- Raporttien generointi
|
||||||
|
|
||||||
|
## Missä logiikka on
|
||||||
|
|
||||||
|
| Kerros | Tiedosto | Sisältö |
|
||||||
|
|---|---|---|
|
||||||
|
| Reititin | `ci-main.yml`, `ci-feature.yml` | Vain `uses:`-kutsut |
|
||||||
|
| Workflow_call | `ci-unit-tests.yml`, `ci-acc-tests.yml`, … | Varsinainen testi-/build-logiikka |
|
||||||
|
| Provider | `config-provider.yml`, `check-version.yml`, … | Jaettu infrastruktuuri |
|
||||||
|
| Skriptit | `scripts/` | Apufunktiot (status, publish, validointi) |
|
||||||
|
|
||||||
|
## Perustelu
|
||||||
|
|
||||||
|
Reitittimen ja toteutuksen erottelu:
|
||||||
|
|
||||||
|
- Reititin kertoo **mitä** ajetaan ja **missä järjestyksessä** — luettavissa
|
||||||
|
yhdellä silmäyksellä ilman teknistä taustaa
|
||||||
|
- Toteutus (workflow_call) kertoo **miten** — tekninen henkilö voi keskittyä
|
||||||
|
yhteen tiedostoon kerrallaan
|
||||||
|
- Providerin reusable workflow't eivät koskaan sisällä inline-logiikkaa —
|
||||||
|
skriptit ovat omissa tiedostoissaan
|
||||||
|
|
||||||
|
Tämä on sama periaate kuin web-sovelluksissa: reititin ei sisällä
|
||||||
|
business-logiikkaa, vain HTTP-verbit ja polut.
|
||||||
+65
-63
@@ -1,6 +1,6 @@
|
|||||||
# AI Context: Gitea Actions CI -kirjasto
|
# AI Context: Gitea Actions CI -kirjasto
|
||||||
|
|
||||||
**Updated**: 2026-06-12 (POC-vaihe, suunniteltu uudelleenkirjoitus)
|
**Updated**: 2026-06-19 (provider/consumer dual role, Project Skills -aktivointi)
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-,
|
Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-,
|
||||||
@@ -8,90 +8,92 @@ 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-kirjasto: reusable workflowt, scriptit, ADRt, dokumentaatio.
|
Provider- ja consumer-kirjasto: reusable workflowt, scriptit, ADRt, dokumentaatio,
|
||||||
Consumer kutsuu `build-feature.yml`-workflowa `uses:`-direktiivillä.
|
ja consumer-esimerkit (dogfood). Consumer kutsuu provider-workflowta `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. Kohdeltava kuten se olisi jo
|
omat tekniset valinnat, oma design-rationale. Kaikki git-pages-spesifi tieto
|
||||||
oma reponsa: kaikki git-pages-spesifi tieto kuuluu `git-pages/docs/`- alle,
|
kuuluu `git-pages/docs/`-alle, ei juuren `docs/`-kansioon.
|
||||||
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/` | Reusable workflowt (`build-feature.yml`, `config-provider.yml`) |
|
| `.gitea/workflows/config-provider.yml` | Provider: lataa + validoi config-tiedoston, tuottaa `env_json` |
|
||||||
| `scripts/` | `publish-git-pages.sh`, `report-status.sh`, `dispatch-workflow.sh` |
|
| `.gitea/workflows/check-version.yml` | Provider: tarkistaa onko commitille jo artifact, laskee version |
|
||||||
| **`git-pages/`** | **Oma kokonaisuus: Helm-chartti + docs + retention** |
|
| `.gitea/workflows/docker-build-push.yml` | Provider: buildaa + puskea Docker-imagen, tagittaa commitin |
|
||||||
| `docs/` | Root-tason arkkitehtuuri, ADRt (0001–0005) |
|
| `.gitea/workflows/ci-container-build-push.yml` | Provider: buildaa + puskea CI-työkalukontin |
|
||||||
|
| `.gitea/workflows/example-*` | **Consumer-esimerkki**: tämän repon oma CI (dogfood) |
|
||||||
|
| `scripts/` | Provider-skriptit: `report-status.sh`, `publish-git-pages.sh`, `ci-validate.sh` |
|
||||||
|
| `.gitea/scripts/` | **Consumer-skriptit**: `bats-coverage.sh`, `bats-report.sh` |
|
||||||
|
| `docs/` | Arkkitehtuuri, ADRt (0004–0008) |
|
||||||
|
| `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 |
|
|
||||||
|
|
||||||
**Tarkemmat git-pages-asiat:** `git-pages/docs/` (implementation-notes,
|
### Provider workflowt (6 kpl)
|
||||||
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
|
|
||||||
vapaasti muutettavissa
|
|
||||||
- **Vain Gitea, vain reusable workflowt**: ei custom actioneita, ei
|
|
||||||
multi-platform
|
|
||||||
- **Raportit git-pagesissa**: HTML selailtavissa, retention automaattinen
|
|
||||||
- **Git-pages omana kokonaisuutena**: voi erottaa omaksi repokseen
|
|
||||||
tulevaisuudessa
|
|
||||||
|
|
||||||
## Tech Stack (POC)
|
- **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.
|
||||||
- **Runtime:** Bash, curl, jq, python3 (retention whiteout)
|
- **Vain Gitea, vain reusable workflowt**: ei custom actioneita, ei multi-platform
|
||||||
- **Alusta:** Gitea Actions, Gitea act runner
|
- **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.
|
||||||
- **Hostaus:** git-pages 0.9.1 (Codeberg), Traefik, cert-manager
|
- **Exit-koodi on ainoa onnistumisen mittari**: Ei pipeä, ei tiedostoheuristiikkaa. ADR 0008.
|
||||||
- **Integraatiot:** Gitea REST API, Gitea Packages
|
- **Raportit git-pagesissa**: HTML selailtavissa, retention automaattinen
|
||||||
|
- **GITHUB_STEP_SUMMARY**: Summary-näkymä raporttilinkeille Gitea 1.27:ssä (forward-compat)
|
||||||
|
|
||||||
## 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> <url> <context>`
|
- Status: `bash scripts/report-status.sh <state> <desc> <context> [suite] [url]`
|
||||||
|
|
||||||
## 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
|
||||||
kuuluu `git-pages/docs/`-alle
|
- Älä käytä commit-status API:a jollei ole raporttia linkitettäväksi (ADR 0007)
|
||||||
- Älä POSTaa commit-status APIin jokaiselle vaiheelle — natiivi riittää
|
- Älä käytä pipeä `run`-komennon viimeisenä — se syö exit-koodin (ADR 0008)
|
||||||
|
|||||||
+37
-17
@@ -1,7 +1,6 @@
|
|||||||
# Architecture — Gitea Actions CI -kirjasto
|
# Architecture — Gitea Actions CI -kirjasto
|
||||||
|
|
||||||
> ⚠️ POC-vaihe. Tämä dokumentti kuvaa suunniteltua arkkitehtuuria.
|
> Normatiivinen lähde: ADR 0004, 0005, 0006, 0007, 0008.
|
||||||
> Normatiivinen lähde: ADR 0004, ADR 0005, `docs/design-rationale.md`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -18,31 +17,52 @@ Kirjasto on Gitea-spesifi. Raportit hallinnoidaan git-pages Helm-chartilla
|
|||||||
|
|
||||||
| Rooli | Kuvaus |
|
| Rooli | Kuvaus |
|
||||||
|-------|--------|
|
|-------|--------|
|
||||||
| **Provider** | `gitea-ci-library` — tarjoaa `build-feature.yml` (lukittu rajapinta) sekä scriptit |
|
| **Provider** | `gitea-ci-library` — tarjoaa reusable workflowt (`config-provider.yml`, `check-version.yml`, `docker-build-push.yml`) ja scriptit |
|
||||||
| **Consumer** | Mikropalveluprojekti — kutsuu `uses:`-direktiivillä, omistaa pipeline-logiikan |
|
| **Consumer** | Mikropalveluprojekti — kutsuu `uses:`-direktiivillä, omistaa pipeline-logiikan. Tämän repon oma toteutus: `example-*`-tiedostot |
|
||||||
|
|
||||||
Tarkemmin: ADR 0005.
|
Tarkemmin: ADR 0005.
|
||||||
|
|
||||||
## Komponentit (POC)
|
## Komponentit
|
||||||
|
|
||||||
| Komponentti | Tila |
|
| Komponentti | Tyyppi | Kuvaus |
|
||||||
|-------------|------|
|
|---|---|---|
|
||||||
| `build-feature.yml` | Toimii. Ainoa reusable workflow. |
|
| `config-provider.yml` | Provider | Lataa + validoi `.conf`-tiedoston, tuottaa `env_json` |
|
||||||
| `publish-git-pages.sh` | Toimii. PATCH tar git-pagesiin. |
|
| `check-version.yml` | Provider | Tarkistaa git-tagit, laskee version, palauttaa `artifact_exists` + `version` |
|
||||||
| `report-status.sh` | Toimii. POSTaa commit-status (vain custom-linkkiin). |
|
| `docker-build-push.yml` | Provider | Buildaa Docker-imagen, puskea rekisteriin, tagittaa commitin |
|
||||||
| `dispatch-workflow.sh` | Toimii. Dispatchee workflown ja pollaa valmistumista. |
|
| `example-feature.yml` | Consumer | Feature-haaran CI: load-config → bats + cucumber → summary |
|
||||||
| `git-pages/` | Helm-chartti raporttien hostaukseen. Oma kokonaisuus, tarkemmin: `git-pages/docs/`. |
|
| `example-main.yml` | Consumer | Main-haaran CI: load-config → check-version → bats + cucumber → summary → docker |
|
||||||
|
| `example-bats-tests.yml` | Consumer | Unit-testit Batsilla |
|
||||||
|
| `example-cucumber-tests.yml` | Consumer | Hyväksymätestit Cucumberilla |
|
||||||
|
| `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, workflow-dispatch, run-pollaus |
|
| Gitea REST API | Commit-status (vain custom-linkit), git-tagit |
|
||||||
| **Gitea Packages** | Docker-imagen säilytys |
|
| Gitea Packages | Docker-imagen säilytys |
|
||||||
| **git-pages** | Raporttien hostaus |
|
| git-pages | Raporttien hostaus |
|
||||||
|
|
||||||
## Arkkitehtuuriset rajoitteet
|
## Arkkitehtuuriset rajoitteet
|
||||||
|
|
||||||
- `build-feature.yml` on ainoa consumerin kutsuma rajapinta (ADR 0005)
|
- Provider-workflowt ovat reusableja (`workflow_call`), consumer omistaa orkestroinnin
|
||||||
- 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,4 +1,5 @@
|
|||||||
**⚠️ STATUS: ALERT DRAFT** — Ei ole validoitu. Voi sisältää virheellisiä tai puutteellisia käytäntöjä.
|
**⚠️ STATUS: OSITTAIN VANHENTUNUT** — Statusraportointi (7) ja exit-koodit (8)
|
||||||
|
on formalisoitu ADR:iin 0007 ja 0008. Loput osiot validioitu POC-ajossa.
|
||||||
|
|
||||||
|
|
||||||
# CI Pipeline Practices
|
# CI Pipeline Practices
|
||||||
|
|||||||
+115
-254
@@ -1,269 +1,130 @@
|
|||||||
# Konfiguraatiomalli — `ci-flow-values.yaml`
|
# Konfiguraatiomalli — `gitea-env.conf`
|
||||||
|
|
||||||
> ⚠️ **POC-vaihe.** Tämä dokumentti on peritty Jenkins-versiosta ja sisältää
|
> Consumer määrittelee ympäristökohtaiset arvot KEY=VALUE-muotoisessa
|
||||||
> Docker-spesifistä legacyä (isContainerBuild, Docker-labelit). POC osoitti,
|
> `.conf`-tiedostossa. Providerin `config-provider.yml` validoi tiedoston
|
||||||
> että provider on agnostinen consumerin build-ekosysteemille.
|
> ja muuntaa sen JSON:ksi (`env_json`), jota kaikki downstream-workflowt
|
||||||
>
|
> 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Miksi redesign
|
## Tiedoston sijainti ja nimi
|
||||||
|
|
||||||
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:
|
Consumerin repossa: `.gitea/workflows/gitea-env.conf` (nimi vapaasti
|
||||||
|
valittavissa — `config-provider.yml`:n `config_path`-input määrittää polun).
|
||||||
|
|
||||||
- `creditentials`-viittaukset korvautuvat Gitea org secrets/variables -mekanismilla
|
Esimerkki (tämän repon dogfood):
|
||||||
- `test-flow`-syntaksi yksinkertaistuu (ei enää JSON-serialisointia env-muuttujiin)
|
```
|
||||||
- Docker/NPM-rekisterit MVP:ssä vain Gitea Packages — factory/adapter-pattern valmiina laajennukselle
|
.gitea/workflows/example-gitea-env.conf
|
||||||
- `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
|
||||||
# ci-flow-values.yaml — projektin juuressa
|
load-config:
|
||||||
#
|
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-file: ci-flow-values.yaml
|
config_path: .gitea/workflows/gitea-env.conf
|
||||||
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`)
|
|
||||||
|
|
||||||
Kaikki Git-, raportointi-, SonarQube- ja deploy-konfiguraatio on `ci-flow-values.yaml`:ssa.
|
## `check-version.yml` — version päättely
|
||||||
|
|
||||||
|
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ää.
|
||||||
|
|||||||
+160
-107
@@ -1,150 +1,195 @@
|
|||||||
# Design Rationale — Gitea Actions CI -kirjasto
|
# Design Rationale — Gitea Actions CI -kirjasto
|
||||||
|
|
||||||
> Miksi kirjasto on rakennettu näin. Arvot, periaatteet ja reunaehdot, joiden varaan arkkitehtuuri nojaa.
|
> Miksi kirjasto on rakennettu näin. Arvot, periaatteet ja reunaehdot, joiden
|
||||||
|
> varaan arkkitehtuuri nojaa.
|
||||||
>
|
>
|
||||||
> Tämä dokumentti on **normatiivinen** — arkkitehtuurin on noudatettava näitä periaatteita. Jos ehdotettu muutos on ristiriidassa rationalen kanssa, rationalen on muututtava ensin.
|
> Tämä dokumentti on **normatiivinen** — arkkitehtuurin on noudatettava näitä
|
||||||
|
> periaatteita. Jos ehdotettu muutos on ristiriidassa rationalen kanssa,
|
||||||
|
> rationalen on muututtava ensin.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Miksi tämä projekti on olemassa
|
## Miksi tämä projekti on olemassa
|
||||||
|
|
||||||
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.
|
Mikropalveluarkkitehtuurissa jokainen palvelu tarvitsee CI-putken: testit,
|
||||||
|
laatutarkistukset, buildin, kontituksen ja julkaisun. Ilman jaettua
|
||||||
|
kirjastoa jokainen tiimi kopioi saman YAML-boilerplaten, tekee omat
|
||||||
|
virheensä ja ylläpitää omaa versiotaan. Ajan myötä putket ajautuvat erilleen
|
||||||
|
— toisessa on `shell: bash`, toisessa ei; toinen käyttää `set -o pipefail`,
|
||||||
|
toinen kadottaa exit-koodin `tee`:hen.
|
||||||
|
|
||||||
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.
|
Tämä kirjasto on se mitä kopioidaan. Se tarjoaa valmiit, testatut,
|
||||||
|
dokumentoidut rakennuspalikat joista jokainen tiimi kokoaa oman putkensa.
|
||||||
Kirjasto ei ole Jenkins-migraatiotyökalu. Se on Gitea Actions -natiivi
|
Palikat ovat Gitea Actionsin `uses:`-direktiivillä kutsuttavia reusable
|
||||||
uudelleensuunnittelu, joka säilyttää Jenkins-version todistetut patternit
|
workflow'ta — ei asennusta, ei runtime-riippuvuutta, ei versiopäivityksiä
|
||||||
mutta hylkää ne osat, jotka olivat sidottuja Jenkinsin arkkitehtuuriin.
|
projekteihin.
|
||||||
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. Hyödynnä natiivia
|
### 1. Palikka-arkkitehtuuri: pieniä, vaihdettavia, yhden vastuun workflow'ta
|
||||||
|
|
||||||
Gitea Actionsin ja Gitean natiiveja ominaisuuksia käytetään aina kun ne
|
Jokainen provider-workflow tekee yhden asian:
|
||||||
riittävät. Uutta kilpailevaa toteutusta ei kirjoiteta, jos toimiva ratkaisu
|
|
||||||
on jo olemassa.
|
|
||||||
|
|
||||||
**Miksi:** Oma toteutus on aina ylläpidettävä, testattava ja
|
| Workflow | Vastuu |
|
||||||
dokumentoitava. Natiivi ominaisuus tulee ilmaiseksi, kehittyy alustan
|
|---|---|
|
||||||
mukana ja on käyttäjälle tuttu.
|
| `config-provider.yml` | Lataa ja validoi konfiguraatio |
|
||||||
|
| `check-version.yml` | Tarkistaa onko commit buildattu, laskee version |
|
||||||
|
| `docker-build-push.yml` | Buildaa, puskea ja tagittaa kontin |
|
||||||
|
|
||||||
**Esimerkkejä:**
|
Mikään workflow ei kutsu toista provider-workflowta. Consumer
|
||||||
- Gitea Actions näyttää jobien statuksen automaattisesti — omaa
|
— siis mikropalvelun oma pipeline-tiedosto — on ainoa paikka joka
|
||||||
commit-status API -kutsua ei tarvita jokaiselle vaiheelle
|
tietää mitä palikoita tarvitaan ja missä järjestyksessä.
|
||||||
- Gitea organization secrets/variables korvaa erillisen credential-hallinnan
|
|
||||||
- Reusable workflow -mekanismi korvaa custom action -runtimen
|
|
||||||
|
|
||||||
### 2. Git-commit on universaali statusnäkymä
|
**Miksi:** Tämä on sama periaate kuin Unix-putkissa tai mikropalveluissa:
|
||||||
|
pieniä, itsenäisiä komponentteja jotka tekevät yhden asian hyvin.
|
||||||
|
Consumer voi vaihtaa yhden palikan toiseen — esimerkiksi Docker-buildin
|
||||||
|
tilalle Maven-paketoinnin — ilman että muut palikat muuttuvat.
|
||||||
|
Ratkaisu ei ole se että kaikki ajetaan, vaan se että jokainen tiimi
|
||||||
|
valitsee mitä tarvitsee. Monoliittinen "kaikki yhdessä" -workflow
|
||||||
|
pakottaisi jokaisen tiimin ajamaan tarpeettomia vaiheita.
|
||||||
|
|
||||||
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.
|
### 2. Gitea ensin — hyödynnä alustaa, älä taistele sitä vastaan
|
||||||
|
|
||||||
**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.
|
Gitea Actions tarjoaa kolme asiaa ilmaiseksi:
|
||||||
|
|
||||||
**Mitä tarkoittaa käytännössä:** Gitea Actions näyttää jobien tilan
|
1. **Jobien visuaalinen status** — jokainen jobi näkyy automaattisesti
|
||||||
(checkmark/risti/spinner) commit-näkymässä automaattisesti. API:a
|
commit-näkymässä checkmarkilla, spinnerillä tai ristillä.
|
||||||
(`/api/v1/repos/{owner}/{repo}/statuses/{sha}`) käytetään vain
|
2. **Cross-job riippuvuudet** — `needs` hoitaa virheiden propagointin:
|
||||||
custom-raporttilinkin välittämiseen. ADR 0004.
|
jos edeltävä jobi feilaa, riippuvat jobit skipataan.
|
||||||
|
3. **Reusable workflow -jakelu** — `uses: org/repo/.gitea/workflows/file.yml@v1`
|
||||||
|
on natiivisti versioitu, skopattu ja välimuistitettu.
|
||||||
|
|
||||||
### 3. Reusable workflow — ei omaa runtimea
|
Kirjasto käyttää näitä kaikkia. Ei omaa tilakonetta, ei custom
|
||||||
|
action -runtimea, ei ulkoista orkestraattoria.
|
||||||
|
|
||||||
Kirjasto jaetaan Gitea Actionsin reusable workflow -mekanismilla. Ei Docker-pohjaisia custom actioneita, ei erillistä ajonaikaista palvelinta.
|
**Esimerkki:** Tool-jobit eivät kutsu commit-status API:a lainkaan.
|
||||||
|
Gitean oma job-status riittää — `success`/`failure`/`running` näkyy
|
||||||
|
automaattisesti. API:a käytetään vain kun tarvitaan **custom-linkki**
|
||||||
|
(testiraporttiin tai Docker registryyn), jota natiivistaatus ei tarjoa.
|
||||||
|
Tämä linjaus on dokumentoitu ADR 0004 ja 0007:ssä.
|
||||||
|
|
||||||
**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.
|
### 3. Status näkyy siellä missä työ tehdään — Git-commitissa
|
||||||
|
|
||||||
**Mitä tä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ä.
|
Kehittäjä työskentelee Gitissä. `git log`, `git blame`, PR-näkymä —
|
||||||
|
nämä ovat päivittäiset työkalut. CI-statuksen kuuluu näkyä siellä,
|
||||||
|
ei erillisessä dashboardissa.
|
||||||
|
|
||||||
### 4. Konfiguraatio kuuluu repoon
|
Gitea Actionsin natiivi job-status tekee tämän automaattisesti:
|
||||||
|
jokainen commit näyttää välittömästi mitkä jobit on ajettu ja millä
|
||||||
|
tuloksella. Testiraportteihin pääsee yhdellä klikkauksella commitin
|
||||||
|
status-kuvakkeesta — koska `report-status.sh` asettaa `target_url`:n
|
||||||
|
osoittamaan suoraan HTML-raporttiin git-pagesissa.
|
||||||
|
|
||||||
Projektikohtainen konfiguraatio (`ci-flow-values.yaml`-tyyppinen tiedosto) asuu mikropalvelun omassa repossa. Reusable workflow lukee sen, ei toisinpäin.
|
Tämä ei ole kosmeettinen yksityiskohta. Se on devops-käytännön
|
||||||
|
ydin: palautesilmukka on lyhin mahdollinen. Commit → build → status
|
||||||
|
näkyy samassa näkymässä jossa kehittäjä jo on.
|
||||||
|
|
||||||
**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.
|
### 4. Exit-koodi on ainoa totuus
|
||||||
|
|
||||||
**Poikkeus:** Infra-tason asetukset (git-pages host, Gitea-instanssin URL)
|
CI-putken jokaisen `run`-stepin onnistuminen määräytyy **vain ja
|
||||||
ovat organisaatiotasolla Gitean organization secrets/variables
|
ainoastaan** exit-koodin perusteella. Ei tiedoston olemassaolon, ei
|
||||||
-mekanismissa. Ne eivät ole repokohtaisia.
|
stdout-tulosteen, ei arvauksen. `0` = ok, kaikki muu = ei ok.
|
||||||
|
|
||||||
### 5. Deterministinen testigraafi, vaiheittainen suoritus
|
Tämä kuulostaa itsestään selvältä, mutta YAML-pipelineissa se rikkoutuu
|
||||||
|
helposti. Pipe (`|`) `tee`:hen syö exit-koodin. Tiedoston olemassaolon
|
||||||
|
tarkistus (`[ -f results.xml ]`) ei kerro testien läpimenosta.
|
||||||
|
|
||||||
Test flow on tunnettu ennen buildin alkua, ja testit ajetaan yksi kerrallaan. Jos steppi epäonnistuu, koko flow pysähtyy.
|
**Käytännössä:** Jokainen `run`-steppi ottaa exit-koodin talteen
|
||||||
|
`$?`-muuttujaan ennen kuin mikään muu komento ehtii muuttaa sitä,
|
||||||
|
ja stepin viimeinen rivi on `exit ${EXIT}`. Pipeä ei käytetä
|
||||||
|
työvaiheen viimeisenä komentona. Ks. ADR 0008.
|
||||||
|
|
||||||
**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.
|
### 5. Pienin mahdollinen pinta-ala
|
||||||
|
|
||||||
**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.
|
Jokainen ylimääräinen riippuvuus on ylimääräinen vikaantumispiste.
|
||||||
|
Kirjaston ainoat riippuvuudet:
|
||||||
|
|
||||||
### 6. Raporttien hallinta erillisellä palvelulla
|
- Gitea Actions (alusta)
|
||||||
|
- `bash`, `curl`, `jq` (ubuntu-latest runnerissa valmiina)
|
||||||
|
- Docker (runnerissa valmiina)
|
||||||
|
- git-pages (raporttien hostaus, erillinen palvelu)
|
||||||
|
|
||||||
Raporttien selailtavuudesta ja elinkaaresta vastaa erillinen palvelu, joka
|
Ei Pythonia, ei Node.js:ää ajonaikaisesti (testit omissa konteissaan).
|
||||||
asennetaan git-pages Helm-chartilla. Raportit ovat julkisia URL:lla
|
Ei tietokantaa. Ei ulkoista tilanhallintaa. Kirjasto on joukko
|
||||||
(osoite tunnettava). URL linkitetään Git-committiin.
|
YAML-tiedostoja ja shell-skriptejä — samat työkalut jotka jokainen
|
||||||
|
devops-ihminen jo osaa.
|
||||||
|
|
||||||
**Miksi:** Jenkins-versiossa linkki Cucumber-raporttiin oli kriittinen
|
### 6. Konfiguraatio repoon, salaisuudet Giteaan
|
||||||
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.
|
|
||||||
|
|
||||||
### 7. Yksi CI-alusta, yksi integraatiopiste
|
Projektikohtainen konfiguraatio (`.gitea/workflows/gitea-env.conf`)
|
||||||
|
asuu mikropalvelun omassa repossa. Kehittäjä omistaa sen — hän tietää
|
||||||
|
mikä on Docker-imagen nimi, mihin registryyn puskea, mikä on
|
||||||
|
testiympäristön URL.
|
||||||
|
|
||||||
Kirjasto tukee vain Giteaa. Ei GitLab-, BitBucket- tai GitHub-abstraktioita.
|
Salaisuudet (tokenit, salasanat) elävät Gitean secrets-mekanismissa,
|
||||||
|
eivät repon tiedostoissa. `secrets: inherit` välittää ne providerin
|
||||||
|
workflow'hun ilman että consumerin tarvitsee tietää mitä salaisuuksia
|
||||||
|
mikäkin provider tarvitsee.
|
||||||
|
|
||||||
**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.
|
Poikkeus: infra-tason asetukset (`GIT_PAGES_URL`, `GITEA_API_URL`)
|
||||||
|
ovat Gitean organization secrets/variables -mekanismissa. Ne eivät
|
||||||
|
ole repokohtaisia.
|
||||||
|
|
||||||
**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.
|
### 7. Consumer omistaa orkestroinnin, provider tarjoaa palikat
|
||||||
|
|
||||||
### 8. Cross-repo commit traceability
|
Tämä on kirjaston tärkein arkkitehtuurinen päätös (ADR 0005).
|
||||||
|
|
||||||
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.
|
Provider (`gitea-ci-library`) ei tiedä mitä testejä ajetaan, missä
|
||||||
|
järjestyksessä, tai millä branchilla. Se tarjoaa kolme reusable
|
||||||
|
workflow'ta ja joukon skriptejä.
|
||||||
|
|
||||||
**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**.
|
Consumer (mikropalvelun `example-feature.yml` / `example-main.yml`)
|
||||||
|
päättää:
|
||||||
|
- Mitkä palikat kutsutaan
|
||||||
|
- Missä järjestyksessä (`needs`)
|
||||||
|
- Millä branch-ehdoilla (`if`)
|
||||||
|
- Mitkä testikontit käytetään (input-parametrit)
|
||||||
|
|
||||||
**Mekanismi:**
|
Tämä on tarkoituksellinen vallanjako. Provider ei voi tietää jokaisen
|
||||||
|
tiimin tarpeita — eikä sen pidäkään. Consumer ei voi muuttaa providerin
|
||||||
|
sisäistä toteutusta — eikä sen pidäkään. Rajapinta on `workflow_call` ja
|
||||||
|
se on molemmille osapuolille selvä.
|
||||||
|
|
||||||
```mermaid
|
### 8. Branch-kohtainen reititys, ei yhtä kaikille
|
||||||
%%{init: {'theme': 'base'}}%%
|
|
||||||
sequenceDiagram
|
|
||||||
participant MR as Mikropalvelu-repo
|
|
||||||
participant HR as Helm-repo
|
|
||||||
participant TR as Testi-repo
|
|
||||||
participant GA as Gitea API
|
|
||||||
|
|
||||||
Note over MR: commit abc123
|
Eri brancheilla on eri tavoite:
|
||||||
Note over MR: build kontti v1.2.3
|
|
||||||
|
|
||||||
MR->>GA: POST dispatch deploy-workflow
|
- **Feature-haara:** Onko koodi laadukasta? → testit, validointi
|
||||||
GA->>HR: workflow käyntiin
|
- **Main-haara:** Onko tästä versiosta jo artifakti? Jos ei →
|
||||||
Note over HR: commit def456
|
testit + build + push + tag. Jos on → ei tehdä mitään (tai
|
||||||
Note over HR: container.version = 1.2.3
|
jatketaan klusteritesteihin).
|
||||||
|
|
||||||
HR->>GA: POST status "deployed by abc123"
|
Tämä logiikka elää consumerin pipeline-tiedostossa, ei providerissa.
|
||||||
GA->>MR: Status: deployed to staging
|
Se on puhdasta `if`-ehtoa ja `needs`-ketjutusta — ei skriptausta,
|
||||||
Note right of MR: URL → def456
|
ei monimutkaisia ehtoja providerin sisällä.
|
||||||
|
|
||||||
HR->>GA: POST status "from abc123"
|
### 9. Raportit erillisellä palvelulla, linkit commitissa
|
||||||
GA->>HR: Status: from abc123
|
|
||||||
Note right of HR: URL → abc123
|
|
||||||
|
|
||||||
MR->>GA: POST dispatch integraatiotestit
|
Gitea Actionsin artifact-järjestelmä on binääriarkisto — ZIP-lataus,
|
||||||
GA->>TR: workflow käyntiin
|
ei HTML-selailtavuutta. Testiraportit (Cucumber HTML, Bats-coverage)
|
||||||
Note over TR: commit ghi789
|
on voitava avata selaimessa yhdellä klikkauksella.
|
||||||
|
|
||||||
TR->>GA: POST status "integration OK"
|
Ratkaisu: git-pages Helm-chartti, joka tarjoaa staattista
|
||||||
GA->>MR: Status: integration OK
|
tiedostohostingia HTTP:llä. `publish-git-pages.sh` vie raportit
|
||||||
Note right of MR: URL → ghi789
|
sinne; `report-status.sh` linkittää commit-statuksen suoraan
|
||||||
|
raporttiin. Retention hoitaa git-pagesin sidecar automaattisesti.
|
||||||
|
|
||||||
TR->>GA: POST status "tested v1.2.3"
|
Tulevaisuudessa `GITHUB_STEP_SUMMARY` (Gitea 1.27+) tarjoaa
|
||||||
GA->>HR: Status: tested v1.2.3
|
vaihtoehtoisen kanavan: jobin Summary-välilehdelle renderöityvä
|
||||||
Note right of HR: URL → def456
|
Markdown-taulukko kaikista raporttilinkeistä.
|
||||||
|
|
||||||
TR->>GA: POST status "tested abc123"
|
### 10. Vain Gitea — ei monialustatukea ilman tarvetta
|
||||||
GA->>MR: Status: tested abc123 (root)
|
|
||||||
Note right of MR: URL → abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
**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.
|
Yhden alustan tukeminen kunnolla on vaikeampaa kuin kolmen tukeminen
|
||||||
|
huonosti. Gitea Actionsin `uses:`-mekanismi, `needs`-semantiikka,
|
||||||
|
`secrets: inherit`, `gitea`-konteksti — nämä ovat alustakohtaisia
|
||||||
|
ominaisuuksia joita abstraktiokerros vain haittaisi.
|
||||||
|
|
||||||
|
Jos toinen alusta tulee ajankohtaiseksi, sille kirjoitetaan oma
|
||||||
|
toteutus. Siihen asti yksi alusta riittää. Ennenaikainen yleistys
|
||||||
|
on devopsissa yhtä haitallista kuin ohjelmistosuunnittelussa.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -152,23 +197,31 @@ sequenceDiagram
|
|||||||
|
|
||||||
### Mitä kirjasto EI tee
|
### Mitä kirjasto EI tee
|
||||||
|
|
||||||
- **Ei ulkoista orkestraattoria.** Test flow -ketjutus perustuu Gitean REST APIin ja workflowhin itseensä. Ei erillistä palvelinta, joka hallinnoi tilaa.
|
- **Ei ulkoista orkestraattoria.** Pipeline-ohjaus on Gitea Actionsin
|
||||||
- **Ei Jenkins-migraatiota.** Vanhaa Jenkinsfileä ei voi ajaa Gitea Actionsissa. Tämä on uusi kirjasto uudella konfiguraatioformaatilla.
|
`needs`-ketjuissa ja consumerin `if`-ehdoissa.
|
||||||
- **Ei reaaliaikaista build-seurantaa.** Commit-statusviestit ovat pollattavia, eivät push-pohjaisia. Gitean UI hoitaa reaaliaikaisuuden.
|
- **Ei custom actioneita.** Reusable workflow on kevyempi, versioitu
|
||||||
- **Ei multi-repo-monorepo-konfiguraatiota.** Jokainen mikropalvelu omistaa oman `ci-flow-values.yaml`:nsa. Jaettua konfiguraatiota ei ole projektitasolla.
|
ja jaeltu Gitean oman mekanismin kautta.
|
||||||
|
- **Ei asennusta projekteihin.** Consumer viittaa `uses:`-direktiivillä
|
||||||
|
suoraan tämän repon workflow-tiedostoihin. Ei npm-pakettia, ei
|
||||||
|
git-submodulea, ei kopioitavia tiedostoja.
|
||||||
|
- **Ei runtime-riippuvuuksia.** Provider-skriptit käyttävät vain
|
||||||
|
työkaluja jotka ovat Gitea Actionsin `ubuntu-latest` runnerissa
|
||||||
|
valmiina: `bash`, `curl`, `jq`.
|
||||||
|
- **Ei monorepo-konfiguraatiota.** Jokainen mikropalvelu omistaa
|
||||||
|
oman pipeline-tiedostonsa ja konfiguraationsa.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Mitä tietoisesti hylättiin
|
## Mitä tietoisesti hylättiin
|
||||||
|
|
||||||
## Mitä tietoisesti hylättiin
|
|
||||||
|
|
||||||
| Hylätty | Syy |
|
| Hylätty | Syy |
|
||||||
|---------|-----|
|
|---|---|
|
||||||
| Multi-Git-platform-tuki (GitLab, BitBucket) | Vain Gitea on relevantti. Abstraktointi ilman tarvetta on turhaa kompleksisuutta |
|
| Monoliittinen "kaikki yhdessä" -workflow | Pakottaa kaikille samat vaiheet. Palikka-arkkitehtuuri antaa jokaiselle tiimille vain mitä se tarvitsee |
|
||||||
| Gitea Packages raporttien hostingiin | Ei tue HTML-selailtavuutta — vain binääriartefaktien lataus |
|
| Oma orkestraattoripalvelin | Ylimääräinen ylläpidettävä. Gitean `needs` ja `if` riittävät |
|
||||||
| Gitea Releases raporttien hostingiin | Saastuttaa release-historian. Satoja CI-raportteja oikeiden julkaisujen seassa |
|
| Docker-pohjaiset custom actionit | Tuovat riippuvuuden Docker-rekisteriin. Reusable workflow on natiivimpi |
|
||||||
| Gitea Pages + reports-branch | Race condition rinnakkaisten buildien pushissa samaan branchiin |
|
| Commit-status API jokaiselle vaiheelle | Duplikointia — Gitea näyttää job-statuksen automaattisesti. API vain custom-linkeille |
|
||||||
| Ulkoinen orkestraattoripalvelin | Ylimääräinen ylläpidettävä. Gitean oma API riittää |
|
| `tee`-putki debug-näkyvyyteen | Syö exit-koodin. stdout ohjataan tiedostoon `>` ilman pipeä |
|
||||||
| Docker-pohjaiset custom actionit | Tuovat riippuvuuden Docker-rekisteriin ja monimutkaistavat jakelua. Otetaan käyttöön vain pakon edessä |
|
| Multi-Git-platform-tuki | Ennenaikaista optimointia ilman tarvetta |
|
||||||
| `repository_dispatch` (webhook) test flow -ketjutukseen | Lisää konfiguraatiota vastaanottaviin repoihin. Suora REST API -kutsu on eksplisiittisempi ja debuggattavampi |
|
| Gitea Packages raporttien hostingiin | Ei HTML-selailtavuutta — vain binäärilataus |
|
||||||
|
| Gitea Pages + reports-branch | Race condition rinnakkaisten pushien kanssa |
|
||||||
|
| `repository_dispatch` ketjutukseen | Lisää konfiguraatiota vastaanottaviin repoihin. Suora API-kutsu eksplisiittisempi |
|
||||||
|
|||||||
+57
-100
@@ -1,8 +1,6 @@
|
|||||||
# 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)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,130 +11,89 @@
|
|||||||
|
|
||||||
**Main success:**
|
**Main success:**
|
||||||
- Kehittäjä avaa commitin Giteassa
|
- Kehittäjä avaa commitin Giteassa
|
||||||
- Näkee statusviestit: "Building...", "Unit tests OK", "Docker build OK", "Docker pushed"
|
- Näkee job-statukset automaattisesti: spinner (käynnissä), checkmark (ok), risti (feilasi)
|
||||||
- Jokainen statusviesti on klikattavissa → vie buildin sivuun tai raporttiin
|
- Testijobit näyttävät statuksen linkillä: "unit-tests Link to Bats reports", "acc-tests Link to Cucumber reports"
|
||||||
- Epäonnistunut steppi näkyy punaisella — kehittäjä klikkaa ja näkee mikä meni vikaan
|
- Klikkaamalla testistatusta kehittäjä pääsee suoraan HTML-raporttiin git-pagesissa
|
||||||
|
- 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 `key`-arvolla
|
- Useampi workflow samalle commitille → statukset erottuvat `context`-avaimella
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## UC2: Kehittäjä lukee testiraportteja selaimessa
|
## UC2: Kehittäjä lukee testiraportteja selaimessa
|
||||||
|
|
||||||
**Actor:** Kehittäjä
|
**Actor:** Kehittäjä
|
||||||
**Precondition:** Build on valmistunut, raportit pushattu Minioon
|
**Precondition:** Build on valmistunut, raportit julkaistu git-pagesiin
|
||||||
|
|
||||||
**Main success:**
|
**Main success:**
|
||||||
- Kehittäjä klikkaa commitin statusviestin URL:ää ("Unit tests OK" → URL)
|
- Kehittäjä klikkaa commitin status-kuvaketta (esim. "unit-tests")
|
||||||
- Selain avautuu, OIDC-kirjautuminen (Gitea-tunnuksilla)
|
- Selain avautuu suoraan HTML-raporttiin git-pagesissa
|
||||||
- Cucumber-raportti renderöityy HTML:nä selaimessa
|
- Bats-raportissa näkyy: testitulokset, code coverage
|
||||||
- Raportissa näkyy: mitkä testit menivät läpi, mitkä epäonnistuivat, stack tracet
|
- Cucumber-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 pushausta) → 404
|
- Raporttia ei ole (testit skipattiin, workflow kaatui ennen julkaisua) → 404
|
||||||
- OIDC-sessio vanhentunut → uudelleenohjaus kirjautumiseen
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## UC3: Kehittäjä selaa projektin build-historiaa
|
## UC3: Kehittäjä vertailee kahden buildin raportteja
|
||||||
|
|
||||||
**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 projektin build-indeksin `{MINIO_BASE}/{repo}/index.html`
|
- Kehittäjä avaa kaksi buildia Gitean Actions-näkymässä eri välilehtiin
|
||||||
- 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ä | MinIO static web hosting |
|
| Raportit selailtavissa HTML:nä | git-pages static hosting |
|
||||||
| Linkki commitista suoraan raporttiin | Statusviestin `url`-kenttä |
|
| Linkki commitista suoraan raporttiin | Commit-status API:n `target_url` |
|
||||||
| Build-indeksi per projekti | Generoitu `index.html` Minioon |
|
| Raporttien pysyvyys | git-pages retention sidecar |
|
||||||
| Navigaatio raporttien välillä | "Back to build" / "Back to builds" — linkit indeksisivuilla |
|
| Virheiden propagointi | Gitea Actions `needs`-ketju |
|
||||||
| Cross-repo-navigaatio | Statusviestit linkittävät repoja ristiin |
|
| Pipeline-pysäytys virhetilanteessa | `needs` automaattinen skip |
|
||||||
| Raporttien pysyvyys | ConfigMap-pohjainen retention policy |
|
| Exit-koodi ainoa totuus | ADR 0008 |
|
||||||
| Autentikointi | OIDC (Traefik middleware, Gitea OAuth2) |
|
| Statusraportointi vain raporttilinkeille | ADR 0007 |
|
||||||
|
|||||||
+1
-11
@@ -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. Tämä vastaa Jenkinsin pod template -konseptia, mutta on yksinkertaisempi:
|
Jokainen job voi määritellä käyttämänsä kontit. Eri jobeilla voi olla eri kontti:
|
||||||
|
|
||||||
### `container:` — ajonaikainen ympäristö
|
### `container:` — ajonaikainen ympäristö
|
||||||
|
|
||||||
@@ -99,13 +99,3 @@ 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ä |
|
|
||||||
|
|||||||
+105
-140
@@ -1,68 +1,125 @@
|
|||||||
# Jaetut skriptit
|
# Jaetut skriptit
|
||||||
|
|
||||||
> ⚠️ **POC-vaihe.** Osa kuvatuista skripteistä (push-reports.sh, tag-commit.sh)
|
> Provider-skriptit asuvat `scripts/`-hakemistossa. Consumer-skriptit
|
||||||
> on suunniteltu mutta ei toteutettu. Toteutetut: `publish-git-pages.sh`,
|
> asuvat `.gitea/scripts/`-hakemistossa. ADR 0006.
|
||||||
> `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 build-statuksen Gitea-commitin REST APIin.
|
POSTaa commit-statuksen Gitea REST APIin. Käytetään **vain** kun tarvitaan
|
||||||
|
custom-linkki (testiraportti, Docker registry). Tool-jobit luottavat
|
||||||
|
Gitean natiiviin job-statukseen. ADR 0007.
|
||||||
|
|
||||||
### Rajapinta
|
### Rajapinta
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
report-status.sh <state> <description> <url> [key] [root_commit] [root_repo]
|
report-status.sh <state> <description> <context> [suite] [custom_url]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parametri | Pakollinen | Kuvaus |
|
| Parametri | Pakollinen | Kuvaus |
|
||||||
|-----------|------------|--------|
|
|---|---|---|
|
||||||
| `state` | Kyllä | `pending`, `success`, `failure`, `error` |
|
| `state` | Kyllä | `pending`, `success`, `failure` |
|
||||||
| `description` | Kyllä | Ihmisluettava kuvaus (esim. "Unit tests passed") |
|
| `description` | Kyllä | Ihmisluettava kuvaus |
|
||||||
| `url` | Kyllä | Linkki buildiin tai raporttiin |
|
| `context` | Kyllä | Uniikki avain (`unit-tests`, `acc-tests`, `ci-docker-build-push`) |
|
||||||
| `key` | Ei | Uniikki avain. Oletus: `commit-{sha_short}` |
|
| `suite` | Ei | Julkaistun raportin suite-nimi → linkki git-pagesiin |
|
||||||
| `root_commit` | Ei | Root-buildin commit-hash (cross-repo-raportointia varten) |
|
| `custom_url` | Ei | Oma URL (ohittaa oletus-URL:n generoinnin) |
|
||||||
| `root_repo` | Ei | Root-buildin repo (cross-repo-raportointia varten) |
|
|
||||||
|
|
||||||
### Kutsuesimerkkejä
|
### Kutsuesimerkkejä
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Buildin aloitus
|
# Testijobi, linkki git-pages-raporttiin
|
||||||
report-status.sh pending "Building..." "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
|
report-status.sh success "Link to Bats reports" unit-tests bats
|
||||||
|
|
||||||
# Testivaihe valmis, linkki raporttiin
|
# Docker build, custom URL registryyn
|
||||||
report-status.sh success "Unit tests OK" "$MINIO_BASE_URL/$GITHUB_REPOSITORY/${GITHUB_SHA::8}/cucumber/overview-features.html" "unit-test"
|
report-status.sh success "Docker build & push 1.2.0 OK" ci-docker-build-push "" \
|
||||||
|
"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 "{
|
-d "{\"state\":\"$STATE\",\"target_url\":\"$URL\",\"description\":\"$DESCRIPTION\",\"context\":\"$CONTEXT\"}"
|
||||||
\"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
|
||||||
|
|
||||||
@@ -73,125 +130,33 @@ 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-tiedoston nimi (esim. `test.yml`) |
|
| `workflow_file` | Kyllä | Workflow-tiedosto (esim. `ci-main.yml`) |
|
||||||
| `ref` | Kyllä | Branch |
|
| `ref` | Kyllä | Branch |
|
||||||
| `inputs_json` | Kyllä | JSON-objekti input-parametreina |
|
| `inputs_json` | Kyllä | JSON-objekti dispatch-inputteina |
|
||||||
| `gitea_api_url` | Kyllä | Gitean API-URL (esim. `https://gitea.example.com`) |
|
| `gitea_api_url` | Kyllä | Gitean API-URL |
|
||||||
| `gitea_token` | Kyllä | Gitea API -token |
|
| `gitea_token` | Kyllä | Gitea API -token (write kohderepoon) |
|
||||||
| `timeout_minutes` | Ei | Oletus: 360 (6 tuntia) |
|
| `timeout_minutes` | Ei | Aikakatkaisu (oletus 360) |
|
||||||
|
|
||||||
### Toiminta
|
### Toiminta
|
||||||
|
|
||||||
1. **Dispatch:** `POST /api/v1/repos/{target_repo}/actions/workflows/{workflow_file}/dispatches`
|
1. **Generoi `dispatch_id`** — 8-hex uniikki tunniste
|
||||||
2. **Etsi run:** `GET /api/v1/repos/{target_repo}/actions/runs?status=running` → etsi uusin (aikaleimasta)
|
2. **Injektoi** `dispatch_id` inputteihin
|
||||||
3. **Poll:** `GET /api/v1/repos/{target_repo}/actions/runs/{run_id}` 10s välein
|
3. **Dispatch:** `POST /api/v1/repos/{target_repo}/actions/workflows/{workflow_file}/dispatches`
|
||||||
4. **Lopeta:** Kun `status == "completed"` → palauta `conclusion` (`success`/`failure`/`cancelled`)
|
4. **Etsi run:** pollaa rinnakkaisia `workflow_dispatch`-runeja, matchaa `display_title` sisältää `dispatch_id`:n
|
||||||
5. **Timeout:** Jos kestää yli `timeout_minutes` → palauta `timeout`
|
5. **Poll:** `GET /api/v1/repos/{target_repo}/actions/runs/{run_id}` — odota valmistumista
|
||||||
|
6. **Palauta:** exit 0 (success), exit 1 (failure), exit 124 (timeout)
|
||||||
### 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` | Org variable | `report-status.sh` |
|
| `GITEA_API_URL` | `env_json` | `report-status.sh`, `ci-validate.sh` |
|
||||||
| `GITEA_TOKEN` | Org secret | `report-status.sh`, `tag-commit.sh` |
|
| `GIT_PAGES_URL` | `env_json` | `publish-git-pages.sh`, `report-status.sh` |
|
||||||
| `MINIO_BASE_URL` | Org variable | `push-reports.sh` |
|
| `GITEA_TOKEN` | Gitea secret | `report-status.sh`, `check-version.yml`, `docker-build-push.yml` |
|
||||||
| `MINIO_ACCESS_KEY` | Org secret | `push-reports.sh` |
|
| `GIT_PAGES_PUBLISH_TOKEN` | Gitea secret | `publish-git-pages.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_SERVER_URL` | Automaattinen | `report-status.sh` |
|
| `GITHUB_RUN_ID` | Automaattinen | `report-status.sh` |
|
||||||
| `GITHUB_RUN_ID` | Automaattinen | `report-status.sh`, `tag-commit.sh` |
|
| `GITHUB_RUN_NUMBER` | Automaattinen | `docker-build-push.yml` (tag-commit) |
|
||||||
| `GITHUB_RUN_NUMBER` | Automaattinen | `tag-commit.sh` |
|
|
||||||
| `GITHUB_ACTOR` | Automaattinen | Docker-labelit |
|
|
||||||
|
|||||||
+11
-9
@@ -1,7 +1,6 @@
|
|||||||
# Tech Stack — Gitea Actions CI -kirjasto
|
# Tech Stack — Gitea Actions CI -kirjasto
|
||||||
|
|
||||||
> ⚠️ POC-vaihe. Osa teknologiavalinnoista voi muuttua uudelleenkirjoituksen
|
> Katso myös `git-pages/docs/tech-stack.md`.
|
||||||
> myötä. Katso myös `git-pages/docs/tech-stack.md`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -21,19 +20,22 @@ 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, workflow-dispatch, branch-listaus (retention) |
|
| **Gitea REST API** | `/api/v1/` | Commit-status, git-tagit, workflow-dispatch |
|
||||||
| **git-pages** | HTTP | Raporttien hostaus |
|
| **git-pages** | HTTP (PATCH tar) | Raporttien hostaus |
|
||||||
| **Gitea Packages** | Container registry API | Docker-imagen push |
|
| **Gitea Packages** | Container registry API | Docker-imagen push |
|
||||||
|
|
||||||
## Mitä EI tueta (verrattuna Jenkins-versioon)
|
## Mitä EI tueta
|
||||||
|
|
||||||
| Teknologia | Syy |
|
| Teknologia | Syy |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **MinIO** | Korvattu git-pagesilla |
|
| **Multi-Git-platform** | Vain Gitea — yksi alusta kunnolla (periaate 10) |
|
||||||
| **Multi-Git-platform** | Vain Gitea |
|
| **Custom actionit** | Reusable workflow on kevyempi ja natiivimpi (periaate 2) |
|
||||||
| **Jenkins** (shared library, plugins) | Gitea Actions korvaa |
|
| **Ulkoinen orkestraattori** | Gitean `needs` + `if` hoitaa ohjauksen |
|
||||||
| **Artifactory/Nexus** | MVP:ssä ei, factory/adapter-pattern valmiina |
|
| **Artifactory/Nexus** | Build & push toimii Docker-standardilla. UI-tason linkitys (`report-summary`) vaatii Nexus/Artifactory-spesifin URL-rakenteen — ei vielä toteutettu, toteutetaan tarvittaessa |
|
||||||
|
|||||||
+221
-313
@@ -1,390 +1,298 @@
|
|||||||
# Reusable workflowt
|
# Reusable workflowt
|
||||||
|
|
||||||
> ⚠️ **POC-vaihe.** Toteutettu: `quality-gate.yml`. Suunnitteilla:
|
> Provider-workflowt tarjoavat ydintoiminnallisuuden. Consumer kokoaa ne
|
||||||
> `ci-master.yml`, `deploy.yml`, `test.yml`.
|
> haluamakseen pipelineksi. Esimerkkitoteutus: `example-*`-tiedostot.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Yhteiset konventiot
|
## Yhteiset konventiot
|
||||||
|
|
||||||
Kaikki workflowt:
|
Kaikki workflowt:
|
||||||
- Käyttävät `concurrency:`-ryhmää estämään saman branchin rinnakkaiset ajot (vastaa Jenkins `disableConcurrentBuilds()`)
|
- Käyttävät `concurrency:`-ryhmää estämään saman branchin rinnakkaiset ajot
|
||||||
- Lukevat konfiguraation `ci-flow-values.yaml`:sta
|
- Provider-workflowt lukevat konfiguraation inputtina (`env_json`)
|
||||||
- Raportoivat jokaisen vaiheen Gitea-commitin statukseen `report-status.sh`:lla
|
- Statusraportointi: tool-jobit natiivilla, test-jobit API:lla raporttilinkin takia (ADR 0007)
|
||||||
- Käyttävät projektilta saatuja `with:`-parametreja konttien määrittelyyn (kirjasto ei pakota konttiversioita)
|
- Exit-koodi aina ylös, ei pipeä (ADR 0008)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `quality-gate.yml` — Merge-portti
|
## Provider-workflowt
|
||||||
|
|
||||||
**Trigger:** `workflow_call` — consumer kutsuu `uses:`-direktiivillä
|
### `config-provider.yml` — Konfiguraation lataus ja validointi
|
||||||
|
|
||||||
**Rooli:** Laatuportti, joka ajetaan branch protection -sääntönä ennen PR:n
|
**Trigger:** `workflow_call`
|
||||||
sulkemista mainiin. Pipeline on ajettava (`run > 1`) eikä yhtään jobia
|
|
||||||
saa failata.
|
|
||||||
|
|
||||||
**Provider-Consumer-malli (ADR 0005):** Provider tarjoaa orkestroinnin
|
**Inputs:**
|
||||||
(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.
|
|
||||||
|
|
||||||
### Inputs (providerin rajapinta)
|
| Parametri | Pakollinen | Kuvaus |
|
||||||
|
|-----------|------------|--------|
|
||||||
|
| `config_path` | Kyllä | Polku `.conf`-tiedostoon |
|
||||||
|
|
||||||
| Parametri | Pakollinen | Tyyppi | Kuvaus |
|
**Secrets:**
|
||||||
|-----------|------------|--------|--------|
|
|
||||||
| `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ä | Gitea API-kutsuihin (commit-status) |
|
| `GITEA_TOKEN` | Kyllä | Validointia varten |
|
||||||
| `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | Raporttien julkaisuun git-pagesiin |
|
| `GIT_PAGES_PUBLISH_TOKEN` | Kyllä | Validointia varten |
|
||||||
|
|
||||||
### Steppi-kaavio (Java-esimerkki)
|
**Outputs:**
|
||||||
|
|
||||||
```mermaid
|
| Output | Kuvaus |
|
||||||
%%{init: {'theme': 'base', 'flowchart': {'arrowheadScale': 2}}}%%
|
|--------|--------|
|
||||||
flowchart TD
|
| `env_json` | JSON-muotoiset ympäristömuuttujat |
|
||||||
VAL["validate
|
| `config_path` | Sama polku takaisin (DRY downstream-käyttöön) |
|
||||||
provider: tarkista
|
|
||||||
CI-konfiguraatio"] --> TEST["test
|
|
||||||
consumer: mvn test
|
|
||||||
→ testiraportit + coverage"]
|
|
||||||
|
|
||||||
VAL --> AI_SCAN["ai-scan \[optional\]
|
**Steppi-kaavio:**
|
||||||
consumer: tietoturva-
|
```
|
||||||
tai laatu-skannaus"]
|
checkout → validate CI config → parse conf to JSON
|
||||||
|
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Consumerin omat stepit (test, sonarqube, ai-scan) ovat esimerkki.
|
### `check-version.yml` — Version ja artifactin tarkistus
|
||||||
Vastaava rakenne toimii millä tahansa kielellä tai työkalulla.
|
|
||||||
|
|
||||||
### Optionaaliset laatu- ja tietoturvaskannaukset
|
**Trigger:** `workflow_call` — käytetään vain main-haarassa
|
||||||
|
|
||||||
Consumer voi lisätä pipelineen omia skannaussteppejä testien rinnalle.
|
**Inputs:** `env_json`
|
||||||
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.
|
|
||||||
|
|
||||||
```mermaid
|
**Outputs:** `artifact_exists` (true/false), `version` (string)
|
||||||
%%{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"]
|
|
||||||
|
|
||||||
SAST --> PUB
|
**Steppi-kaavio:**
|
||||||
SCA --> PUB
|
```
|
||||||
SECRETS --> PUB
|
checkout → laske versio package.json + git-tageista → output
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
| Kategoria | Esimerkki | Kuvaus |
|
### `docker-build-push.yml` — Docker build & push
|
||||||
|-----------|-----------|--------|
|
|
||||||
| **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 |
|
|
||||||
|
|
||||||
### Error handling
|
**Trigger:** `workflow_call`
|
||||||
|
|
||||||
Providerin julkaisu- ja status-stepit käyttävät `if: always()`-ehtoa,
|
**Inputs:**
|
||||||
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.
|
|
||||||
|
|
||||||
### Merge-portti
|
| Parametri | Pakollinen | Kuvaus |
|
||||||
|
|-----------|------------|--------|
|
||||||
|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
|
||||||
|
| `version` | Kyllä | Version string (check-version output) |
|
||||||
|
|
||||||
Branch protection -säännössä Giteassa vaaditaan ennen PR:n sulkemista:
|
**`env_json`-avaimet:**
|
||||||
- **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
|
|
||||||
|
|
||||||
### Optionaalinen PR-ympäristö (preview app)
|
| Avain | Pakollinen | Kuvaus |
|
||||||
|
|-------|------------|--------|
|
||||||
|
| `DOCKER_REGISTRY` | Kyllä | Registry (esim. `gitea.app.keskikuja.site/niko`) |
|
||||||
|
| `DOCKER_IMAGE_NAME` | Kyllä | Kuvan nimi ilman registry-polkua |
|
||||||
|
| `DOCKER_UI_URL` | Ei | Registry UI -linkki raportointia varten |
|
||||||
|
| `DOCKERFILE` | Ei | Dockerfile-polku, oletus `Dockerfile` |
|
||||||
|
| `GITEA_API_URL` | Kyllä | Gitean API-URL |
|
||||||
|
| `GIT_TAG_PREFIX` | Ei | Tag-prefix (esim. `docker/`) |
|
||||||
|
|
||||||
Consumer voi halutessaan buildata kontin ja deployata sen väliaikaiseen
|
**Secrets:** `GITEA_TOKEN`, `DOCKER_USERNAME`, `DOCKER_PASSWORD`
|
||||||
PR-ympäristöön. Tämä on optionaalinen continuation-haara, joka
|
|
||||||
aktivoituu ehdolla:
|
|
||||||
|
|
||||||
- PR:ssä on tietty label (esim. `preview`)
|
**Steppi-kaavio:**
|
||||||
- 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Quality-gate läpäisty (testit + skannaukset ok)
|
**Huomio:** Ei käytä `container:`-direktiiviä — ajaa suoraan runnerilla,
|
||||||
2. Buildaa kontti, tagi sisältää PR-numeron (`pr-42`)
|
joten `actions/checkout` toimii ilman node-asennuksia.
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `ci-master.yml` — Main-branch build
|
### `helm-build-push.yml` — Helm chart build & push
|
||||||
|
|
||||||
**Trigger:** `workflow_call` — kutsutaan main-branchiin pushattaessa
|
**Trigger:** `workflow_call`
|
||||||
|
|
||||||
**Rooli:** Buildaa artifaktin (kontti, JAR, npm-paketti tms.) ja julkaisee
|
**Inputs:**
|
||||||
sen rekisteriin. Jos sama commit on jo buildattu (version tag on olemassa),
|
|
||||||
build skipataan ja siirrytään suoraan test flow'hun.
|
|
||||||
|
|
||||||
**Provider-Consumer-malli (ADR 0005):** Provider orkestroi idempotent
|
| Parametri | Pakollinen | Kuvaus |
|
||||||
build-logiikan (`isArtifactBuilt`-tarkistus), mutta consumer omistaa
|
|-----------|------------|--------|
|
||||||
build-stepit — valitsee työkalut ja artifaktityypin.
|
| `env_json` | Kyllä | Konffi `gitea-env.conf`:stä |
|
||||||
|
| `version` | Kyllä | Version string (check-version output) |
|
||||||
|
| `chart_path` | Ei | Polku Chart.yaml-hakemistoon, oletus `.` |
|
||||||
|
|
||||||
### isArtifactBuilt-check
|
**`env_json`-avaimet:**
|
||||||
|
|
||||||
Ennen buildia tarkistetaan, onko tälle commitille jo olemassa versiotagi:
|
| Avain | Pakollinen | Kuvaus |
|
||||||
|
|-------|------------|--------|
|
||||||
|
| `HELM_REGISTRY` | Kyllä | OCI-registry (esim. `gitea.app.keskikuja.site/niko`) |
|
||||||
|
| `HELM_UI_URL` | Ei | Registry UI -linkki raportointia varten |
|
||||||
|
| `GITEA_API_URL` | Kyllä | Gitean API-URL |
|
||||||
|
| `GIT_TAG_PREFIX` | Ei | Tag-prefix (esim. `helm/`) |
|
||||||
|
|
||||||
```bash
|
**Secrets:** `GITEA_TOKEN`, `HELM_USER`, `HELM_PASSWORD`
|
||||||
TAG=$(git tag --points-at HEAD | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
|
|
||||||
if [ -n "$TAG" ]; then
|
**Steppi-kaavio:**
|
||||||
echo "artifact_already_built=true" >> $GITHUB_ENV
|
```
|
||||||
echo "artifact_version=$TAG" >> $GITHUB_ENV
|
build-push (helm package → helm push OCI) → tag-commit (git-tagin luonti)
|
||||||
fi
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Jos tagi löytyy, build- ja push-stepit skipataan. Committia vastaan on
|
**Steppien kuvaus `build-push`-jobissa:**
|
||||||
jo olemassa artifakti rekisterissä — uudelleenbuildaus aiheuttaisi
|
1. **Node.js-asennus** — `apk add --no-cache nodejs` (vaaditaan `actions/checkout`-actionia varten)
|
||||||
versiokonflikteja ja tuhlaisi CI-aikaa.
|
2. **Checkout** — sovellusrepo ja gitea-ci-library `.ci/`-polkuun
|
||||||
|
3. **Package** — `helm package` versiolla `$VERSION`
|
||||||
|
4. **Push OCI** — `helm push` registryyn autentikoinnilla
|
||||||
|
5. **Report status** — commit-status + UI-linkki
|
||||||
|
|
||||||
### Steppi-kaavio
|
**Kompromissi:** Kontti `alpine/helm` ei sisällä node.js:ää, mutta
|
||||||
|
`actions/checkout@v4` on JavaScript-action ja vaatii sen. Siksi nodejs
|
||||||
```mermaid
|
asennetaan lennossa ennen checkouttia. Tämä vaatii internet-yhteyden
|
||||||
%%{init: {'theme': 'base', 'flowchart': {'arrowheadScale': 2}}}%%
|
eikä toimi air gap -ympäristössä. Korvaa tarvittaessa custom-kontilla
|
||||||
flowchart TD
|
(jossa helm + nodejs, ks. `skills/ci-container-build/SKILL.md`).
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `deploy.yml` — GitOps-deployment
|
### `gitops-dispatch.yml` — GitOps-päivityksen dispatch
|
||||||
|
|
||||||
**Trigger:** `workflow_dispatch` (aina dispatchataan toisesta workflow'sta)
|
**Trigger:** `workflow_call`
|
||||||
|
|
||||||
**Elinkaari:**
|
**Inputit:**
|
||||||
|
|
||||||
|
| Parametri | Pakollinen | Kuvaus |
|
||||||
|
|-----------|------------|--------|
|
||||||
|
| `env_json` | Kyllä | Konffi, josta luetaan `GITOPS_FILE`, `GITOPS_YQ_TPL`, `GITOPS_REPO`, `GIT_TAG_PREFIX` |
|
||||||
|
| `version` | Kyllä | Päivitettävä versio (check-version output) |
|
||||||
|
| `component` | Kyllä | `chart` tai `container` — tunniste summary-riville |
|
||||||
|
|
||||||
|
**Secretit:** `GITOPS_TOKEN`
|
||||||
|
|
||||||
|
**Outputit:** `summary` — pipe-formaatti: `{component}|{version}|{status}|{commit_sha}|{repo}`
|
||||||
|
|
||||||
|
**Steppi-kaavio:**
|
||||||
```
|
```
|
||||||
start → read-yaml → update-value → commit → push → report-cross-repo → end
|
checkout → gitops-dispatch.sh → dispatch-workflow.sh → GITOPS_SUMMARY output
|
||||||
```
|
|
||||||
|
|
||||||
### 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `test.yml` — Test flow -steppi
|
## Consumer-esimerkki (`example-*`)
|
||||||
|
|
||||||
**Trigger:** `workflow_dispatch` (dispatchataan deploy-workflow'n jälkeen)
|
### `example-feature.yml` — Feature-haaran CI
|
||||||
|
|
||||||
**Elinkaari:**
|
**Trigger:** `push` [branches-ignore: main]
|
||||||
|
|
||||||
```
|
```
|
||||||
start → version-check → run-tests → push-reports → report-cross-repo → end
|
load-config → bats + cucumber → report-summary (always)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Inputs (dispatch-parametrit)
|
### `example-main.yml` — Main-haaran CI
|
||||||
|
|
||||||
| Parametri | Kuvaus |
|
**Trigger:** `push` [branches: main]
|
||||||
|-----------|--------|
|
|
||||||
| `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 ───────────────────────────────────────────────────────┐
|
||||||
Ennen testejä varmistetaan, että ympäristössä pyörii oikea versio:
|
load-config-helm ───────────────────────────────────────────┐ │
|
||||||
|
│ │
|
||||||
```yaml
|
check-version ←─────────────────────────────────────────────┘ │
|
||||||
- name: Check deployed version
|
│ │
|
||||||
if: inputs.versionCheckScript || inputs.versionApiUrl
|
└→ bats + cucumber │
|
||||||
run: |
|
├─ docker-build-push → gitops-container ─┐ │
|
||||||
if [ -n "${{ inputs.versionCheckScript }}" ]; then
|
└─ helm-build-push → gitops-chart ──────┤ │
|
||||||
bash "${{ inputs.versionCheckScript }}" "${{ inputs.versionApiUrl }}" "${{ inputs.version }}"
|
├→ report-summary ←┘
|
||||||
fi
|
tag-maintenance ←────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
Version check -skripti pollaa Fibonacci-backoffilla — ks. [config-model.md](config-model.md).
|
GitOps-jobit (`gitops-chart`, `gitops-container`) käyttävät
|
||||||
|
`gitops-dispatch.yml`-provider-workflowia. Kaksisuuntainen track:
|
||||||
|
dispatch-workflow.sh → GITOPS_COMMIT + GITOPS_SUMMARY.
|
||||||
|
Katso [skills/gitops-update/SKILL.md](../skills/gitops-update/SKILL.md).
|
||||||
|
|
||||||
### Cross-repo-raportointi
|
### `example-bats-tests.yml` — Bats unit-testit
|
||||||
|
|
||||||
Testien jälkeen raportoidaan kolmeen committiin:
|
**Trigger:** `workflow_call`
|
||||||
|
|
||||||
1. Testi-repon oma commit: testin status
|
Ajaa Bats-testit Docker-kontissa, generoi coveragen (`bashcov`), julkaisee
|
||||||
2. Mikropalvelun commit (`root_commit`): "testit OK/epäonnistui"
|
raportit git-pagesiin, asettaa commit-statuksen linkillä raporttiin.
|
||||||
3. Helm-repon commit (`deploy_commit`): "testattu v{version}"
|
|
||||||
|
|
||||||
### Concurrency
|
### `example-cucumber-tests.yml` — Cucumber hyväksymätestit
|
||||||
|
|
||||||
```yaml
|
**Trigger:** `workflow_call`
|
||||||
concurrency:
|
|
||||||
group: test-${{ inputs.environment }}
|
Ajaa Cucumber-testit Node-kontissa, julkaisee raportit git-pagesiin, asettaa
|
||||||
cancel-in-progress: false
|
commit-statuksen linkillä raporttiin.
|
||||||
```
|
|
||||||
|
### `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).
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ curl_with_host() {
|
|||||||
declare -A BRANCH_CACHE
|
declare -A BRANCH_CACHE
|
||||||
branch_exists() {
|
branch_exists() {
|
||||||
local owner="$1" repo="$2" branch="$3" key="${owner}/${repo}/${branch}"
|
local owner="$1" repo="$2" branch="$3" key="${owner}/${repo}/${branch}"
|
||||||
local status
|
local status attempt
|
||||||
|
|
||||||
[ -z "$GITEA_API_URL" ] && return 0
|
[ -z "$GITEA_API_URL" ] && return 0
|
||||||
[ -z "$GITEA_TOKEN" ] && return 0
|
[ -z "$GITEA_TOKEN" ] && return 0
|
||||||
@@ -25,6 +25,8 @@ branch_exists() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Retry up to 2 times on API errors (hardcoded)
|
||||||
|
for attempt in 1 2 3; do
|
||||||
status=$(curl -sS -o /dev/null -w "%{http_code}" \
|
status=$(curl -sS -o /dev/null -w "%{http_code}" \
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
"${GITEA_API_URL}/api/v1/repos/${owner}/${repo}/branches/${branch}" 2>/dev/null || echo "000")
|
"${GITEA_API_URL}/api/v1/repos/${owner}/${repo}/branches/${branch}" 2>/dev/null || echo "000")
|
||||||
@@ -33,7 +35,22 @@ branch_exists() {
|
|||||||
BRANCH_CACHE[$key]=1
|
BRANCH_CACHE[$key]=1
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$status" = "404" ]; then
|
||||||
return 1
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# API error - retry if not last attempt
|
||||||
|
if [ "$attempt" -lt 3 ]; then
|
||||||
|
sleep 10
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# All retries failed - keep report (fail-safe)
|
||||||
|
echo " WARN: Gitea API error for ${owner}/${repo}/${branch} (status ${status}) after 3 attempts - KEEPING report"
|
||||||
|
BRANCH_CACHE[$key]=1
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
default_max_age=$(jq -r '.branches.default.maxAgeDays // 90' "$CONFIG")
|
default_max_age=$(jq -r '.branches.default.maxAgeDays // 90' "$CONFIG")
|
||||||
@@ -159,46 +176,90 @@ if [ "${#TO_DELETE[@]}" -eq 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Phase 4: whiteout deletion ==="
|
echo "=== Phase 4: full site rebuild ==="
|
||||||
echo "Creating whiteout tar for ${#TO_DELETE[@]} report(s)..."
|
echo "Rebuilding site (${#TO_DELETE[@]} report(s) to delete)..."
|
||||||
|
|
||||||
WHITEOUT_TAR=$(mktemp)
|
ARCHIVE_FILE=$(mktemp)
|
||||||
trap 'rm -f "$WHITEOUT_TAR"' EXIT
|
SITE_DIR=$(mktemp -d)
|
||||||
|
NEW_TAR=$(mktemp)
|
||||||
|
cleanup_phase4() {
|
||||||
|
rm -f "$ARCHIVE_FILE" "$NEW_TAR"
|
||||||
|
rm -rf "$SITE_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup_phase4 EXIT
|
||||||
|
|
||||||
python3 -c "
|
# Try archive.tar first
|
||||||
import tarfile, sys
|
echo "Downloading archive.tar..."
|
||||||
|
HTTP_CODE=$(curl_with_host -o "$ARCHIVE_FILE" -w "%{http_code}" -sS "${PAGES_URL}/.git-pages/archive.tar")
|
||||||
|
|
||||||
tar = tarfile.open(name='${WHITEOUT_TAR}', mode='w')
|
if [ "$HTTP_CODE" = "200" ] && tar -tf "$ARCHIVE_FILE" >/dev/null 2>&1; then
|
||||||
|
echo "Extracting archive..."
|
||||||
|
tar -xf "$ARCHIVE_FILE" -C "$SITE_DIR"
|
||||||
|
|
||||||
dirs = set()
|
for dir in "${TO_DELETE[@]}"; do
|
||||||
for d in sys.argv[1:]:
|
if [ -d "$SITE_DIR/$dir" ]; then
|
||||||
dirs.add(d.strip())
|
echo " Removing: $dir"
|
||||||
|
rm -rf "$SITE_DIR/$dir"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "archive.tar failed (HTTP ${HTTP_CODE}) - falling back to manifest-based rebuild"
|
||||||
|
|
||||||
tarinfo = tarfile.TarInfo()
|
ALL_PATHS=$(echo "$MANIFEST" | jq -r '.contents | keys[]' 2>/dev/null || true)
|
||||||
tarinfo.type = tarfile.CHRTYPE
|
|
||||||
tarinfo.devmajor = 0
|
|
||||||
tarinfo.devminor = 0
|
|
||||||
|
|
||||||
for d in sorted(dirs, key=len, reverse=True):
|
if [ -z "$ALL_PATHS" ]; then
|
||||||
info = tarinfo
|
echo "ERROR: no files in manifest - cannot rebuild" >&2
|
||||||
info.name = d
|
exit 1
|
||||||
tar.addfile(info)
|
fi
|
||||||
|
|
||||||
tar.close()
|
EXCLUDE_GREP=""
|
||||||
" "${TO_DELETE[@]}"
|
for dir in "${TO_DELETE[@]}"; do
|
||||||
|
EXCLUDE_GREP="${EXCLUDE_GREP}${EXCLUDE_GREP:+|}^${dir}/"
|
||||||
|
done
|
||||||
|
|
||||||
echo "Patching ${PAGES_URL}/ with whiteout tar..."
|
if [ -n "$EXCLUDE_GREP" ]; then
|
||||||
HTTP_CODE=$(curl_with_host -X PATCH "${PAGES_URL}/" \
|
KEEP_PATHS=$(echo "$ALL_PATHS" | grep -v -E "$EXCLUDE_GREP" || true)
|
||||||
|
else
|
||||||
|
KEEP_PATHS="$ALL_PATHS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$KEEP_PATHS" ]; then
|
||||||
|
echo "No files to keep - site will be empty"
|
||||||
|
mkdir -p "$SITE_DIR/__placeholder__"
|
||||||
|
echo "placeholder" > "$SITE_DIR/__placeholder__/index.html"
|
||||||
|
else
|
||||||
|
FILE_COUNT=$(echo "$KEEP_PATHS" | wc -l | tr -d ' ')
|
||||||
|
echo "Downloading ${FILE_COUNT} file(s)..."
|
||||||
|
while IFS= read -r path; do
|
||||||
|
[ -z "$path" ] && continue
|
||||||
|
dir=$(dirname "$SITE_DIR/$path")
|
||||||
|
mkdir -p "$dir"
|
||||||
|
curl_with_host -o "$SITE_DIR/$path" -sS "${PAGES_URL}/${path}" || {
|
||||||
|
echo " WARN: failed to download ${path}"
|
||||||
|
}
|
||||||
|
done <<< "$KEEP_PATHS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$(ls -A "$SITE_DIR" 2>/dev/null)" ]; then
|
||||||
|
echo "Site is empty - creating placeholder"
|
||||||
|
mkdir -p "$SITE_DIR/__placeholder__"
|
||||||
|
echo "placeholder" > "$SITE_DIR/__placeholder__/index.html"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tar -cf "$NEW_TAR" -C "$SITE_DIR" .
|
||||||
|
|
||||||
|
echo "PUT: replacing site contents..."
|
||||||
|
HTTP_CODE=$(curl_with_host -X PUT "${PAGES_URL}/" \
|
||||||
-H "Content-Type: application/x-tar" \
|
-H "Content-Type: application/x-tar" \
|
||||||
-H "Atomic: no" \
|
--data-binary @"${NEW_TAR}" \
|
||||||
--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" = "204" ]; then
|
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "204" ]; then
|
||||||
echo "Retention cleanup finished."
|
echo "Site rebuild completed."
|
||||||
else
|
else
|
||||||
echo "ERROR: retention HTTP ${HTTP_CODE}" >&2
|
echo "ERROR: PUT HTTP ${HTTP_CODE}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -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, post-upgrade
|
"helm.sh/hook": post-install
|
||||||
"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,6 +32,16 @@ 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__"
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Docker Registry Setup
|
||||||
|
|
||||||
|
Pipeline rakentaa Docker-kontin ja pushee sen haluttuun registryyn.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Konfiguroi `gitea-env.conf`
|
||||||
|
|
||||||
|
```
|
||||||
|
# DOCKER_REGISTRY on muotoa: registry.example.com/org
|
||||||
|
#
|
||||||
|
# host+org: registry.example.com/org
|
||||||
|
#
|
||||||
|
# Pipeline rakentaa kuvan: ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${VERSION}
|
||||||
|
|
||||||
|
DOCKER_REGISTRY=gitea.app.keskikuja.site/niko # PAKOLLINEN — tyhjä ei käy
|
||||||
|
DOCKER_IMAGE_NAME=gitea-ci-library-test-image # PAKOLLINEN — pelkkä kuvan nimi
|
||||||
|
DOCKER_UI_URL= # valinnainen — tarkista Giteasta kontin oma UI-osoite ja laita se tähän ilman versiota. Workflow liittää perään /VERSION
|
||||||
|
```
|
||||||
|
|
||||||
|
| Kenttä | Pakollinen | Kuvaus |
|
||||||
|
|---|---|---|
|
||||||
|
| `DOCKER_REGISTRY` | **kyllä** | Registry + mahdollinen organisaatio. **Tyhjä pysäyttää workflow'n.** |
|
||||||
|
| `DOCKER_IMAGE_NAME` | **kyllä** | Pelkkä kuvan nimi. |
|
||||||
|
| `DOCKER_UI_URL` | ei | Base-URL kontin UI-sivulle (ilman versiota). Osoite riippuu onko kontti linkitetty repoon vai ei — tarkista Giteasta. Workflow liittää perään `/VERSION`. |
|
||||||
|
|
||||||
|
**Koko image-ref = `${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${VERSION}`**
|
||||||
|
Esim. `gitea.app.keskikuja.site/niko/gitea-ci-library-test-image:0.1.0`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 `docker 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 |
|
||||||
|
|---|---|
|
||||||
|
| `DOCKER_PASSWORD` | Edellisessä vaiheessa luotu PAT |
|
||||||
|
|
||||||
|
`DOCKER_USERNAME`-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. Artifactory, Docker Hub), lisää myös:
|
||||||
|
|
||||||
|
| Secret | Arvo |
|
||||||
|
|---|---|
|
||||||
|
| `DOCKER_USERNAME` | Registryn käyttäjätunnus |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Tarkistuslista ennen ajoa
|
||||||
|
|
||||||
|
- [ ] `DOCKER_REGISTRY` asetettu `gitea-env.conf`issa
|
||||||
|
- [ ] `DOCKER_IMAGE_NAME` asetettu `gitea-env.conf`issa
|
||||||
|
- [ ] PAT luotu Giteassa scopella `package` Read and Write
|
||||||
|
- [ ] `DOCKER_PASSWORD`-secret tallennettu repositoryn Secretsiin (se PAT)
|
||||||
|
- [ ] (tarvittaessa) `DOCKER_USERNAME`-secret — oletus `github.actor`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Esimerkkejä eri polkurakenteista
|
||||||
|
|
||||||
|
### 5a. Pelkkä hosti — Artifactory
|
||||||
|
|
||||||
|
```
|
||||||
|
DOCKER_REGISTRY=ngdo-docker.artifactorypro.shared.pub.tds.tieto.com
|
||||||
|
DOCKER_IMAGE_NAME=microservice-temperature-store
|
||||||
|
DOCKER_UI_URL=https://artifactorypro.shared.pub.tds.tieto.com/ui/repos/tree/General/ngdo-docker.artifactorypro.shared.pub.tds.tieto.com/microservice-temperature-store
|
||||||
|
```
|
||||||
|
|
||||||
|
- Kontti: `ngdo-docker.../microservice-temperature-store:0.1.0`
|
||||||
|
- Secret `DOCKER_USERNAME` = service account -tunnus
|
||||||
|
- Secret `DOCKER_PASSWORD` = API-token
|
||||||
|
|
||||||
|
### 5b. Hosti + org — Gitea user-taso
|
||||||
|
|
||||||
|
```
|
||||||
|
DOCKER_REGISTRY=gitea.app.keskikuja.site/niko
|
||||||
|
DOCKER_IMAGE_NAME=gitea-ci-library-test-image
|
||||||
|
DOCKER_UI_URL= # tarkista Giteasta kontin UI-osoite
|
||||||
|
```
|
||||||
|
|
||||||
|
- Kontti: `gitea.app.keskikuja.site/niko/gitea-ci-library-test-image:0.1.0`
|
||||||
|
- Paketti käyttäjän `niko` alla. Linkitys repoon tehdään Gitean UI:sta: paketin sivulta (Package → Settings) → linkitä repositoryyn.
|
||||||
|
|
||||||
|
```
|
||||||
|
DOCKER_REGISTRY=docker.io/library
|
||||||
|
DOCKER_IMAGE_NAME=oma-kuva
|
||||||
|
DOCKER_UI_URL=https://hub.docker.com/r/library/oma-kuva
|
||||||
|
```
|
||||||
|
|
||||||
|
- Secret `DOCKER_USERNAME` = Docker Hub -käyttäjä
|
||||||
|
- Secret `DOCKER_PASSWORD` = Access Token (ei salasana)
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# Helm Registry Setup (OCI)
|
||||||
|
|
||||||
|
Pipeline paketoi Helm chartin OCI-artefaktiksi ja pushee sen OCI-rekisteriin.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Konfiguroi `gitea-env.conf`
|
||||||
|
|
||||||
|
```
|
||||||
|
# HELM_REGISTRY on muotoa: registry.example.com/org
|
||||||
|
#
|
||||||
|
# host+org: registry.example.com/org
|
||||||
|
#
|
||||||
|
# Pipeline rakentaa OCI-refin: oci://${HELM_REGISTRY}/<chart-name>:${VERSION}
|
||||||
|
# (chart-name tulee Chart.yaml:n name-kentästä)
|
||||||
|
|
||||||
|
HELM_REGISTRY=gitea.app.keskikuja.site/niko # PAKOLLINEN — tyhjä ei käy
|
||||||
|
HELM_UI_URL= # valinnainen — tarkista Giteasta kontin oma UI-osoite, workflow liittää perään /chart-name/VERSION
|
||||||
|
GIT_TAG_PREFIX=git-pages/ # valinnainen — monorepo-tägäys
|
||||||
|
VERSION_FILE=git-pages/Chart.yaml # valinnainen — jos Chart.yaml ei rootissa
|
||||||
|
```
|
||||||
|
|
||||||
|
| Kenttä | Pakollinen | Kuvaus |
|
||||||
|
|---|---|---|
|
||||||
|
| `HELM_REGISTRY` | **kyllä** | Registry host + owner (esim. `gitea.app.site/niko`). **Tyhjä pysäyttää workflow'n.** |
|
||||||
|
| `HELM_UI_URL` | ei | Base-URL OCI-paketin UI-sivulle (ilman chart-nimeä ja versiota). Osoite riippuu onko paketti linkitetty repoon vai ei — tarkista Giteasta. Workflow liittää perään `/chart-name/VERSION`. Jos tyhjä, commit-statusia ei erikseen aseteta. |
|
||||||
|
| `GIT_TAG_PREFIX` | ei | Etuliite git-tägille. Pakollinen monorepossa, jotta tagit eivät sekoitu muihin komponentteihin. |
|
||||||
|
| `VERSION_FILE` | ei | Polku version lähteeseen (Chart.yaml, package.json, VERSION). Oletus: juuren `Chart.yaml`. |
|
||||||
|
|
||||||
|
**OCI-ref = `oci://${HELM_REGISTRY}/<chart-name>:${VERSION}`**
|
||||||
|
Esim. `oci://gitea.app.keskikuja.site/niko/git-pages:1.2.3`
|
||||||
|
|
||||||
|
Chartin nimi (`<chart-name>`) määräytyy `Chart.yaml`-tiedoston `name`-kentästä.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Luo PAT (Personal Access Token) Giteassa
|
||||||
|
|
||||||
|
**Gitea → oma profiili (oikea yläkulma) → Settings → Applications → Manage Access Tokens → Generate New Token**
|
||||||
|
|
||||||
|
Valitse scope:
|
||||||
|
|
||||||
|
| Scope | Pääsy |
|
||||||
|
|---|---|
|
||||||
|
| `package` | **Read and Write** |
|
||||||
|
|
||||||
|
> Tämä token toimii salasanana `helm registry login` -komennossa. Muut scopet (kuten `repository`) eivät riitä — konttirekisteri vaatii nimenomaan `package`-scopen.
|
||||||
|
|
||||||
|
Tokenin arvo näytetään **vain kerran** luomisen yhteydessä. Kopioi se talteen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Tallenna PAT repositoryn Secretsiin
|
||||||
|
|
||||||
|
Nämä ovat kaksi eri paikkaa:
|
||||||
|
- **Access Tokenit** (User Settings) = missä luot tokenin
|
||||||
|
- **Repository Secrets** (Repository Settings) = minne talletat sen workflow'n käyttöön
|
||||||
|
|
||||||
|
**Repository → Settings → Actions → Secrets → Add new secret**
|
||||||
|
|
||||||
|
| Secret | Arvo |
|
||||||
|
|---|---|
|
||||||
|
| `HELM_PASSWORD` | Edellisessä vaiheessa luotu PAT |
|
||||||
|
|
||||||
|
`HELM_USER`-secretiä **ei tarvita**. Workflow käyttää automaattisesti `${{ github.actor }}` (workflowin käynnistäjä).
|
||||||
|
|
||||||
|
Jos registry vaatii eri käyttäjätunnuksen kuin `github.actor` (esim. Harbor, Artifactory), lisää myös:
|
||||||
|
|
||||||
|
| Secret | Arvo |
|
||||||
|
|---|---|
|
||||||
|
| `HELM_USER` | Registryn käyttäjätunnus |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Tarkistuslista ennen ajoa
|
||||||
|
|
||||||
|
- [ ] `HELM_REGISTRY` asetettu `gitea-env.conf`issa
|
||||||
|
- [ ] (tarvittaessa) `HELM_UI_URL` asetettu — ilman tätä commit-statusia ei erikseen aseteta
|
||||||
|
- [ ] PAT luotu Giteassa scopella `package` Read and Write
|
||||||
|
- [ ] `HELM_PASSWORD`-secret tallennettu repositoryn Secretsiin (se PAT)
|
||||||
|
- [ ] (tarvittaessa) `HELM_USER`-secret — oletus `github.actor`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Esimerkkejä eri polkurakenteista
|
||||||
|
|
||||||
|
### 5a. Hosti + org — Gitea user-taso
|
||||||
|
|
||||||
|
```
|
||||||
|
HELM_REGISTRY=gitea.app.keskikuja.site/niko
|
||||||
|
```
|
||||||
|
|
||||||
|
- OCI-ref: `oci://gitea.app.keskikuja.site/niko/git-pages:1.2.3`
|
||||||
|
- Paketti käyttäjän `niko` alla. Linkitys repoon tehdään Gitean UI:sta: paketin sivulta (Package → Settings) → linkitä repositoryyn.
|
||||||
|
- `HELM_PASSWORD` = Gitea PAT scopella `package`
|
||||||
|
|
||||||
|
### 5b. Hosti + org — Harbor
|
||||||
|
|
||||||
|
```
|
||||||
|
HELM_REGISTRY=harbor.example.com/projekti
|
||||||
|
```
|
||||||
|
|
||||||
|
- `HELM_USER` = Harbor-käyttäjä
|
||||||
|
- `HELM_PASSWORD` = Harbor-token
|
||||||
|
|
||||||
|
### 5c. Artifactory
|
||||||
|
|
||||||
|
```
|
||||||
|
HELM_REGISTRY=artifactory.example.com/helm-local
|
||||||
|
```
|
||||||
|
|
||||||
|
- `HELM_USER` = service account
|
||||||
|
- `HELM_PASSWORD` = API-token
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitea-ci-library",
|
"name": "gitea-ci-library",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "cucumber.js",
|
"main": "cucumber.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|||||||
Executable
+60
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RAW_VERSION=""
|
||||||
|
|
||||||
|
if [ -n "${VERSION_FILE-}" ] && [ -f "${VERSION_FILE-}" ]; then
|
||||||
|
RAW_VERSION=$(tr -d "$(printf '\xef\xbb\xbf')" < "${VERSION_FILE}" | sed -n 's/^version:[[:space:]]*\([^[:space:]]*\).*/\1/p')
|
||||||
|
if [ -z "${RAW_VERSION}" ]; then
|
||||||
|
if echo "${VERSION_FILE}" | grep -q -E '\.json$'; then
|
||||||
|
RAW_VERSION=$(jq -r '.version' "${VERSION_FILE}")
|
||||||
|
else
|
||||||
|
RAW_VERSION=$(cat "${VERSION_FILE}" | tr -d '[:space:]')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${RAW_VERSION}" ]; then
|
||||||
|
if [ -f VERSION ]; then
|
||||||
|
RAW_VERSION=$(cat VERSION | tr -d '[:space:]')
|
||||||
|
elif [ -f package.json ]; then
|
||||||
|
RAW_VERSION=$(jq -r '.version' package.json)
|
||||||
|
elif [ -f pom.xml ]; then
|
||||||
|
RAW_VERSION=$(grep -oP '<version>\K[^<]+' pom.xml | head -1)
|
||||||
|
elif [ -f Chart.yaml ]; then
|
||||||
|
RAW_VERSION=$(tr -d "$(printf '\xef\xbb\xbf')" < Chart.yaml | sed -n 's/^version:[[:space:]]*\([^[:space:]]*\).*/\1/p')
|
||||||
|
else
|
||||||
|
echo "ERROR: No version source found (VERSION_FILE, VERSION, package.json, pom.xml, Chart.yaml)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
BASE_VERSION=$(echo "$RAW_VERSION" | cut -d'.' -f1-2)
|
||||||
|
echo "gitea-ci-library - Tunnistettu Major.Minor versio: $BASE_VERSION"
|
||||||
|
|
||||||
|
TAGS_JSON=$(curl -s -f -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${SERVER_URL}/api/v1/repos/${REPO}/tags")
|
||||||
|
|
||||||
|
TAG=$(echo "$TAGS_JSON" | jq -r --arg prefix "${GIT_TAG_PREFIX-}" --arg sha "${SHA}" '
|
||||||
|
if type == "array" then
|
||||||
|
.[] | select(.commit.sha == $sha and (.name | startswith($prefix))) | .name
|
||||||
|
else empty end' | head -1)
|
||||||
|
|
||||||
|
mkdir -p /tmp/build-ctx
|
||||||
|
|
||||||
|
if [ -n "$TAG" ]; then
|
||||||
|
echo "ARTIFACT_EXISTS=true" > /tmp/build-ctx/build.env
|
||||||
|
echo "NEXT_VERSION=$TAG" >> /tmp/build-ctx/build.env
|
||||||
|
echo "gitea-ci-library - Artefakti löytyi jo tagilla: $TAG."
|
||||||
|
else
|
||||||
|
echo "ARTIFACT_EXISTS=false" > /tmp/build-ctx/build.env
|
||||||
|
|
||||||
|
HIGHEST_PATCH=$(echo "$TAGS_JSON" | jq -r --arg prefix "${GIT_TAG_PREFIX-}" --arg bv "${GIT_TAG_PREFIX-}${BASE_VERSION}." '
|
||||||
|
if type == "array" then .[] | .name | select(startswith($bv)) | sub($bv; "") | tonumber else empty end' | sort -rn | head -1)
|
||||||
|
|
||||||
|
if [ -z "$HIGHEST_PATCH" ]; then NEXT_PATCH=0; else NEXT_PATCH=$((HIGHEST_PATCH + 1)); fi
|
||||||
|
FULL_VERSION="${BASE_VERSION}.${NEXT_PATCH}"
|
||||||
|
|
||||||
|
echo "NEXT_VERSION=$FULL_VERSION" >> /tmp/build-ctx/build.env
|
||||||
|
echo "gitea-ci-library - Uusi vapaa versio: $FULL_VERSION"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#!/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"
|
||||||
@@ -17,6 +17,11 @@ POLL_INTERVAL="${DISPATCH_POLL_INTERVAL:-10}"
|
|||||||
[ -z "$GITEA_API_URL" ] && echo "ERROR: gitea_api_url argument is required" >&2 && exit 1
|
[ -z "$GITEA_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}')
|
||||||
|
|
||||||
@@ -32,19 +37,30 @@ if [ "$DISPATCH_CODE" != "201" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
RUNS_URL="$GITEA_API_URL/api/v1/repos/$TARGET_REPO/actions/runs?status=running"
|
# Poll: find dispatched run by display_title matching
|
||||||
RUNS_RESP=$(curl -s --connect-timeout 5 --max-time 10 \
|
RUN_ID=""
|
||||||
-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))
|
||||||
@@ -61,6 +77,12 @@ 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
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/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"
|
||||||
Executable
+114
@@ -0,0 +1,114 @@
|
|||||||
|
#!/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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env sh
|
||||||
set -euo pipefail
|
set -eu
|
||||||
|
|
||||||
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="${GITHUB_SHA:0:8}"
|
SHA8=$(echo "$GITHUB_SHA" | cut -c1-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,13 +33,66 @@ 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() {
|
||||||
local method="$1"
|
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" \
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env sh
|
||||||
set -euo pipefail
|
set -eu
|
||||||
|
|
||||||
STATE="${1:-}"
|
STATE="${1:-}"
|
||||||
DESCRIPTION="${2:-}"
|
DESCRIPTION="${2:-}"
|
||||||
KEY="${3:-commit-${GITHUB_SHA:0:8}}"
|
SHA8=$(echo "${GITHUB_SHA:-}" | cut -c1-8)
|
||||||
|
KEY="${3:-commit-${SHA8}}"
|
||||||
SUITE="${4:-}"
|
SUITE="${4:-}"
|
||||||
|
CUSTOM_URL="${5:-}"
|
||||||
|
|
||||||
[ -z "$STATE" ] && echo "ERROR: state argument is required" >&2 && exit 1
|
[ -z "$STATE" ] && echo "ERROR: state argument is required" >&2 && exit 1
|
||||||
[ -z "$DESCRIPTION" ] && echo "ERROR: description argument is required" >&2 && exit 1
|
[ -z "$DESCRIPTION" ] && echo "ERROR: description argument is required" >&2 && exit 1
|
||||||
[ -z "${GITEA_API_URL:-}" ] && echo "ERROR: GITEA_API_URL is not set" >&2 && exit 1
|
[ -z "${GITEA_API_URL:-}" ] && echo "ERROR: GITEA_API_URL is not set" >&2 && exit 1
|
||||||
[ -z "${GITEA_TOKEN:-}" ] && echo "ERROR: GITEA_TOKEN is not set" >&2 && exit 1
|
[ -z "${GITEA_TOKEN:-}" ] && echo "ERROR: GITEA_TOKEN is not set" >&2 && exit 1
|
||||||
|
|
||||||
if [ -n "$SUITE" ]; then
|
if [ -n "$CUSTOM_URL" ]; then
|
||||||
|
URL="$CUSTOM_URL"
|
||||||
|
elif [ -n "$SUITE" ]; then
|
||||||
SUITE="${SUITE%/}/"
|
SUITE="${SUITE%/}/"
|
||||||
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/${SUITE}"
|
SHA8_CUT=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||||||
|
URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/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
|
||||||
@@ -28,15 +33,15 @@ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|||||||
-X POST "$GITEA_API_URL/api/v1/repos/$REPO/statuses/$COMMIT" \
|
-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\":\"$KEY\"}")
|
-d "{\"state\":\"$STATE\",\"target_url\":\"$URL\",\"description\":\"$DESCRIPTION\",\"context\":\"$KEY\"}" || true)
|
||||||
|
|
||||||
if [ "$HTTP_CODE" = "201" ]; then
|
if [ "$HTTP_CODE" = "201" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$HTTP_CODE" ]; then
|
if [ -z "$HTTP_CODE" ] || [ "$HTTP_CODE" = "000" ]; then
|
||||||
echo "ERROR: Failed to connect to Gitea API at $GITEA_API_URL" >&2
|
echo "gitea-ci-library - ERROR: Failed to connect to Gitea API at $GITEA_API_URL" >&2
|
||||||
else
|
else
|
||||||
echo "ERROR: API returned HTTP $HTTP_CODE" >&2
|
echo "gitea-ci-library - ERROR: gitea-ci-library, API returned HTTP $HTTP_CODE" >&2
|
||||||
fi
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
---
|
||||||
|
name: ci-container-build
|
||||||
|
description: |
|
||||||
|
Creating or modifying CI container build workflows. Activates when consumer
|
||||||
|
needs to build a custom CI container image (multi-tool image for tests,
|
||||||
|
linting, validation) that is pushed to a container registry for use in
|
||||||
|
other pipeline jobs.
|
||||||
|
activation-gate: |
|
||||||
|
User mentions CI container, custom CI image, ci-container-build, Dockerfile
|
||||||
|
for CI tools, multi-tool container, or needs to build/push a container that
|
||||||
|
other CI jobs will use as their runtime environment.
|
||||||
|
category: ci
|
||||||
|
impact: high
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI Container Build — Template
|
||||||
|
|
||||||
|
Template jolla consumer luo oman CI-kontin build-workflown.
|
||||||
|
Kontti sisältää useamman työkalun yhdistelmän (esim. helm + kubeconform + xsltproc),
|
||||||
|
jota muut jobit käyttävät ajonaikaisena ympäristönä.
|
||||||
|
|
||||||
|
## Rakenne
|
||||||
|
|
||||||
|
Vain `workflow_dispatch` — **ei automaattista buildausta koskaan**. Kontti buildataan
|
||||||
|
manuaalisesti Gitea Actions UI:sta. Tiedoston ilmestyessä main-haaraan workflow näkyy
|
||||||
|
välittömästi Actions-tabissa ajettavana.
|
||||||
|
|
||||||
|
Build-prosessi lataa ensin `config-provider.yml`:llä `DOCKER_REGISTRY`:n
|
||||||
|
conf-tiedostosta, sitten buildaa ja puskaa kontin.
|
||||||
|
|
||||||
|
Kun kontti on pushattu registryyn, se on muiden pipeline-jobien käytettävissä
|
||||||
|
`latest`-tägillä — rebuild = käyttöönotto. Mitään versioviittauksia ei tarvitse
|
||||||
|
päivittää.
|
||||||
|
|
||||||
|
## Offline-periaate (DoD)
|
||||||
|
|
||||||
|
CI-kontin **Definition of Done**:
|
||||||
|
|
||||||
|
> Kontti ei lataa mitään pipeline-vaiheessa (`workflow run` -stepit) eikä kontin
|
||||||
|
> runtime-prosessissa (`container:` / `docker run`). Kaikki riippuvuudet
|
||||||
|
> (kielikohtaiset paketit, työkalut, binäärit) on joko:
|
||||||
|
> - Pre-cachattu kontin **build-vaiheessa** Dockerfilessä, TAI
|
||||||
|
> - Kopioitu multi-stage buildilla toisesta imagesta (`COPY --from`)
|
||||||
|
>
|
||||||
|
> Ainoa sallittu lataushetki on `docker build`. Sen jälkeen kontti toimii
|
||||||
|
> ilman verkkoyhteyttä.
|
||||||
|
|
||||||
|
**Miksi:** Toistettavuus, air gap -yhteensopivuus, nopeus. Pipeline ei saa
|
||||||
|
epäonnistua sen takia että ulkoinen registry on alhaalla tai että `go mod download`
|
||||||
|
joutuu latamaan 100 modulia jokaisella testiajolla.
|
||||||
|
|
||||||
|
**Kielikohtaiset pre-cachet:** Jos kontissa ajetaan kielikohtaista testiä
|
||||||
|
(Go, Java, Node, Python, ...), kaikki kielikohtaiset riippuvuudet on
|
||||||
|
pre-cachattava Dockerfilessä build-vaiheessa:
|
||||||
|
- Go: `COPY go.mod go.sum ./` → `RUN go mod download`
|
||||||
|
- Java/Maven: `COPY pom.xml ./` → `RUN mvn dependency:go-offline`
|
||||||
|
- Node: `COPY package.json package-lock.json ./` → `RUN npm ci --omit=dev`
|
||||||
|
- Python: `COPY requirements.txt ./` → `RUN pip wheel --wheel-dir=/wheels -r requirements.txt` → `COPY --from` käyttöön
|
||||||
|
|
||||||
|
## Nimeäminen
|
||||||
|
|
||||||
|
CI-kontin build-workflow noudattaa samaa nimeämiskonventiota kuin muutkin
|
||||||
|
tiedostot `.gitea/workflows/`-kansiossa:
|
||||||
|
|
||||||
|
```
|
||||||
|
<komponentti>.ci-feature.yml ← feature-haaran reititin
|
||||||
|
<komponentti>.ci-main.yml ← main-haaran reititin
|
||||||
|
<komponentti>.<testityyppi>.yml ← yksittäinen testi tai operaatio
|
||||||
|
<komponentti>.ci-container-build-<kontti>.yml ← CI-kontin build-workflow
|
||||||
|
<komponentti>.gitea-env.conf ← komponenttikohtainen konfiguraatio
|
||||||
|
```
|
||||||
|
|
||||||
|
Single repossa `<komponentti>` jätetään pois — tiedostot ovat suoraan `ci-feature.yml`,
|
||||||
|
`ci-main.yml`, `<testityyppi>.yml`, `ci-container-build-<kontti>.yml`.
|
||||||
|
|
||||||
|
Monorepossa prefiksi pitää komponentin tiedostot yhdessä: `ls <komponentti>.*` löytää kaikki
|
||||||
|
kerralla. **Olemassaolevia prefiksejä ei saa poistaa.**
|
||||||
|
|
||||||
|
Esimerkkejä:
|
||||||
|
```
|
||||||
|
.gitea/workflows/chart.ci-container-build-helm.yml
|
||||||
|
.gitea/workflows/api.ci-container-build-node.yml
|
||||||
|
.gitea/workflows/ci-container-build-bats.yml ← single repo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Template
|
||||||
|
|
||||||
|
> **Korvaa kaikki `__SUURAAKKOSET__`-placeholderit projektin todellisilla arvoilla.**
|
||||||
|
> Ainoastaan `${{ ... }}`-syntaksilla merkityt Gitea Actions -muuttujat ovat ajonaikaisia
|
||||||
|
> eikä niitä korvata.
|
||||||
|
|
||||||
|
Luo `.gitea/workflows/__KOMPONENTTI__.ci-container-build-__KONTTI__.yml`
|
||||||
|
(single repo: `ci-container-build-__KONTTI__.yml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI Container Build & Push
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
config_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: 'Polku .gitea-env.conf-tiedostoon (esim. .gitea/workflows/chart.gitea-env.conf)'
|
||||||
|
dockerfile_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: 'Polku Dockerfileen (esim. ci-helm.Dockerfile)'
|
||||||
|
image_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: 'Kontin nimi ilman registry-polkua (esim. ci-helm)'
|
||||||
|
tag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'latest'
|
||||||
|
description: 'Image-tägi'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
load-config:
|
||||||
|
uses: __OWNER__/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
config_path: ${{ inputs.config_path }}
|
||||||
|
|
||||||
|
build-push:
|
||||||
|
needs: [load-config]
|
||||||
|
uses: __OWNER__/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@v1
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
env_json: ${{ needs.load-config.outputs.env_json }}
|
||||||
|
dockerfile_path: ${{ inputs.dockerfile_path }}
|
||||||
|
image_name: ${{ inputs.image_name }}
|
||||||
|
tag: ${{ inputs.tag }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Käyttö
|
||||||
|
|
||||||
|
**Gitea Actions UI:sta** (heti kun tiedosto on main-haarassa):
|
||||||
|
|
||||||
|
```
|
||||||
|
Gitea → Actions → CI Container Build & Push → Run workflow
|
||||||
|
|
||||||
|
config_path: .gitea/workflows/__KOMPONENTTI__.gitea-env.conf
|
||||||
|
dockerfile_path: ci-__TYÖKALU__.Dockerfile
|
||||||
|
image_name: ci-__TYÖKALU__
|
||||||
|
tag: latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dockerfile
|
||||||
|
|
||||||
|
Dockerfile yhdistää tarvitut työkalut yhteen konttiin.
|
||||||
|
**Kaikki riippuvuudet ladataan build-vaiheessa — kontti on täysin itseriittoinen.**
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Tapa A: COPY --from toisesta imagesta
|
||||||
|
FROM __BASE_IMAGE__:__VERSION__
|
||||||
|
COPY --from=__SOURCE_IMAGE__:__VERSION__ /path/to/binary /usr/local/bin/
|
||||||
|
RUN apk add --no-cache __PAKETIT__
|
||||||
|
|
||||||
|
# Tapa B: Build-vaiheen curl-lataus
|
||||||
|
FROM __BASE_IMAGE__:__VERSION__
|
||||||
|
RUN apk add --no-cache curl __PAKETIT__ && \
|
||||||
|
curl -fsSL __URL__/__BINARY__.tar.gz | tar xz -C /usr/local/bin && \
|
||||||
|
apk del curl
|
||||||
|
|
||||||
|
# Tapa C: Multi-stage + kielikohtainen pre-cache
|
||||||
|
FROM __BASE_IMAGE__:__VERSION__ AS deps
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
FROM deps AS build
|
||||||
|
COPY . .
|
||||||
|
RUN go test -c -o /tmp/test.bin ./...
|
||||||
|
|
||||||
|
FROM __BASE_IMAGE__:__VERSION__
|
||||||
|
COPY --from=deps /go/pkg/mod /go/pkg/mod
|
||||||
|
COPY --from=build /tmp/test.bin /usr/local/bin/test
|
||||||
|
```
|
||||||
|
|
||||||
|
`COPY --from` on kevyempi (ei curl-asennusta). `curl` (Tapa B) on sallittu
|
||||||
|
vain build-vaiheessa — `apk del curl` poistaa työkalun ennen runtimea.
|
||||||
|
Tapa C pre-cacheaa kielikohtaiset riippuvuudet ja tuottaa täysin
|
||||||
|
offline-runtime-kontin.
|
||||||
|
|
||||||
|
## Testaus ennen julkaisua
|
||||||
|
|
||||||
|
Konttia ei saa pushata registryyn ennen kuin se on validoitu.
|
||||||
|
|
||||||
|
### 1. Aja testit kontin sisällä
|
||||||
|
|
||||||
|
Testit on ajettava **kontin sisällä**, ei suoraan lokaalilla koneella.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# OIKEIN — kontin sisällä
|
||||||
|
docker build -t ci-tyokalu:test .
|
||||||
|
docker run --rm -v "$(pwd):/repo" -w /repo ci-tyokalu:test bash -c 'bats tests/'
|
||||||
|
|
||||||
|
# VÄÄRIN — lokaalit binäärit vs kontti
|
||||||
|
bats tests/ # eri bash/työkalut kuin kontissa
|
||||||
|
bashcov -- bats tests/ # eri ruby-versio kuin kontissa
|
||||||
|
```
|
||||||
|
|
||||||
|
Lokaali ympäristö (macOS, eri kirjastoversiot) poikkeaa aina kontista.
|
||||||
|
Testi voi mennä läpi lokaalissa mutta failata CI:ssä, tai päinvastoin.
|
||||||
|
|
||||||
|
### 2. Fragile-testien seulonta (10x ajo)
|
||||||
|
|
||||||
|
Aja koko testipaketti **10 kertaa peräkkäin** kontin sisällä ennen pushausta:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
echo "=== RUN $i ==="
|
||||||
|
docker run --rm -v "$(pwd):/repo" -w /repo ci-tyokalu:test \
|
||||||
|
bash -c 'bats tests/' || exit 1
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Jos yksikin ajo failaa, kontissa on fragile testi — korjaa ennen pushausta.
|
||||||
|
Fragile testit syövät devaukseen käytettyä aikaa turhilla uusinta-ajoilla.
|
||||||
|
|
||||||
|
## Mitä EI kannata tehdä
|
||||||
|
|
||||||
|
- Älä lisää `workflow_call`-triggariä — CI-konttia ei koskaan buildata automaattisesti
|
||||||
|
- Älä poista `<komponentti>.`-prefiksiä olemassaolevista tiedostoista — ne kuuluvat monorepo-nimeämiskonventioon
|
||||||
|
- Älä sisällytä CI-konttiin mitään sovelluskoodia — vain työkalut
|
||||||
|
- Älä koskaan lataa mitään pipeline- tai runtime-vaiheessa — kaikki lataukset kuuluvat `docker build` -vaiheeseen (Offline-periaate)
|
||||||
|
- Älä jätä kielikohtaisia riippuvuuksia pre-cachaamatta — `go mod download`, `npm install`, `mvn dependency:go-offline` jne. ajetaan Dockerfilessä, ei pipelinessä
|
||||||
@@ -0,0 +1,636 @@
|
|||||||
|
# Consumer Pipelines — Reference
|
||||||
|
|
||||||
|
Mallipohjat, esimerkit ja konfiguraatiot. Katso säännöt `SKILL.md`:stä.
|
||||||
|
|
||||||
|
## Pre-cache-esimerkit (Offline Container)
|
||||||
|
|
||||||
|
Alla Dockerfile-esimerkit kielikohtaisista pre-cacheista. Kaikki ajetaan
|
||||||
|
build-vaiheessa — kontti on täysin itseriittoinen eikä lataa mitään
|
||||||
|
pipeline- tai runtime-vaiheessa.
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM golang:1.24-alpine AS deps
|
||||||
|
WORKDIR /build
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
FROM deps AS test-build
|
||||||
|
COPY . .
|
||||||
|
RUN go test -c -o /tmp/test.bin ./...
|
||||||
|
|
||||||
|
FROM alpine:3.21
|
||||||
|
RUN apk add --no-cache git nodejs
|
||||||
|
COPY --from=deps /go/pkg/mod /go/pkg/mod
|
||||||
|
COPY --from=test-build /tmp/test.bin /usr/local/bin/test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node.js
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM node:22-alpine AS deps
|
||||||
|
WORKDIR /build
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
FROM node:22-alpine
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
COPY --from=deps /build/node_modules /app/node_modules
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Java / Maven
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM maven:3.9-eclipse-temurin-21 AS deps
|
||||||
|
WORKDIR /build
|
||||||
|
COPY pom.xml ./
|
||||||
|
RUN mvn dependency:go-offline -B
|
||||||
|
|
||||||
|
FROM maven:3.9-eclipse-temurin-21 AS build
|
||||||
|
COPY --from=deps /root/.m2 /root/.m2
|
||||||
|
COPY . .
|
||||||
|
RUN mvn package -B -DskipTests
|
||||||
|
|
||||||
|
FROM eclipse-temurin:21-jre
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY --from=build /build/target/*.jar /app/app.jar
|
||||||
|
WORKDIR /app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM python:3.12-alpine AS deps
|
||||||
|
WORKDIR /build
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip wheel --wheel-dir=/wheels -r requirements.txt
|
||||||
|
|
||||||
|
FROM python:3.12-alpine
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
COPY --from=deps /build/wheels /wheels
|
||||||
|
COPY --from=deps /build/requirements.txt /
|
||||||
|
RUN pip install --no-index --find-links=/wheels -r /requirements.txt && rm -rf /wheels
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Helm + Node.js (korvaa helm-build-push.yml:n runtime-apk)
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM alpine/helm:3.16.0 AS helm-bin
|
||||||
|
FROM node:22-alpine
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
COPY --from=helm-bin /usr/bin/helm /usr/local/bin/helm
|
||||||
|
```
|
||||||
|
|
||||||
|
Tämä kontti korvaa `helm-build-push.yml`:n `alpine/helm:3.19.0`-image-riippuvuuden
|
||||||
|
ja poistaa tarpeen asentaa node.js runtime-vaiheessa.
|
||||||
|
|
||||||
|
## Reititin — täydellinen esimerkki
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
load-config:
|
||||||
|
uses: <owner>/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
<test-1>:
|
||||||
|
needs: [load-config]
|
||||||
|
uses: ./.gitea/workflows/<component>.<test-1>.yml
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
env_json: ${{ needs.load-config.outputs.env_json }}
|
||||||
|
|
||||||
|
<test-2>:
|
||||||
|
needs: [load-config]
|
||||||
|
uses: ./.gitea/workflows/<component>.<test-2>.yml
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
env_json: ${{ needs.load-config.outputs.env_json }}
|
||||||
|
|
||||||
|
report-summary:
|
||||||
|
needs: [load-config, <test-1>, <test-2>]
|
||||||
|
if: always()
|
||||||
|
uses: <owner>/gitea-ci-library/.gitea/workflows/report-summary.yml@v1
|
||||||
|
with:
|
||||||
|
env_json: ${{ needs.load-config.outputs.env_json }}
|
||||||
|
suites: <suite-1> <suite-2>
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI-kontin build — parametroitu workflow
|
||||||
|
|
||||||
|
CI-kontin build on `workflow_dispatch`-triggeröity job, joka näkyy Gitea Actionsissa kuten Jenkinsin
|
||||||
|
parametroitu job — käyttäjä antaa inputit UI:sta ennen ajoa.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI Container Build <työkalu>
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
config_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: '.gitea/workflows/<komponentti>.gitea-env.conf'
|
||||||
|
description: 'Polku .gitea-env.conf-tiedostoon'
|
||||||
|
dockerfile_path:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: '<komponentti>/Dockerfile.ci-<työkalu>'
|
||||||
|
description: 'Polku Dockerfileen'
|
||||||
|
image_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'ci-<työkalu>'
|
||||||
|
description: 'Kontin nimi ilman registry-polkua'
|
||||||
|
tag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: 'latest'
|
||||||
|
description: 'Image-tägi'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
load-config:
|
||||||
|
uses: <owner>/gitea-ci-library/.gitea/workflows/config-provider.yml@v1
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
config_path: ${{ inputs.config_path }}
|
||||||
|
|
||||||
|
build-push:
|
||||||
|
needs: [load-config]
|
||||||
|
uses: <owner>/gitea-ci-library/.gitea/workflows/ci-container-build-push.yml@v1
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
env_json: ${{ needs.load-config.outputs.env_json }}
|
||||||
|
dockerfile_path: ${{ inputs.dockerfile_path }}
|
||||||
|
image_name: ${{ inputs.image_name }}
|
||||||
|
tag: ${{ inputs.tag }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI-kontin ajaminen testijobissa
|
||||||
|
|
||||||
|
**Ainoa sallittu tapa** consumer-puolella on `container:`-direktiivi. `docker run` komennolla
|
||||||
|
kontin käynnistäminen stepin sisällä on anti-pattern. `container:`-direktiivillä kaikki stepit
|
||||||
|
ajetaan samassa kontissa — tiedostot ovat suoraan filesystemillä eikä erillistä
|
||||||
|
volyyminhallintaa tarvita.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
<työkalu>:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ inputs.<image-name> }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: <owner>/gitea-ci-library
|
||||||
|
path: .ci
|
||||||
|
|
||||||
|
- name: Run <työkalu>
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p "reports/<suite>"
|
||||||
|
<komento> > "reports/<suite>/results.txt" 2>&1
|
||||||
|
|
||||||
|
- name: Post-process reports
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
<mahdollinen_raporttien_jälkikäsittely>
|
||||||
|
|
||||||
|
- name: Report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Monorepossa context ja description sisältävät komponentin nimen:
|
||||||
|
```yaml
|
||||||
|
- name: Report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usean runnerin cache-ongelma:** Jos eri kerroilla käynnistyy eri runnereita,
|
||||||
|
niillä voi olla eri versio `latest`-imagen digesteistä. Ratkaisuja:
|
||||||
|
- Rebuildaa kontti ja aja `docker pull <image>` manuaalisesti kaikilla runnereilla
|
||||||
|
- Käytä versioitua tagia (`v2`, `v3`, ...) ja päivitä workflow'n default buildauksen jälkeen
|
||||||
|
|
||||||
|
**Mallit:**
|
||||||
|
- `example-cucumber-tests.yml` — ei post-processia
|
||||||
|
- `example-bats-tests.yml` — post-process coverage + report
|
||||||
|
|
||||||
|
## Raporttitasot — tarkat YAML-mallit
|
||||||
|
|
||||||
|
### Taso 1: Ei jälkikäsittelyä
|
||||||
|
|
||||||
|
Single repo:
|
||||||
|
```yaml
|
||||||
|
- name: Run tests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p "reports/<suite>"
|
||||||
|
<testikomento>
|
||||||
|
|
||||||
|
- name: Report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Monorepo:
|
||||||
|
```yaml
|
||||||
|
- name: Run tests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p "reports/<suite>"
|
||||||
|
<testikomento>
|
||||||
|
|
||||||
|
- name: Report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Taso 2: Jälkikäsittely tarvitaan
|
||||||
|
|
||||||
|
Single repo:
|
||||||
|
```yaml
|
||||||
|
- name: Run tests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p "reports/<suite>"
|
||||||
|
<testikomento> > "reports/<suite>/results.txt" 2>&1
|
||||||
|
|
||||||
|
- name: Post-process coverage
|
||||||
|
if: always()
|
||||||
|
run: <siirrä coverage-data reports/<suite>/coverage/-hakemistoon>
|
||||||
|
|
||||||
|
- name: Post-process test report
|
||||||
|
if: always()
|
||||||
|
run: <HTML-generointi raa'asta outputista>
|
||||||
|
|
||||||
|
- name: Report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
bash .ci/scripts/ci-report.sh "<Test type> test report" <context> <suite> ${{ job.status }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Monorepo:
|
||||||
|
```yaml
|
||||||
|
- name: Run tests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p "reports/<suite>"
|
||||||
|
<testikomento> > "reports/<suite>/results.txt" 2>&1
|
||||||
|
|
||||||
|
- name: Post-process coverage
|
||||||
|
if: always()
|
||||||
|
run: <siirrä coverage-data reports/<suite>/coverage/-hakemistoon>
|
||||||
|
|
||||||
|
- name: Post-process test report
|
||||||
|
if: always()
|
||||||
|
run: <HTML-generointi raa'asta outputista>
|
||||||
|
|
||||||
|
- name: Report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
bash .ci/scripts/ci-report.sh "<Komponentti>: <Test type> test report" <komponentti>.<context> <suite> ${{ job.status }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Väärin vs oikein — yksi asia per step
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# VÄÄRIN — helm template fail → kubeconform jää ajamatta, report jää tekemättä
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
helm template ... > /tmp/manifests.yaml
|
||||||
|
kubeconform ... > results.txt 2>&1
|
||||||
|
|
||||||
|
# OIKEIN — erilliset stepit
|
||||||
|
- name: Helm template
|
||||||
|
run: helm template platform-helm/ -f values.yaml > /tmp/manifests.yaml 2>&1
|
||||||
|
|
||||||
|
- name: Kubeconform
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
mkdir -p reports/kubeconform
|
||||||
|
kubeconform ... > reports/kubeconform/results.txt 2>&1
|
||||||
|
|
||||||
|
- name: Report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
bash .ci/scripts/ci-report.sh "Helm kubeconform" helm-test kubeconform ${{ job.status }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Monorepossa:
|
||||||
|
```yaml
|
||||||
|
- name: Report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
bash .ci/scripts/ci-report.sh "<Komponentti>: Helm kubeconform" <komponentti>.helm-test kubeconform ${{ job.status }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Väärin vs oikein — post-process
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# VÄÄRIN — jos coverage epäonnistuu, report jää generoimatta
|
||||||
|
- name: Post-process reports
|
||||||
|
run: |
|
||||||
|
bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
|
||||||
|
bash .ci/.gitea/scripts/bats-report.sh reports/bats
|
||||||
|
|
||||||
|
# OIKEIN — erilliset stepit if: always()
|
||||||
|
- name: Post-process coverage
|
||||||
|
if: always()
|
||||||
|
run: bash .ci/.gitea/scripts/bats-coverage.sh reports/bats
|
||||||
|
|
||||||
|
- name: Post-process test report
|
||||||
|
if: always()
|
||||||
|
run: bash .ci/.gitea/scripts/bats-report.sh reports/bats
|
||||||
|
```
|
||||||
|
|
||||||
|
## Raportin julkaisukelpoisuus
|
||||||
|
|
||||||
|
`ci-report.sh` päättää onko raportti julkaisukelpoinen skannaamalla `reports/<suite>/`-hakemistoa.
|
||||||
|
|
||||||
|
### Mitä skannataan
|
||||||
|
|
||||||
|
| Mitä | Sääntö |
|
||||||
|
|---|---|
|
||||||
|
| **Tiedostot (FILES)** | Kaikki `reports/<suite>/`-juuressa olevat tiedostot paitsi `index.html` |
|
||||||
|
| **Alihakemistot (SUBDIRS)** | Vain ne, joissa on `index.html` |
|
||||||
|
|
||||||
|
### Julkaisukelpoisuus
|
||||||
|
|
||||||
|
| Tila | Seuraus |
|
||||||
|
|---|---|
|
||||||
|
| `FILES + SUBDIRS = 0` | **Failure** — `ci-report.sh` palauttaa virheen, raporttia ei julkaista |
|
||||||
|
| `FILES + SUBDIRS = 1` | Suora linkki itemiin — ei generoi index-sivua |
|
||||||
|
| `FILES + SUBDIRS > 1` | Generoi `reports/<suite>/index.html`-sivun, linkit kaikkiin itemeihin |
|
||||||
|
|
||||||
|
### Hakemistorakenne
|
||||||
|
|
||||||
|
```
|
||||||
|
reports/<suite>/
|
||||||
|
├── results.txt ← testin stdout (skannataan FILES)
|
||||||
|
├── test-report.html ← generoitu HTML (skannataan FILES)
|
||||||
|
└── <mikä tahansa>/ ← alihakemisto (skannataan SUBDIRS)
|
||||||
|
└── index.html ← VAIN jos tämä on olemassa
|
||||||
|
```
|
||||||
|
|
||||||
|
### Esimerkki: coverage-näkymä
|
||||||
|
|
||||||
|
```
|
||||||
|
reports/<suite>/coverage/index.html ← on olemassa
|
||||||
|
```
|
||||||
|
|
||||||
|
Coverage-dataa ei siirretä automaattisesti. Testin tai post-process-stepin pitää
|
||||||
|
siirtää coverage `reports/<suite>/coverage/`-hakemistoon ja varmistaa että `index.html` on mukana.
|
||||||
|
|
||||||
|
**Provider vastuulla:** `ci-report.sh` (provider-skripti) hoitaa sekä hakemistorakenteen
|
||||||
|
skannauksen, `index.html`-generoinnin että julkaisun git-pagesiin. Consumer tuottaa
|
||||||
|
vain raakatiedostot `reports/<suite>/`-hakemistoon — `ci-report.sh` päättää
|
||||||
|
julkaisukelpoisuuden ja generoi tarvittavan navigaation.
|
||||||
|
|
||||||
|
## Debug-ohje: raportti ei näy
|
||||||
|
|
||||||
|
### 1. Aja lokaalisti samalla komennolla kuin CI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p reports/bats
|
||||||
|
bashcov -- bats tests/ > reports/bats/results.txt 2>&1
|
||||||
|
echo "exit: $?"
|
||||||
|
ls -la reports/bats/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Lisää `echo "DEBUG: ..." >&2` ennen ja jälkeen kriittisen operaation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "DEBUG: coverage exists? $([ -d coverage ] && echo YES || echo NO)" >&2
|
||||||
|
echo "DEBUG: target/index.html exists? $([ -f reports/suite/coverage/index.html ] && echo YES || echo NO)" >&2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Tarkista kutsuparametrit
|
||||||
|
|
||||||
|
Yleisin virhe: skripti odottaa `$1` = X, mutta kutsuja antaa `$1` = Y ja `$2` = X.
|
||||||
|
|
||||||
|
### 4. Tarkista tiedostopolut
|
||||||
|
|
||||||
|
1. Onko lähdetiedosto olemassa ennen kopiointia?
|
||||||
|
2. Onko kohde olemassa kopioinnin jälkeen?
|
||||||
|
3. Onko `index.html` subdirissä (vaaditaan `ci-report.sh`:lle)?
|
||||||
|
|
||||||
|
### 5. Poista debug-echot kun ongelma on korjattu
|
||||||
|
|
||||||
|
### 6. Älä kokeile — debuggaa
|
||||||
|
|
||||||
|
Kokeilu = arvaus. Debuggaus = lisää echo, aja, lue logi, eristä ongelma. Vasta sitten korjaa.
|
||||||
|
|
||||||
|
## Konfiguraatiotiedosto (.gitea-env.conf)
|
||||||
|
|
||||||
|
Tiedosto on `key=value`-muotoinen (kuten `.env`). Kommentit ja tyhjät rivit sallittuja.
|
||||||
|
|
||||||
|
### Single repo
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .gitea/workflows/gitea-env.conf
|
||||||
|
GITEA_API_URL=https://gitea.example.com
|
||||||
|
GIT_PAGES_URL=https://reports.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker-artifaktin buildaavat projektit
|
||||||
|
|
||||||
|
```ini
|
||||||
|
DOCKER_REGISTRY=gitea.example.com/myorg
|
||||||
|
DOCKER_IMAGE_NAME=my-service
|
||||||
|
DOCKER_UI_URL=https://gitea.example.com/myorg/-/packages/container
|
||||||
|
#DOCKERFILE=Dockerfile.platform # valinnainen, oletus Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
`DOCKER_UI_URL` ei sisällä image-nimeä — se on puhdas container-registryn osoite.
|
||||||
|
Image-nimi lisätään automaattisesti URL:iin `docker-build-push.yml`:ssä.
|
||||||
|
|
||||||
|
### Helm-artifaktin buildaavat projektit
|
||||||
|
|
||||||
|
```ini
|
||||||
|
HELM_REGISTRY=gitea.example.com/myorg
|
||||||
|
GIT_TAG_PREFIX=git-pages/
|
||||||
|
VERSION_FILE=git-pages/Chart.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
| Kenttä | Pakollinen | Kuvaus |
|
||||||
|
|---|---|---|
|
||||||
|
| `HELM_REGISTRY` | **kyllä** | Registry host + owner, esim. `gitea.example.com/myorg`. **Tyhjä pysäyttää workflow'n.** |
|
||||||
|
| `GIT_TAG_PREFIX` | ei | Etuliite git-tägille. Pakollinen monorepossa. |
|
||||||
|
| `VERSION_FILE` | ei | Polku version lähteeseen. Oletus: juuren `Chart.yaml`. |
|
||||||
|
|
||||||
|
### Salaisuudet (Gitea Settings → Secrets)
|
||||||
|
|
||||||
|
| Secret | Pakollinen |
|
||||||
|
|---|---|
|
||||||
|
| `GITEA_TOKEN` | Aina (Gitean sisäinen, automaattisesti saatavilla) |
|
||||||
|
| `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ä) |
|
||||||
@@ -0,0 +1,544 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
source "$BATS_TEST_DIRNAME/helpers/mock-api.sh"
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
export GITEA_TOKEN=test-token
|
||||||
|
export GIT_TAG_PREFIX=""
|
||||||
|
export SERVER_URL="http://localhost:18080"
|
||||||
|
export REPO="niko/test"
|
||||||
|
export SHA="abc123"
|
||||||
|
rm -rf /tmp/build-ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
mock_stop 2>/dev/null || true
|
||||||
|
rm -rf /tmp/build-ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "VERSION_FILE=Chart.yaml extracts version from YAML" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/Chart.yaml"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$ARTIFACT_EXISTS" = "false" ]
|
||||||
|
[ "$NEXT_VERSION" = "0.3.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "VERSION_FILE=VERSION extracts version from plain text" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$ARTIFACT_EXISTS" = "false" ]
|
||||||
|
[ "$NEXT_VERSION" = "0.3.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "VERSION_FILE=package.json extracts version from JSON" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/package.json"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$ARTIFACT_EXISTS" = "false" ]
|
||||||
|
[ "$NEXT_VERSION" = "0.3.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "VERSION_FILE=subdir/Chart.yaml extracts version from monorepo" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/subdir/Chart.yaml"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$ARTIFACT_EXISTS" = "false" ]
|
||||||
|
[ "$NEXT_VERSION" = "0.4.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "no VERSION_FILE, root VERSION found" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
WORKDIR=$(mktemp -d)
|
||||||
|
cp "$BATS_TEST_DIRNAME/fixtures/check-version/VERSION" "$WORKDIR/VERSION"
|
||||||
|
|
||||||
|
SCRIPT="$PWD/scripts/check-version.sh"
|
||||||
|
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
|
||||||
|
|
||||||
|
rm -rf "$WORKDIR"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$NEXT_VERSION" = "0.3.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "no VERSION_FILE, root Chart.yaml found" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
WORKDIR=$(mktemp -d)
|
||||||
|
cp "$BATS_TEST_DIRNAME/fixtures/check-version/Chart.yaml" "$WORKDIR/Chart.yaml"
|
||||||
|
|
||||||
|
SCRIPT="$PWD/scripts/check-version.sh"
|
||||||
|
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
|
||||||
|
|
||||||
|
rm -rf "$WORKDIR"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$NEXT_VERSION" = "0.3.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "tag exists for commit sets ARTIFACT_EXISTS=true" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": [{"name": "0.3.0", "commit": {"sha": "abc123"}}]}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$ARTIFACT_EXISTS" = "true" ]
|
||||||
|
[ "$NEXT_VERSION" = "0.3.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "tag with prefix filters correctly" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": [{"name": "git-pages/0.3.0", "commit": {"sha": "abc123"}}, {"name": "docker/0.3.0", "commit": {"sha": "abc123"}}]}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export GIT_TAG_PREFIX="git-pages/"
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$ARTIFACT_EXISTS" = "true" ]
|
||||||
|
[ "$NEXT_VERSION" = "git-pages/0.3.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "no tag, new version calculated" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$ARTIFACT_EXISTS" = "false" ]
|
||||||
|
[ "$NEXT_VERSION" = "0.3.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "highest patch calculated correctly" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": [{"name": "0.3.0", "commit": {"sha": "def456"}}, {"name": "0.3.1", "commit": {"sha": "def456"}}]}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/VERSION"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
[ "$ARTIFACT_EXISTS" = "false" ]
|
||||||
|
[ "$NEXT_VERSION" = "0.3.2" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "VERSION_FILE=Chart-umbrella.yaml extracts only top-level version" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
export VERSION_FILE="$BATS_TEST_DIRNAME/fixtures/check-version/Chart-umbrella.yaml"
|
||||||
|
run bash scripts/check-version.sh
|
||||||
|
|
||||||
|
echo "STATUS=$status"
|
||||||
|
echo "OUTPUT=$output"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
source /tmp/build-ctx/build.env
|
||||||
|
echo "NEXT_VERSION=$NEXT_VERSION"
|
||||||
|
[ "$NEXT_VERSION" = "0.1.0" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "no version source exits with error" {
|
||||||
|
mock_set_sequence '[{"code": 200, "body": []}]'
|
||||||
|
mock_start
|
||||||
|
|
||||||
|
WORKDIR=$(mktemp -d)
|
||||||
|
SCRIPT="$PWD/scripts/check-version.sh"
|
||||||
|
run bash -c "cd '$WORKDIR' && exec bash '$SCRIPT'"
|
||||||
|
|
||||||
|
rm -rf "$WORKDIR"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"ERROR"* ]]
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
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() {
|
||||||
@@ -12,8 +13,7 @@ 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,"status":"running"}]}},
|
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}},
|
||||||
{"code":200,"body":{"id":1,"status":"running"}},
|
|
||||||
{"code":200,"body":{"id":1,"status":"running"}},
|
{"code":200,"body":{"id":1,"status":"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,"status":"running"}]}},
|
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}},
|
||||||
{"code":200,"body":{"id":1,"status":"running"}},
|
{"code":200,"body":{"id":1,"status":"running"}},
|
||||||
{"code":200,"body":{"id":1,"status":"completed","conclusion":"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,"status":"running"}]}},
|
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}},
|
||||||
{"code":200,"body":{"id":1,"status":"running"}},
|
{"code":200,"body":{"id":1,"status":"running"}},
|
||||||
{"code":200,"body":{"id":1,"status":"completed","conclusion":"cancelled"}}
|
{"code":200,"body":{"id":1,"status":"completed","conclusion":"cancelled"}}
|
||||||
]'
|
]'
|
||||||
@@ -47,18 +47,18 @@ teardown() {
|
|||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "timeout: poll never completes, exceeds timeout_minutes → exit 124" {
|
@test "timeout: no matching run found, exceeds timeout_minutes → exit 124" {
|
||||||
mock_set_sequence '[
|
mock_set_sequence '[
|
||||||
{"code":201},
|
{"code":201},
|
||||||
{"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"}}
|
{"code":200,"body":{"workflow_runs":[]}}
|
||||||
]'
|
]'
|
||||||
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,"status":"running"}]}},
|
{"code":200,"body":{"workflow_runs":[{"id":1,"display_title":"POC (test123)","run_number":42,"status":"running"}]}},
|
||||||
{"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}}
|
{"code":200,"body":{"id":1,"status":"completed","conclusion":"success"}}
|
||||||
]'
|
]'
|
||||||
mock_start
|
mock_start
|
||||||
@@ -91,6 +91,7 @@ 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" {
|
||||||
@@ -120,15 +121,15 @@ teardown() {
|
|||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "dispatch: no workflow run found after dispatch → exit 1" {
|
@test "dispatch: no workflow run found after dispatch → exit 124 (timeout)" {
|
||||||
mock_set_sequence '[
|
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"
|
run bash scripts/dispatch-workflow.sh "test-owner/test-repo" "test.yml" "main" '{}' "http://localhost:18080" "test-token-abc123" "0.001"
|
||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 124 ]
|
||||||
[[ "$output" == *"ERROR"* ]]
|
[[ "$output" == *"ERROR"* || "$output" == *"Timeout"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "missing inputs_json argument → exit 1" {
|
@test "missing inputs_json argument → exit 1" {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
Feature: GitOps update
|
||||||
|
As a GitOps repository
|
||||||
|
I want to update version references and report results to the caller
|
||||||
|
So that the deployment chain is traceable from source to GitOps commit
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given a project repository exists in Gitea
|
||||||
|
And a commit has been pushed to the repository
|
||||||
|
|
||||||
|
@mock
|
||||||
|
Scenario: Not enough env vars — script fails, no status set
|
||||||
|
Given insufficient environment variables are provided for the GitOps update
|
||||||
|
When the GitOps update script runs
|
||||||
|
Then the script exits with error
|
||||||
|
|
||||||
|
@mock
|
||||||
|
Scenario: GitOps job fails — no status set (SHA not yet known)
|
||||||
|
Given the GitOps repository clone will fail
|
||||||
|
When the GitOps update script runs
|
||||||
|
Then the script exits with error
|
||||||
|
|
||||||
|
@mock
|
||||||
|
Scenario: Everything succeeds — GitOps repo gets success status with link to caller
|
||||||
|
Given a valid GitOps update dispatch
|
||||||
|
When the GitOps update script runs
|
||||||
|
Then the script exits successfully
|
||||||
|
And the GitOps repo commit shows a success status with a link to the caller commit
|
||||||
|
|
||||||
|
@mock
|
||||||
|
Scenario: GitOps push fails — GitOps repo gets failure status
|
||||||
|
Given the GitOps repo push will fail after the version is committed
|
||||||
|
When the GitOps update script runs
|
||||||
|
Then the script exits with error
|
||||||
|
And the GitOps repo commit shows a failure status linking to the caller commit
|
||||||
|
|
||||||
|
@mock
|
||||||
|
Scenario: No changes — GitOps repo gets "no change" status
|
||||||
|
Given the version file already has the target version
|
||||||
|
When the GitOps update script runs
|
||||||
|
Then the script exits successfully
|
||||||
|
And the GitOps repo commit shows a "no change" status
|
||||||
|
And no Git commit or push was performed
|
||||||
@@ -6,10 +6,15 @@ 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 () {
|
||||||
execSync(`bash -c 'source "${MOCK_SCRIPT}" && mock_start'`, {
|
const out = execSync(`bash -c 'source "${MOCK_SCRIPT}" && mock_start && sleep 1 && curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health/check'`, {
|
||||||
cwd: PROJECT_ROOT,
|
cwd: PROJECT_ROOT,
|
||||||
stdio: 'ignore',
|
encoding: 'utf-8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
});
|
});
|
||||||
|
const trimmed = out.trim();
|
||||||
|
if (!trimmed.startsWith('2') && !trimmed.startsWith('4')) {
|
||||||
|
throw new Error(`Mock server failed to start (HTTP ${trimmed})`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
After({ tags: '@mock' }, function () {
|
After({ tags: '@mock' }, function () {
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
const { spawnSync, execSync } = require('child_process');
|
||||||
|
const { Before, After, Given, When, Then } = require('@cucumber/cucumber');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
|
||||||
|
const MOCK_SCRIPT = path.join(PROJECT_ROOT, 'tests', 'helpers', 'mock-api.sh');
|
||||||
|
const GITOPS_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'gitops-update.sh');
|
||||||
|
const MOCK_HELPERS = path.join(PROJECT_ROOT, 'tests', 'helpers');
|
||||||
|
const REQ_FILE = '/tmp/gitops-mock-requests.log';
|
||||||
|
|
||||||
|
const BASE_ENV = {
|
||||||
|
INPUT_FILE: 'dev/Chart.yaml',
|
||||||
|
YQ_TPL: '(.version) = "{{VERSION}}"',
|
||||||
|
VERSION: '0.2.3',
|
||||||
|
SOURCE_REPO: 'niko/app',
|
||||||
|
SOURCE_COMMIT: 'abc123def456',
|
||||||
|
GITOPS_REPO: 'niko/app-gitops',
|
||||||
|
GITEA_API_URL: 'http://localhost:18080',
|
||||||
|
GITEA_TOKEN: 'test-token',
|
||||||
|
};
|
||||||
|
|
||||||
|
Before({ tags: '@mock' }, function () {
|
||||||
|
process.env.PATH = `${MOCK_HELPERS}:${process.env.PATH}`;
|
||||||
|
try { execSync('rm -f /tmp/gitops-mock-requests.log', { stdio: 'ignore' }); } catch (_) {}
|
||||||
|
// Restart mock with known request file path
|
||||||
|
const result = spawnSync('bash', ['-c', `
|
||||||
|
source "${MOCK_SCRIPT}"
|
||||||
|
mock_stop 2>/dev/null
|
||||||
|
MOCK_REQUEST_FILE="${REQ_FILE}"
|
||||||
|
mock_start
|
||||||
|
sleep 0.5
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health
|
||||||
|
`], {
|
||||||
|
cwd: PROJECT_ROOT, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
const code = result.stdout.trim();
|
||||||
|
if (!code.startsWith('2') && !code.startsWith('4')) {
|
||||||
|
throw new Error(`GitOps mock restart failed: ${result.stderr.substring(0,200)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
After({ tags: '@mock' }, function () {
|
||||||
|
spawnSync('bash', ['-c', `source "${MOCK_SCRIPT}" && mock_stop 2>/dev/null`], { stdio: 'ignore' });
|
||||||
|
try { execSync('rm -f /tmp/gitops-mock-requests.log /tmp/gitops-git-calls.log', { stdio: 'ignore' }); } catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
function bash(cmd) {
|
||||||
|
const result = spawnSync('bash', ['-c', cmd], {
|
||||||
|
cwd: PROJECT_ROOT,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
|
});
|
||||||
|
return { status: result.status, stdout: result.stdout || '', stderr: result.stderr || '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFirstBody() {
|
||||||
|
return bash(`grep -A1 '^POST ' "${REQ_FILE}" 2>/dev/null | head -2 | tail -1 || echo ""`).stdout.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFirstPath() {
|
||||||
|
return bash(`grep '^POST ' "${REQ_FILE}" 2>/dev/null | head -1 | awk '{print $2}' || echo ""`).stdout.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastBody() {
|
||||||
|
return bash(`grep -A1 '^POST ' "${REQ_FILE}" 2>/dev/null | grep -v '^POST ' | tail -1 || echo ""`).stdout.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastPath() {
|
||||||
|
return bash(`grep '^POST ' "${REQ_FILE}" 2>/dev/null | tail -1 | awk '{print $2}' || echo ""`).stdout.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestCount() {
|
||||||
|
return parseInt(bash(`grep -c '^POST ' "${REQ_FILE}" 2>/dev/null || echo 0`).stdout.trim(), 10) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gitCalls() {
|
||||||
|
const callsFile = process.env.GIT_CALLS_FILE || '/dev/null';
|
||||||
|
const out = bash(`cat "${callsFile}" 2>/dev/null || echo ""`).stdout;
|
||||||
|
return out.split('\n').filter(l => l.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runScript(envOverrides) {
|
||||||
|
const env = { ...BASE_ENV, ...envOverrides };
|
||||||
|
const scriptPath = `/tmp/gitops-run-${Date.now()}.sh`;
|
||||||
|
const exports = Object.entries(env)
|
||||||
|
.map(([k, v]) => `export ${k}="${v.replace(/"/g, '\\"')}"`)
|
||||||
|
.join('\n');
|
||||||
|
require('fs').writeFileSync(scriptPath, `${exports}\nexport PATH="${MOCK_HELPERS}:$PATH"\nset -euo pipefail\nbash "${GITOPS_SCRIPT}"\nsync\n`, 'utf8');
|
||||||
|
try {
|
||||||
|
return bash(`bash "${scriptPath}"`);
|
||||||
|
} finally {
|
||||||
|
require('fs').unlinkSync(scriptPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Given('insufficient environment variables are provided for the GitOps update', function () {
|
||||||
|
this.envOverrides = { INPUT_FILE: '' };
|
||||||
|
});
|
||||||
|
|
||||||
|
Given('the GitOps repository clone will fail', function () {
|
||||||
|
this.envOverrides = { GIT_MOCK_FAIL: '1' };
|
||||||
|
});
|
||||||
|
|
||||||
|
Given('a valid GitOps update dispatch', function () {
|
||||||
|
this.envOverrides = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
Given('the GitOps repo push will fail after the version is committed', function () {
|
||||||
|
this.envOverrides = { GIT_MOCK_FAIL_PUSH: '1' };
|
||||||
|
});
|
||||||
|
|
||||||
|
Given('the version file already has the target version', function () {
|
||||||
|
this.envOverrides = {
|
||||||
|
GIT_MOCK_DIFF_NO_CHANGES: '1',
|
||||||
|
GIT_CALLS_FILE: '/tmp/gitops-git-calls.log',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
When('the GitOps update script runs', function () {
|
||||||
|
this.result = runScript(this.envOverrides || {});
|
||||||
|
});
|
||||||
|
|
||||||
|
Then('the script exits with error', function () {
|
||||||
|
if (this.result.status === 0) throw new Error(`Expected non-zero exit, got 0. stderr: ${this.result.stderr.substring(0,200)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
Then('the script exits successfully', function () {
|
||||||
|
if (this.result.status !== 0) throw new Error(`Expected exit 0, got ${this.result.status}: ${this.result.stderr.substring(0,200)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
Then('the GitOps repo commit shows a success status with a link to the caller commit', function () {
|
||||||
|
const count = requestCount();
|
||||||
|
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
|
||||||
|
const body = getFirstBody();
|
||||||
|
if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`);
|
||||||
|
if (!body.includes('"context":"app ')) throw new Error(`Expected context "app unknown", body: ${body.substring(0,200)}`);
|
||||||
|
if (!body.includes('"description":"Install to dev 0.2.3"')) throw new Error(`Expected description, body: ${body.substring(0,200)}`);
|
||||||
|
if (!body.includes('niko/app/commit/abc123def456')) throw new Error(`Expected link to caller commit, body: ${body.substring(0,200)}`);
|
||||||
|
const pathStr = getFirstPath();
|
||||||
|
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
Then('the GitOps repo commit shows a failure status linking to the caller commit', function () {
|
||||||
|
const count = requestCount();
|
||||||
|
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
|
||||||
|
const body = getFirstBody();
|
||||||
|
if (!body.includes('"state":"failure"')) throw new Error(`Expected failure state, body: ${body.substring(0,200)}`);
|
||||||
|
if (!body.includes('"context":"app ')) throw new Error(`Expected context "app unknown", body: ${body.substring(0,200)}`);
|
||||||
|
if (!body.includes('"description":"Install to dev 0.2.3"')) throw new Error(`Expected description, body: ${body.substring(0,200)}`);
|
||||||
|
if (!body.includes('niko/app/commit/abc123def456')) throw new Error(`Expected link to caller commit, body: ${body.substring(0,200)}`);
|
||||||
|
const pathStr = getFirstPath();
|
||||||
|
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
Then('the GitOps repo commit shows a "no change" status', function () {
|
||||||
|
const count = requestCount();
|
||||||
|
if (count < 1) throw new Error(`Expected at least 1 request, got ${count}`);
|
||||||
|
const body = getFirstBody();
|
||||||
|
if (!body.includes('"state":"success"')) throw new Error(`Expected success state, body: ${body.substring(0,200)}`);
|
||||||
|
if (!body.includes('"description":"Install to dev 0.2.3 \u2014 no change"')) {
|
||||||
|
throw new Error(`Expected "no change" description, body: ${body.substring(0,200)}`);
|
||||||
|
}
|
||||||
|
const pathStr = getFirstPath();
|
||||||
|
if (!pathStr.includes('/repos/niko/app-gitops/statuses/')) throw new Error(`Expected gitops repo path, got: ${pathStr}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
Then('no Git commit or push was performed', function () {
|
||||||
|
const calls = gitCalls();
|
||||||
|
if (calls.some(l => l.includes(' commit ') || l.includes(' push '))) {
|
||||||
|
throw new Error(`Expected no commit or push, got: ${calls.join(', ')}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -15,7 +15,7 @@ function bash(cmd) {
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
});
|
});
|
||||||
return { status: 0, stdout: out };
|
return { status: 0, stdout: out, stderr: '' };
|
||||||
} 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_POLL_INTERVAL="0.1"; bash "${DISPATCH_SCRIPT}" ${args}`);
|
return bash(`export DISPATCH_ID="test123"; export DISPATCH_POLL_INTERVAL="0.1"; bash "${DISPATCH_SCRIPT}" ${args}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
Given('a deployment has completed in the target environment', function () {
|
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, status: 'running' }] } },
|
{ code: 200, body: { workflow_runs: [{ id: 1, display_title: 'Workflow (test123)', run_number: 42, status: 'running' }] } },
|
||||||
{ code: 200, body: { id: 1, status: 'completed', conclusion: 'success' } },
|
{ 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, status: 'running' }] } },
|
{ code: 200, body: { workflow_runs: [{ id: 1, display_title: 'Workflow (test123)', run_number: 42, status: 'running' }] } },
|
||||||
{ code: 200, body: { id: 1, status: 'completed', conclusion: 'failure' } },
|
{ 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,15 +98,19 @@ Then('the calling pipeline reports failure', function () {
|
|||||||
When('a test workflow is dispatched but does not finish within the allowed time', function () {
|
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: [{ 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: { workflow_runs: [] } },
|
||||||
]));
|
]));
|
||||||
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123" "0.001"');
|
const r = runDispatch('"test-owner/test-repo" "test.yml" "main" \'{"version":"1.2.3"}\' "http://localhost:18080" "test-token-abc123" "0.05"');
|
||||||
this.dispatchResult = r.status;
|
this.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) throw new Error(`Expected timeout exit 124, got ${this.dispatchResult}`);
|
if (this.dispatchResult !== 124) {
|
||||||
|
throw new Error(`Expected timeout exit 124, got ${this.dispatchResult}. stderr: ${(this.dispatchStderr || '').substring(0,300)}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: test-chart
|
||||||
|
description: Test chart for version extraction
|
||||||
|
type: application
|
||||||
|
version: 0.3.0
|
||||||
|
appVersion: "1.0.0"
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
0.3.0
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version": "0.3.0"}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
<project><version>0.3.0</version></project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: subdir-chart
|
||||||
|
description: Chart in subdirectory for monorepo testing
|
||||||
|
type: application
|
||||||
|
version: 0.4.0
|
||||||
|
appVersion: "1.0.0"
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
#!/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"* ]]
|
||||||
|
}
|
||||||
Binary file not shown.
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/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
|
||||||
@@ -12,8 +12,10 @@ 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
|
||||||
[ -n "$pids" ] && kill -9 $pids 2>/dev/null || true
|
if [ -n "$pids" ]; then
|
||||||
|
kill -9 $pids 2>/dev/null || true
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_wait_port_free() {
|
_wait_port_free() {
|
||||||
@@ -24,6 +26,14 @@ _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"
|
||||||
@@ -36,7 +46,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=$(mktemp)
|
MOCK_REQUEST_FILE="${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)
|
||||||
|
|
||||||
@@ -55,7 +65,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=$!
|
||||||
sleep 0.5
|
_wait_port_ready
|
||||||
}
|
}
|
||||||
|
|
||||||
mock_stop() {
|
mock_stop() {
|
||||||
|
|||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "yq $*" >> "${YQ_CALLS_FILE:-/dev/null}"
|
||||||
Reference in New Issue
Block a user