Skip to content

feat(claw4k8s): multi-namespace watcher + Slack/Discord notifications #156

feat(claw4k8s): multi-namespace watcher + Slack/Discord notifications

feat(claw4k8s): multi-namespace watcher + Slack/Discord notifications #156

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
changes:
runs-on: ubuntu-latest
outputs:
go: ${{ steps.filter.outputs.go }}
docs: ${{ steps.filter.outputs.docs }}
hermesrs: ${{ steps.filter.outputs.hermesrs }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: filter
with:
filters: |
go:
- '**/*.go'
- 'go.mod'
- 'go.sum'
- 'Makefile'
- 'Dockerfile*'
- 'runtimes/**/Dockerfile*'
docs:
- '**/*.md'
- 'docs/**'
hermesrs:
- 'runtimes/hermesrs/**'
- 'internal/runtime/hermesrs.go'
lint:
needs: changes
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: latest
test:
needs: changes
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- name: Install setup-envtest
run: go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
- name: Run tests
run: |
export KUBEBUILDER_ASSETS="$(setup-envtest use 1.32.x -p path)"
go test -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Upload coverage
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage
path: coverage.out
build:
needs: changes
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- name: Build
run: go build ./...
- name: Vet
run: go vet ./...
verify-codegen:
needs: changes
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- name: Install controller-gen
run: go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.20.1
- name: Verify generated files are up to date
run: |
make generate
make manifests
if [ -n "$(git status --porcelain)" ]; then
echo "::error::Generated files are out of date. Run 'make generate && make manifests' and commit the changes."
git diff
exit 1
fi
docker-build:
needs: changes
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- { name: operator, file: Dockerfile, context: . }
- { name: claw4k8s, file: Dockerfile.claw4k8s, context: . }
- { name: init, file: Dockerfile.init, context: . }
- { name: ipcbus, file: Dockerfile.ipcbus, context: . }
- { name: channel-slack, file: Dockerfile.channel-slack, context: . }
- { name: channel-webhook, file: Dockerfile.channel-webhook, context: . }
- { name: openclaw, file: runtimes/openclaw/Dockerfile, context: runtimes/openclaw }
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build ${{ matrix.target.name }}
run: docker build -f ${{ matrix.target.file }} ${{ matrix.target.context }}
# Separate slow build for hermes-agent-rs (Rust full compile ~10 min).
# Only runs when the Dockerfile or adapter changes — keeps PR feedback fast.
docker-build-hermesrs:
needs: changes
if: needs.changes.outputs.hermesrs == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build hermes-agent-rs
run: |
docker build -f runtimes/hermesrs/Dockerfile \
--build-arg HERMES_REPO=https://github.com/willamhou/hermes-agent-rs.git \
--build-arg HERMES_REF=main \
-t hermesrs:ci-smoke \
runtimes/hermesrs/
govulncheck:
needs: changes
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: govulncheck ./...
coverage-gate:
needs: [changes, test]
if: needs.changes.outputs.go == 'true' && needs.test.result == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: go.mod
cache: true
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: coverage
- name: Check coverage threshold
run: |
THRESHOLD=60
TOTAL=$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $NF}' | tr -d '%')
echo "Total coverage: ${TOTAL}%"
if awk "BEGIN {exit !(${TOTAL} < ${THRESHOLD})}"; then
echo "::error::Coverage ${TOTAL}% is below threshold ${THRESHOLD}%"
exit 1
fi
dco:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Check DCO sign-off
run: |
commits=$(git log --no-merges --format='%H %s' origin/${{ github.base_ref }}..HEAD)
failed=0
while IFS= read -r line; do
[ -z "$line" ] && continue
sha=$(echo "$line" | cut -d' ' -f1)
body=$(git log -1 --format='%B' "$sha")
if ! echo "$body" | grep -q "Signed-off-by:"; then
echo "::error::Commit $sha is missing Signed-off-by line. Use 'git commit -s'."
failed=1
fi
done <<< "$commits"
if [ "$failed" -eq 1 ]; then
exit 1
fi
# Aggregate gate for branch protection.
#
# Set this single job as the sole required status check on main.
# It passes when all applicable jobs either succeeded or were correctly
# skipped (e.g., docs-only PRs skip Go jobs). It fails if any required
# job failed, was cancelled, or errored.
#
# This lets docs-only PRs merge without admin override while still
# failing fast on any real regression.
ci-gate:
needs: [changes, lint, test, build, verify-codegen, docker-build, docker-build-hermesrs, govulncheck, coverage-gate, dco]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Check all required jobs passed or were skipped
run: |
# Any failure or cancellation in a dependency fails the gate.
# 'skipped' is acceptable (path-filtered jobs on docs-only PRs).
results='${{ toJSON(needs) }}'
echo "needs results: $results"
failed=$(echo "$results" | jq -r 'to_entries[] | select(.value.result == "failure" or .value.result == "cancelled") | .key')
if [ -n "$failed" ]; then
echo "::error::One or more required jobs failed or were cancelled:"
echo "$failed"
exit 1
fi
echo "All required jobs passed or were correctly skipped."