Feature/docker kuntoon (#11)

Co-authored-by: moilanik <niko.moilanen@tietoevry.com>
Reviewed-on: #11
This commit is contained in:
2026-06-15 17:22:04 +03:00
parent 3d45b08f70
commit 73d7ec1669
27 changed files with 994 additions and 801 deletions
-30
View File
@@ -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"
-124
View File
@@ -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
+64
View File
@@ -0,0 +1,64 @@
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 }}
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
- name: Check existing artifact and calculate version
run: |
RAW_VERSION=$(jq -r '.version' package.json)
BASE_VERSION=$(echo "$RAW_VERSION" | cut -d'.' -f1-2)
echo "gitea-ci-library - Tunnistettu Major.Minor versio: $BASE_VERSION"
TAGS_JSON=$(curl -s -f -H "Authorization: token $GITEA_TOKEN" \
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/tags")
TAG=$(echo "$TAGS_JSON" | jq -r 'if type == "array" then .[] | select(.commit.sha == "${{ github.sha }}") | .name else empty end' | head -1)
mkdir -p /tmp/build-ctx
if [ -n "$TAG" ]; then
echo "ARTIFACT_EXISTS=true" > /tmp/build-ctx/build.env
echo "NEXT_VERSION=$TAG" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Artefakti löytyi jo tagilla: $TAG."
else
echo "ARTIFACT_EXISTS=false" > /tmp/build-ctx/build.env
HIGHEST_PATCH=$(echo "$TAGS_JSON" | jq -r --arg bv "$BASE_VERSION." '
if type == "array" then .[] | .name | select(startswith($bv)) | sub($bv; "") | tonumber else empty end' | sort -rn | head -1)
if [ -z "$HIGHEST_PATCH" ]; then NEXT_PATCH=0; else NEXT_PATCH=$((HIGHEST_PATCH + 1)); fi
FULL_VERSION="${BASE_VERSION}.${NEXT_PATCH}"
echo "NEXT_VERSION=$FULL_VERSION" >> /tmp/build-ctx/build.env
echo "gitea-ci-library - Uusi vapaa versio: $FULL_VERSION"
fi
- name: Set job outputs
id: set-outputs
run: |
source /tmp/build-ctx/build.env
echo "artifact_exists=$ARTIFACT_EXISTS" >> "$GITHUB_OUTPUT"
echo "version=$NEXT_VERSION" >> "$GITHUB_OUTPUT"
-34
View File
@@ -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
+24 -1
View File
@@ -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"
+101
View File
@@ -0,0 +1,101 @@
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 || '' }}
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: |
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
docker build \
--label "git.commit=${{ github.sha }}" \
--label "git.commitBy=${{ github.actor }}" \
--label "build.date=${NOW}" \
-t "${DOCKER_IMAGE_NAME}:${VERSION}" .
REGISTRY="${DOCKER_REGISTRY:?DOCKER_REGISTRY not set in env.conf}"
IMAGE="${DOCKER_IMAGE_NAME:?DOCKER_IMAGE_NAME not set in env.conf}"
REGISTRY_HOST="${REGISTRY%%/*}"
FULL_IMAGE="${REGISTRY}/${IMAGE}:${VERSION}"
echo "Pushing ${FULL_IMAGE} ..."
docker tag "${DOCKER_IMAGE_NAME}:${VERSION}" "$FULL_IMAGE"
echo "$DOCKER_PASSWORD" | docker login "$REGISTRY_HOST" -u "$DOCKER_USERNAME" --password-stdin
docker push "$FULL_IMAGE"
docker logout "$REGISTRY_HOST"
- name: Report status SUCCESS
if: success()
run: |
CONTAINER_URL=""
if [ -n "${DOCKER_UI_URL:-}" ] && [ -n "${VERSION:-}" ]; then
CONTAINER_URL="${DOCKER_UI_URL}/${VERSION}"
fi
bash .ci/scripts/report-status.sh success "Docker build & push ${VERSION} OK" ci-docker-build-push "" "$CONTAINER_URL"
- name: Report status FAILURE
if: failure()
run: bash .ci/scripts/report-status.sh failure "Docker build & push ${VERSION} FAILED" ci-docker-build-push
tag-commit:
runs-on: ubuntu-latest
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\": \"${VERSION}\", \"message\": \"Build #$RUN_NUMBER\", \"target\": \"$SHA\"}")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "409" ]; then
exit 0
else
exit 1
fi
+64
View File
@@ -0,0 +1,64 @@
name: Bats Tests
on:
workflow_call:
inputs:
env_json:
required: true
type: string
bats-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:
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: Report status
if: always()
run: |
if [ "${BATS_EXIT}" = "0" ]; then
bash .ci/scripts/report-status.sh success "Link to Bats reports" unit-tests bats
else
bash .ci/scripts/report-status.sh failure "Link to Bats reports" unit-tests bats
fi
@@ -0,0 +1,70 @@
name: Cucumber Tests
on:
workflow_call:
inputs:
env_json:
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:
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=$?
echo "CUCUMBER_EXIT=${CUCUMBER_EXIT}" >> "${GITHUB_ENV}"
exit ${CUCUMBER_EXIT}
- name: Publish cucumber reports
if: always()
run: bash .ci/scripts/publish-git-pages.sh cucumber
- name: Report status
if: always()
shell: bash
run: |
if [ "${CUCUMBER_EXIT}" = "0" ]; then
if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
bash .ci/scripts/report-status.sh success "Link to Cucumber reports" acc-tests cucumber
else
bash .ci/scripts/report-status.sh success "Link to Cucumber reports" acc-tests
fi
else
if [ -f "reports/${GITHUB_SHA:0:8}/cucumber/index.html" ]; then
bash .ci/scripts/report-status.sh failure "Link to Cucumber reports" acc-tests cucumber
else
bash .ci/scripts/report-status.sh failure "Link to Cucumber reports" acc-tests
fi
fi
+41
View File
@@ -0,0 +1,41 @@
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 }}
bats-image: bats/bats:latest
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 }}
cucumber-node-image: node:22
report-summary:
name: Report Summary
needs: [load-config, bats, cucumber]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/example-report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: bats cucumber
+5
View File
@@ -0,0 +1,5 @@
GITEA_API_URL=https://gitea.app.keskikuja.site
GIT_PAGES_URL=https://ci-reports.helm-dev.keskikuja.site
DOCKER_REGISTRY=gitea.app.keskikuja.site/niko
DOCKER_IMAGE_NAME=gitea-ci-library-test-image
DOCKER_UI_URL=https://gitea.app.keskikuja.site/niko/-/packages/container/gitea-ci-library-test-image
+61
View File
@@ -0,0 +1,61 @@
name: CI Main
on:
push:
branches:
- 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
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 }}
bats-image: bats/bats:latest
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 }}
cucumber-node-image: node:22
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 }}
report-summary:
name: Report Summary
needs: [load-config, bats, cucumber]
if: always()
uses: niko/gitea-ci-library/.gitea/workflows/example-report-summary.yml@main
with:
env_json: ${{ needs.load-config.outputs.env_json }}
suites: bats cucumber
@@ -0,0 +1,34 @@
name: Report Summary
on:
workflow_call:
inputs:
env_json:
required: true
type: string
suites:
required: true
type: string
description: Space-separated suite names published to git-pages
env:
GIT_PAGES_URL: ${{ fromJson(inputs.env_json).GIT_PAGES_URL }}
jobs:
summary:
runs-on: ubuntu-latest
steps:
- name: Generate report links
shell: bash
run: |
SHA8="${GITHUB_SHA:0:8}"
BASE="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${SHA8}"
{
echo "## Test Reports"
echo ""
echo "| Suite | Report |"
echo "|-------|--------|"
for suite in ${{ inputs.suites }}; do
echo "| ${suite} | [View report](${BASE}/${suite}/) |"
done
} >> "${GITHUB_STEP_SUMMARY}"
-2
View File
@@ -1,2 +0,0 @@
GITEA_API_URL=https://gitea.app.keskikuja.site
GIT_PAGES_URL=https://ci-reports.helm-dev.keskikuja.site
-133
View File
@@ -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
+26 -4
View File
@@ -102,34 +102,56 @@ 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_TOKEN="<registration-token>"
GITEA_ACTIONS_NAMESPACE="gitea-actions" GITEA_ACTIONS_NAMESPACE="gitea-actions"
```
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.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
+8 -9
View File
@@ -6,8 +6,8 @@ Provider-repossa (`gitea-ci-library`) kansioiden omistajuus on seuraava:
| Kansio / Tiedosto | Omistaja | Tyyppi | | Kansio / Tiedosto | Omistaja | Tyyppi |
|-------------------|----------|--------| |-------------------|----------|--------|
| `.gitea/workflows/` | Sekoitettu | Providerin reusable workflowt + consumerin 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)
+85
View File
@@ -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.
+73
View File
@@ -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}`.
+40 -62
View File
@@ -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-15 (siivottu, provider/consumer-erottelu valmis)
## Project Overview ## Project Overview
Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-, Gitea Actions reusable workflow -kirjasto mikropalveluiden build-, testaus-,
@@ -8,90 +8,68 @@ 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-kirjasto: reusable workflowt, scriptit, ADRt, dokumentaatio.
Consumer kutsuu `build-feature.yml`-workflowa `uses:`-direktiivillä. 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 (00010005) | | `.gitea/workflows/example-*` | **Consumer-esimerkki**: tämän repon oma CI (dogfood) |
| `scripts/` | Provider-skriptit: `report-status.sh`, `publish-git-pages.sh`, `ci-validate.sh` |
| `.gitea/scripts/` | **Consumer-skriptit**: `bats-coverage.sh`, `bats-report.sh` |
| `docs/` | Arkkitehtuuri, ADRt (00040008) |
| `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 (3 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. |
### 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-report-summary.yml` | workflow_call | `GITHUB_STEP_SUMMARY`-taulukko raporttilinkeillä (Gitea 1.27+) |
| `example-gitea-env.conf` | — | KEY=VALUE config tälle repolle |
## 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**: `example-*`-tiedostot ovat consumer-esimerkkejä, provider-workflowt reusableja. 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
View File
@@ -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 |
| `example-report-summary.yml` | Consumer | `GITHUB_STEP_SUMMARY`-taulukko (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)
+2 -1
View File
@@ -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
+72 -336
View File
@@ -1,390 +1,126 @@
# 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:** `env_json`, `version`
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 **Secrets:** `GITEA_TOKEN`, `DOCKER_USERNAME`, `DOCKER_PASSWORD`
Branch protection -säännössä Giteassa vaaditaan ennen PR:n sulkemista: **Steppi-kaavio:**
- **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)
Consumer voi halutessaan buildata kontin ja deployata sen väliaikaiseen
PR-ympäristöön. Tämä on optionaalinen continuation-haara, joka
aktivoituu ehdolla:
- PR:ssä on tietty label (esim. `preview`)
- Commit message sisältää triggerisanan (esim. `[preview]`)
**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
``` ```
build-push (build + push samassa jobissa, ei levyn kautta) → tag-commit
1. Quality-gate läpäisty (testit + skannaukset ok)
2. Buildaa kontti, tagi sisältää PR-numeron (`pr-42`)
3. Deployaa PR-ympäristöön (preview/review app)
4. Asettaa commit-statuksen linkillä ympäristöön
5. **PR:n sulkeutuessa** (merge/close): cleanup-job tuhoaa ympäristön
Tämä on **consumerin vastuulla** — provider tarjoaa tarvittavat
skriptit (`publish-git-pages.sh`, `report-status.sh`), mutta
trigger-ehto, kontin buildaus ja ympäristön hallinta kuuluvat
consumerin pipelineen.
---
## `ci-master.yml` — Main-branch build
**Trigger:** `workflow_call` — kutsutaan main-branchiin pushattaessa
**Rooli:** Buildaa artifaktin (kontti, JAR, npm-paketti tms.) ja julkaisee
sen rekisteriin. Jos sama commit on jo buildattu (version tag on olemassa),
build skipataan ja siirrytään suoraan test flow'hun.
**Provider-Consumer-malli (ADR 0005):** Provider orkestroi idempotent
build-logiikan (`isArtifactBuilt`-tarkistus), mutta consumer omistaa
build-stepit — valitsee työkalut ja artifaktityypin.
### isArtifactBuilt-check
Ennen buildia tarkistetaan, onko tälle commitille jo olemassa versiotagi:
```bash
TAG=$(git tag --points-at HEAD | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
if [ -n "$TAG" ]; then
echo "artifact_already_built=true" >> $GITHUB_ENV
echo "artifact_version=$TAG" >> $GITHUB_ENV
fi
```
Jos tagi löytyy, build- ja push-stepit skipataan. Committia vastaan on
jo olemassa artifakti rekisterissä — uudelleenbuildaus aiheuttaisi
versiokonflikteja ja tuhlaisi CI-aikaa.
### Steppi-kaavio
```mermaid
%%{init: {'theme': 'base', 'flowchart': {'arrowheadScale': 2}}}%%
flowchart TD
CHECK{"isArtifactBuilt?
git tag --points-at HEAD"}
CHECK -- "ei" --> QG["quality-gate
testit + skannaukset"]
QG --> BUILD["build-artifact
consumer: docker build /
mvn package / npm build"]
BUILD --> PUSH["push registry
gitea packages /
docker registry"]
PUSH --> TAG["tag-commit
tagittaa commitin
versiolla (esim. 1.2.3.${RUN})"]
CHECK -- "kyllä" --> K8S["continueToTestFlow
(future: K8s-testit
test plan -mukaan)"]
TAG --> K8S
FAIL("fail") -. "quality-gate
ei läpäisty" .-> END
K8S --> END(["end
commit-status"])
style CHECK fill:#f59e0b,color:#111827
style QG fill:#059669,color:#ffffff
style BUILD fill:#0891b2,color:#ffffff
style PUSH fill:#dc2626,color:#ffffff
style TAG fill:#f59e0b,color:#111827
style K8S fill:#7c3aed,color:#ffffff
style FAIL fill:#dc2626,color:#ffffff
style END fill:#2563eb,color:#ffffff
linkStyle default stroke:#9ca3af,stroke-width:3px
```
### Elinkaari
1. **isArtifactBuilt?** — tarkista onko tagi olemassa
2. **quality-gate** — jos ei tagia, aja `quality-gate.yml` (testit, skannaukset)
3. **build-artifact** — jos quality-gate läpäisty, buildaa artifakti
4. **push registry** — julkaise rekisteriin (Gitea Packages, Docker registry, jne.)
5. **tag-commit** — tagittaa commitin versiolla (esim. `1.2.3.<run_number>`)
6. **continueToTestFlow***(future)* aja K8s-testit test plan -mukaan
7. **commit-status** — aseta lopullinen status
### Concurrency
```yaml
concurrency:
group: master-${{ github.repository }}
cancel-in-progress: false
```
Vain yksi master-build kerrallaan per repo. Ei cancel-in-progress —
käynnissä olevan buildin annetaan valmistua.
---
## `deploy.yml` — GitOps-deployment
**Trigger:** `workflow_dispatch` (aina dispatchataan toisesta workflow'sta)
**Elinkaari:**
```
start → read-yaml → update-value → commit → push → report-cross-repo → end
```
### Inputs (dispatch-parametrit)
| Parametri | Kuvaus |
|-----------|--------|
| `environment` | Ympäristön nimi (korvaa `{.environment}`) |
| `version` | Uusi konttiversio |
| `root_commit` | Mikropalvelun commit josta deploy käynnistyi |
| `root_repo` | Mikropalvelun repo |
| `root_build_url` | URL mikropalvelun buildiin |
### Mitä deploy tekee
1. Lukee `{projectFolder}/{fileName}` YAML-tiedoston (korvaa `{.environment}``environment`)
2. Päivittää `{property}`-avaimen arvoksi `{version}`
3. `git add`, `git commit -m "deploy {version} to {environment}"`
4. `git push origin HEAD:master`
5. Raportoi statuksen:
- Helm-repon committiin: **"from {root_commit}"**, URL → root-build
- Mikropalvelun committiin (`root_commit`): **"deployed to {environment}"**, URL → Helm-commit
6. Palauttaa Helm-commitin hashin (`outputs.commit`)
### Concurrency
```yaml
concurrency:
group: deploy-${{ github.repository }}-${{ inputs.environment }}
cancel-in-progress: false
``` ```
--- ---
## `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 → check-version →
Ennen testejä varmistetaan, että ympäristössä pyörii oikea versio: [artifact exists] → done
[no artifact] → bats + cucumber → report-summary (always) → docker-build-push
```yaml
- name: Check deployed version
if: inputs.versionCheckScript || inputs.versionApiUrl
run: |
if [ -n "${{ inputs.versionCheckScript }}" ]; then
bash "${{ inputs.versionCheckScript }}" "${{ inputs.versionApiUrl }}" "${{ inputs.version }}"
fi
``` ```
Version check -skripti pollaa Fibonacci-backoffilla — ks. [config-model.md](config-model.md). ### `example-bats-tests.yml` — Bats unit-testit
### Cross-repo-raportointi **Trigger:** `workflow_call`
Testien jälkeen raportoidaan kolmeen committiin: Ajaa Bats-testit Docker-kontissa, generoi coveragen (`bashcov`), julkaisee
raportit git-pagesiin, asettaa commit-statuksen linkillä raporttiin.
1. Testi-repon oma commit: testin status ### `example-cucumber-tests.yml` — Cucumber hyväksymätestit
2. Mikropalvelun commit (`root_commit`): "testit OK/epäonnistui"
3. Helm-repon commit (`deploy_commit`): "testattu v{version}"
### Concurrency **Trigger:** `workflow_call`
```yaml Ajaa Cucumber-testit Node-kontissa, julkaisee raportit git-pagesiin, asettaa
concurrency: commit-statuksen linkillä raporttiin.
group: test-${{ inputs.environment }}
cancel-in-progress: false ### `example-report-summary.yml` — Raporttien koontinäkymä
```
**Trigger:** `workflow_call` — ajetaan `if: always()` testien jälkeen
**Inputs:** `env_json`, `suites` (space-separated lista suite-nimistä)
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.
---
## Suunnitteilla
- `deploy.yml` — GitOps-deployment (dispatch-workflow.sh-pohjainen)
- `test.yml` — Klusteritason test flow
+26 -9
View File
@@ -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,15 +25,32 @@ branch_exists() {
return 0 return 0
fi fi
status=$(curl -sS -o /dev/null -w "%{http_code}" \ # Retry up to 2 times on API errors (hardcoded)
-H "Authorization: token ${GITEA_TOKEN}" \ for attempt in 1 2 3; do
"${GITEA_API_URL}/api/v1/repos/${owner}/${repo}/branches/${branch}" 2>/dev/null || echo "000") status=$(curl -sS -o /dev/null -w "%{http_code}" \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_API_URL}/api/v1/repos/${owner}/${repo}/branches/${branch}" 2>/dev/null || echo "000")
if [ "$status" = "200" ]; then if [ "$status" = "200" ]; then
BRANCH_CACHE[$key]=1 BRANCH_CACHE[$key]=1
return 0 return 0
fi fi
return 1
if [ "$status" = "404" ]; then
return 1
fi
# API error - retry if not last attempt
if [ "$attempt" -lt 3 ]; then
sleep 10
continue
fi
done
# All retries failed - keep report (fail-safe)
echo " WARN: Gitea API error for ${owner}/${repo}/${branch} (status ${status}) after 3 attempts - KEEPING report"
BRANCH_CACHE[$key]=1
return 0
} }
default_max_age=$(jq -r '.branches.default.maxAgeDays // 90' "$CONFIG") default_max_age=$(jq -r '.branches.default.maxAgeDays // 90' "$CONFIG")
+112
View File
@@ -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)
+25 -25
View File
@@ -1,27 +1,27 @@
{ {
"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": {
"doc": "docs", "doc": "docs",
"test": "tests" "test": "tests"
}, },
"scripts": { "scripts": {
"test": "npm run test:bats && npm run test:cucumber", "test": "npm run test:bats && npm run test:cucumber",
"test:bats": "mkdir -p reports && docker run --rm -v \"$(pwd):/repo:ro\" -v \"$(pwd)/reports:/repo/reports\" -w /repo --entrypoint bash bats/bats:latest -c 'apk add -q python3 curl jq lsof ruby && gem install bashcov -q > /dev/null 2>&1; bats tests/'", "test:bats": "mkdir -p reports && docker run --rm -v \"$(pwd):/repo:ro\" -v \"$(pwd)/reports:/repo/reports\" -w /repo --entrypoint bash bats/bats:latest -c 'apk add -q python3 curl jq lsof ruby && gem install bashcov -q > /dev/null 2>&1; bats tests/'",
"test:bats:coverage": "mkdir -p reports && docker run --rm -v \"$(pwd):/repo\" -v \"$(pwd)/reports:/repo/reports\" -w /repo --entrypoint bash bats/bats:latest -c 'apk add -q python3 curl jq lsof ruby && gem install bashcov -q > /dev/null 2>&1; bashcov -- bats tests/'", "test:bats:coverage": "mkdir -p reports && docker run --rm -v \"$(pwd):/repo\" -v \"$(pwd)/reports:/repo/reports\" -w /repo --entrypoint bash bats/bats:latest -c 'apk add -q python3 curl jq lsof ruby && gem install bashcov -q > /dev/null 2>&1; bashcov -- bats tests/'",
"test:cucumber": "docker run --rm -v \"$(pwd):/repo:ro\" -v \"$(pwd)/node_modules:/repo/node_modules\" -w /repo --entrypoint bash node:22 -c 'apt-get update -qq && apt-get install -y -qq jq lsof && npm ci && npx cucumber-js tests/features/ --tags @mock and ~@wip'" "test:cucumber": "docker run --rm -v \"$(pwd):/repo:ro\" -v \"$(pwd)/node_modules:/repo/node_modules\" -w /repo --entrypoint bash node:22 -c 'apt-get update -qq && apt-get install -y -qq jq lsof && npm ci && npx cucumber-js tests/features/ --tags @mock and ~@wip'"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "ssh://git@gitea.app.keskikuja.site:30009/niko/gitea-ci-library.git" "url": "ssh://git@gitea.app.keskikuja.site:30009/niko/gitea-ci-library.git"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"type": "commonjs", "type": "commonjs",
"devDependencies": { "devDependencies": {
"@cucumber/cucumber": "^13.0.0" "@cucumber/cucumber": "^13.0.0"
} }
} }
+10 -5
View File
@@ -1,17 +1,22 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# https://docs.gitea.com/api/next/#tag/repository/operation/repoCreateStatus
STATE="${1:-}" STATE="${1:-}"
DESCRIPTION="${2:-}" DESCRIPTION="${2:-}"
KEY="${3:-commit-${GITHUB_SHA:0:8}}" KEY="${3:-commit-${GITHUB_SHA:0:8}}"
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}" URL="${GIT_PAGES_URL}/${GITHUB_REPOSITORY}/reports/${GITHUB_SHA:0:8}/${SUITE}"
else else
@@ -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
@@ -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 0.3 && curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:18080/api/v1/repos/health/check'`, {
cwd: PROJECT_ROOT, cwd: PROJECT_ROOT,
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 () {