Files
cleveragents-core/.forgejo/workflows/ci.yml
T
brent.edwards 1932bed2a8
CI / benchmark-publish (pull_request) Has been skipped
CI / helm (pull_request) Successful in 41s
CI / push-validation (pull_request) Successful in 30s
CI / build (pull_request) Successful in 52s
CI / lint (pull_request) Successful in 56s
CI / quality (pull_request) Successful in 1m12s
CI / security (pull_request) Successful in 1m26s
CI / typecheck (pull_request) Successful in 1m26s
CI / benchmark-regression (pull_request) Failing after 52s
CI / integration_tests (pull_request) Successful in 3m20s
CI / e2e_tests (pull_request) Failing after 4m17s
CI / unit_tests (pull_request) Successful in 6m55s
CI / docker (pull_request) Failing after 1s
CI / coverage (pull_request) Successful in 14m32s
CI / status-check (pull_request) Failing after 3s
fix(forgejo): remove harbor.cleverthis.com from docker:dind
Fixes: #10861
2026-05-04 23:41:51 +00:00

649 lines
26 KiB
YAML

name: CI
on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop*]
vars:
docker_prefix: "http://harbor.cleverthis.com/docker/"
env:
UV_VERSION: "0.8.0"
PYTHON_VERSION: "3.13"
NOX_DEFAULT_VENV_BACKEND: "uv"
jobs:
lint:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install Node.js (required by actions/checkout)
run: |
apt-get update && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Run lint via nox
run: |
mkdir -p build
nox -s lint 2>&1 | tee build/nox-lint-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
- name: Run format check via nox
run: |
nox -s format -- --check 2>&1 | tee -a build/nox-lint-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
- name: Upload lint log artifact
if: always()
uses: actions/upload-artifact@v3
with:
name: ci-logs-lint
path: build/nox-lint-output.log
retention-days: 30
typecheck:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install Node.js (required by actions/checkout)
run: |
apt-get update && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Run typecheck via nox
run: |
mkdir -p build
nox -s typecheck 2>&1 | tee build/nox-typecheck-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
- name: Upload typecheck log artifact
if: always()
uses: actions/upload-artifact@v3
with:
name: ci-logs-typecheck
path: build/nox-typecheck-output.log
retention-days: 30
security:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install Node.js (required by actions/checkout)
run: |
apt-get update && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Run security scan via nox
run: |
mkdir -p build
nox -s security_scan 2>&1 | tee build/nox-security-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
- name: Run dead code detection via nox
run: |
nox -s dead_code 2>&1 | tee -a build/nox-security-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
- name: Upload security log artifact
if: always()
uses: actions/upload-artifact@v3
with:
name: ci-logs-security
path: build/nox-security-output.log
retention-days: 30
quality:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install Node.js (required by actions/checkout)
run: |
apt-get update && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Run complexity check via nox
run: |
mkdir -p build
nox -s complexity 2>&1 | tee build/nox-quality-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
- name: Upload quality log artifact
if: always()
uses: actions/upload-artifact@v3
with:
name: ci-logs-quality
path: build/nox-quality-output.log
retention-days: 30
unit_tests:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install system dependencies (nodejs for checkout, git for merge tests, curl/tar for Helm)
run: |
apt-get update && apt-get install -y -qq nodejs git curl tar && rm -rf /var/lib/apt/lists/*
- name: Install Helm CLI
run: |
HELM_VERSION="v3.16.4"
ARCH="amd64"
HELM_TARBALL="helm-${HELM_VERSION}-linux-${ARCH}.tar.gz"
curl -fsSL "https://get.helm.sh/${HELM_TARBALL}" -o "/tmp/${HELM_TARBALL}"
curl -fsSL "https://get.helm.sh/${HELM_TARBALL}.sha256sum" -o /tmp/helm.sha256sum
cd /tmp && sha256sum -c helm.sha256sum
tar -xzf "/tmp/${HELM_TARBALL}" -C /tmp
mv /tmp/linux-${ARCH}/helm /usr/local/bin/helm
chmod +x /usr/local/bin/helm
helm version --short
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Run unit tests via nox
run: |
mkdir -p build
nox -s unit_tests 2>&1 | tee build/nox-unit-tests-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
- name: Upload unit tests log artifact
if: always()
uses: actions/upload-artifact@v3
with:
name: ci-logs-unit-tests
path: build/nox-unit-tests-output.log
retention-days: 30
integration_tests:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install system dependencies (nodejs for checkout, git for integration tests, curl/tar for Helm)
run: |
apt-get update && apt-get install -y -qq nodejs git curl tar && rm -rf /var/lib/apt/lists/*
- name: Install Helm CLI
run: |
HELM_VERSION="v3.16.4"
ARCH="amd64"
HELM_TARBALL="helm-${HELM_VERSION}-linux-${ARCH}.tar.gz"
curl -fsSL "https://get.helm.sh/${HELM_TARBALL}" -o "/tmp/${HELM_TARBALL}"
curl -fsSL "https://get.helm.sh/${HELM_TARBALL}.sha256sum" -o /tmp/helm.sha256sum
cd /tmp && sha256sum -c helm.sha256sum
tar -xzf "/tmp/${HELM_TARBALL}" -C /tmp
mv /tmp/linux-${ARCH}/helm /usr/local/bin/helm
chmod +x /usr/local/bin/helm
helm version --short
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Run integration tests via nox
run: |
mkdir -p build
nox -s integration_tests 2>&1 | tee build/nox-integration-tests-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
CLEVERAGENTS_REQUIRE_HELM_RENDER_ASSERTIONS: "true"
# LLM API keys required for Robot Framework integration tests.
# These secrets must be configured in Forgejo UI:
# Repository Settings > Actions > Secrets
# See docs/development/ci-cd.md for details.
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Upload integration tests log artifact
if: always()
uses: actions/upload-artifact@v3
with:
name: ci-logs-integration-tests
path: build/nox-integration-tests-output.log
retention-days: 30
e2e_tests:
runs-on: docker
timeout-minutes: 45
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install system dependencies (nodejs for checkout, git for E2E tests)
run: |
apt-get update && apt-get install -y -qq nodejs git && rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Run E2E tests via nox
run: |
mkdir -p build
nox -s e2e_tests 2>&1 | tee build/nox-e2e-tests-output.log
env:
NOX_DEFAULT_VENV_BACKEND: uv
# Run E2E suites in parallel via pabot. 4 workers keeps
# wall-clock time well under the 45-minute timeout while
# staying within the memory budget of the docker runner.
TEST_PROCESSES: "4"
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
- name: Upload E2E tests log artifact
if: failure()
uses: actions/upload-artifact@v3
with:
name: ci-logs-e2e-tests
path: |
build/nox-e2e-tests-output.log
build/reports/robot-e2e/
retention-days: 30
coverage:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
# unit_tests is included so coverage only runs after tests pass,
# preventing misleading results when tests are still in-flight or failing.
needs: [lint, typecheck, security, quality, unit_tests]
steps:
- name: Install system dependencies (nodejs for checkout, git for merge tests)
run: |
apt-get update && apt-get install -y -qq nodejs git && rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Run coverage report via nox (fail-under 97%)
id: coverage
run: |
mkdir -p build
nox -s coverage_report 2>&1 | tee build/nox-coverage-output.log
# Extract the single-line CI summary from nox output
grep -E '^(nox > )?COVERAGE (OK|FAILED):' build/nox-coverage-output.log || true
env:
NOX_DEFAULT_VENV_BACKEND: uv
- name: Surface coverage summary
if: always()
run: |
if [ -f build/coverage.json ]; then
python3 -c "
import json, sys
with open('build/coverage.json') as f:
data = json.load(f)
summary = data.get('summary') or data.get('totals') or {}
pct = round(summary.get('percent_covered', 0), 1)
threshold = 50 # Temporarily lowered; see issues #4183 and #4184
if pct >= threshold:
print(f'COVERAGE OK: {pct}% (threshold: {threshold}%)')
else:
print(f'COVERAGE FAILED: {pct}% < {threshold}% threshold')
sys.exit(1)
"
else
echo "COVERAGE FAILED: no coverage data generated"
exit 1
fi
- name: Upload coverage log artifact
if: always()
uses: actions/upload-artifact@v3
with:
name: ci-logs-coverage
path: build/nox-coverage-output.log
retention-days: 30
- name: Upload coverage artifacts
if: always()
uses: actions/upload-artifact@v3
with:
name: coverage-reports
path: |
build/coverage.xml
build/coverage.json
build/htmlcov/
retention-days: 30
build:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install Node.js (required by actions/checkout)
run: |
apt-get update && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v4
- name: Install uv and nox
run: |
pip install -q uv==${{ env.UV_VERSION }} nox
- name: Cache uv packages
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
uv-
- name: Build wheel via nox
run: |
nox -s build
env:
NOX_DEFAULT_VENV_BACKEND: uv
docker:
needs: [lint, typecheck, security, quality, unit_tests]
runs-on: docker
container:
image: docker:dind
options: --privileged
steps:
- name: Start Docker daemon and install dependencies
run: |
dockerd &
apk add --no-cache git nodejs
for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break || sleep 1; done
- uses: actions/checkout@v4
- name: Build Docker image (CLI)
run: |
docker build -t cleverernie:test .
- name: Test Docker image (CLI)
run: |
docker run --rm cleverernie:test --version
- name: Build Docker image (Server)
run: |
docker build -f Dockerfile.server -t cleveragents-server:test .
helm:
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install system dependencies (nodejs for checkout, curl for Helm and kubeconform)
run: |
apt-get update && apt-get install -y -qq nodejs curl tar && rm -rf /var/lib/apt/lists/*
- uses: actions/checkout@v4
- name: Install Helm CLI
run: |
HELM_VERSION="v3.16.4"
ARCH="amd64"
HELM_TARBALL="helm-${HELM_VERSION}-linux-${ARCH}.tar.gz"
curl -fsSL "https://get.helm.sh/${HELM_TARBALL}" -o "/tmp/${HELM_TARBALL}"
curl -fsSL "https://get.helm.sh/${HELM_TARBALL}.sha256sum" -o /tmp/helm.sha256sum
cd /tmp && sha256sum -c helm.sha256sum
tar -xzf "/tmp/${HELM_TARBALL}" -C /tmp
mv /tmp/linux-${ARCH}/helm /usr/local/bin/helm
chmod +x /usr/local/bin/helm
helm version --short
- name: Install kubeconform
run: |
KUBECONFORM_VERSION="v0.7.0"
ARCH="amd64"
KUBECONFORM_TARBALL="kubeconform-linux-${ARCH}.tar.gz"
curl -fsSL "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/${KUBECONFORM_TARBALL}" \
-o "/tmp/${KUBECONFORM_TARBALL}"
tar -xzf "/tmp/${KUBECONFORM_TARBALL}" -C /tmp
mv /tmp/kubeconform /usr/local/bin/kubeconform
chmod +x /usr/local/bin/kubeconform
kubeconform -v
- name: Build Helm chart dependencies
run: |
helm dependency build ./k8s
- name: Helm lint chart
run: |
helm lint ./k8s \
--set database.url="postgresql+asyncpg://user:pass@db-host:5432/cleveragents"
- name: Helm template smoke render
run: |
helm template cleveragents ./k8s \
--set database.url="postgresql+asyncpg://user:pass@db-host:5432/cleveragents" >/tmp/rendered.yaml
test -s /tmp/rendered.yaml
- name: Validate rendered manifests with kubeconform
run: |
kubeconform \
-strict \
-ignore-missing-schemas \
-kubernetes-version 1.29.0 \
-summary \
/tmp/rendered.yaml
push-validation:
# Validates that the CI runner can authenticate and push to the repository.
# Root cause of the push failure: actions/checkout@v4 was not configured with
# explicit push credentials (token + persist-credentials), and no git user
# config (name/email) was set — both are required for any push operation.
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Install system dependencies (nodejs for checkout, git for push validation)
run: |
apt-get update && apt-get install -y -qq nodejs git curl && rm -rf /var/lib/apt/lists/*
- name: Checkout with explicit write credentials
uses: actions/checkout@v4
with:
# Pass the Forgejo token explicitly so the credential helper is
# configured for HTTPS push operations. Without this, the default
# checkout may only have read access and push will fail with a
# 403 or authentication error.
token: ${{ secrets.FORGEJO_TOKEN }}
persist-credentials: true
- name: Configure git user for CI operations
run: |
# Required for any git commit or push operation in CI.
# Uses a bot identity to distinguish CI-generated commits from
# human commits. Without this, git push fails with:
# "Author identity unknown — please tell me who you are."
git config user.name "CleverAgents CI"
git config user.email "ci-bot@cleverthis.com"
- name: Verify HTTPS credential helper is configured
run: |
# Confirm that the credential helper set up by actions/checkout
# is active. This ensures HTTPS push operations will authenticate
# correctly without prompting for a password.
echo "=== Git credential configuration ==="
git config --list | grep -E "credential|url" || echo "WARNING: No credential helper found"
echo "=== Remote URL ==="
git remote get-url origin
echo "=== Credential helper check ==="
if git config credential.helper > /dev/null 2>&1; then
echo "OK: Credential helper is configured: $(git config credential.helper)"
else
echo "WARNING: No credential helper configured — push may fail"
fi
- name: Smoke-test push access via API
# Validates write permission using the Forgejo API before attempting
# any real push. This catches credential issues early with a clear
# error message rather than a cryptic git error.
env:
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
run: |
REPO="${{ forgejo.repository }}"
API_URL="${FORGEJO_URL}/api/v1/repos/${REPO}"
echo "=== Testing repository API access ==="
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"${API_URL}")
if [ "${HTTP_STATUS}" != "200" ]; then
echo "ERROR: FORGEJO_TOKEN cannot access repository API (HTTP ${HTTP_STATUS})."
echo "Ensure FORGEJO_TOKEN is set in Repository Settings > Actions > Secrets"
echo "and that the token has repository (write) scope."
exit 1
fi
PUSH_ALLOWED=$(curl -s \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"${API_URL}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(str(d.get('permissions',{}).get('push',False)).lower())")
if [ "${PUSH_ALLOWED}" != "true" ]; then
echo "ERROR: FORGEJO_TOKEN does not have push (write) permission."
echo "Grant the token Contents: Write permission or use a token with full repository scope."
exit 1
fi
echo "OK: Push access verified -- FORGEJO_TOKEN has write permission on ${REPO}"
echo "=== Push access smoke-test passed ==="
status-check:
if: always()
needs: [lint, typecheck, security, quality, unit_tests, integration_tests, e2e_tests, coverage, build, docker, helm, push-validation]
runs-on: docker
container:
image: ${{vars.docker_prefix}}python:3.13-slim
steps:
- name: Check required job results
run: |
echo "lint: ${{ needs.lint.result }}"
echo "typecheck: ${{ needs.typecheck.result }}"
echo "security: ${{ needs.security.result }}"
echo "quality: ${{ needs.quality.result }}"
echo "unit_tests: ${{ needs.unit_tests.result }}"
echo "integration_tests: ${{ needs.integration_tests.result }}"
echo "e2e_tests: ${{ needs.e2e_tests.result }}"
echo "coverage: ${{ needs.coverage.result }}"
echo "build: ${{ needs.build.result }}"
echo "docker: ${{ needs.docker.result }}"
echo "helm: ${{ needs.helm.result }}"
echo "push-validation: ${{ needs.push-validation.result }}"
if [ "${{ needs.lint.result }}" != "success" ] || \
[ "${{ needs.typecheck.result }}" != "success" ] || \
[ "${{ needs.security.result }}" != "success" ] || \
[ "${{ needs.quality.result }}" != "success" ] || \
[ "${{ needs.unit_tests.result }}" != "success" ] || \
[ "${{ needs.integration_tests.result }}" != "success" ] || \
[ "${{ needs.e2e_tests.result }}" != "success" ] || \
[ "${{ needs.coverage.result }}" != "success" ] || \
[ "${{ needs.build.result }}" != "success" ] || \
[ "${{ needs.docker.result }}" != "success" ] || \
[ "${{ needs.helm.result }}" != "success" ] || \
[ "${{ needs.push-validation.result }}" != "success" ]; then
echo "FAILED: One or more required jobs did not succeed"
exit 1
fi
echo "All required CI checks passed"