diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..6d3c1bf --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,131 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +This repository provides shared, reusable GitHub Actions workflows for the go-openapi organization. The workflows are designed to be called from other go-openapi repositories to standardize CI/CD processes across the entire project family. + +## Testing & Development Commands + +### Running Tests +```bash +# Run all unit tests with coverage +gotestsum --jsonfile 'unit.report.json' -- -race -p 2 -count 1 -timeout=20m -coverprofile='unit.coverage.out' -covermode=atomic -coverpkg="$(go list)"/... ./... + +# Run a single test +go test -v -run TestName ./path/to/package + +# Run tests locally (as CI would) +# Uses the local-* workflows which mirror how other repos consume these workflows +``` + +### Linting +```bash +# Run golangci-lint (uses .golangci.yml configuration) +golangci-lint run + +# The linter configuration is highly customized with many linters disabled +# to match go-openapi's established code style +``` + +### Fuzz Testing +```bash +# List available fuzz tests across all packages +go test -list Fuzz ./... + +# Run a specific fuzz test for 1m30s +go test -fuzz=FuzzTestName -fuzztime=1m30s ./path/to/package + +# Fuzz corpus is cached in $(go env GOCACHE)/fuzz +``` + +## Architecture & Design + +### Workflow Types + +This repository contains two types of workflows: + +1. **Shared Workflows** (called by other repos): + - `go-test.yml` - Complete test pipeline with linting, unit tests, fuzz tests, coverage + - `auto-merge.yml` - Auto-approves and merges dependabot and bot PRs + - `bump-release.yml` - Manually triggered release workflow (creates signed tags) + - `tag-release.yml` - Release workflow triggered by pushing tags + - `release.yml` - Common release building workflow (called by other release workflows) + - `codeql.yml` - CodeQL security scanning for Go and GitHub Actions + - `scanner.yml` - Trivy and govulncheck vulnerability scanning + - `contributors.yml` - Automatically updates CONTRIBUTORS.md + - `collect-coverage.yml` - Collects and publishes coverage to codecov + - `collect-reports.yml` - Collects and publishes test reports + - `fuzz-test.yml` - Orchestrates fuzz testing with cached corpus + +2. **Local Test Workflows** (prefixed with `local-*`): + - These workflows test the shared workflows within this repository + - They mimic how consuming repos would invoke the shared workflows + - Example: `local-go-test.yml` calls `./.github/workflows/go-test.yml` + +### How Shared Workflows Are Used + +Other go-openapi repos consume these workflows like: + +```yaml +jobs: + test: + uses: go-openapi/ci-workflows/.github/workflows/go-test.yml@master + secrets: inherit +``` + +Recommended practice: pin to a commit SHA and let dependabot update it: +```yaml +uses: go-openapi/ci-workflows/.github/workflows/go-test.yml@b28a8b978a5ee5b7f4241ffafd6cc6163edb5dfd # v0.1.0 +``` + +### Fuzz Test Architecture + +Fuzz testing has a unique multi-stage design due to Go's limitation that `go test -fuzz` cannot run across multiple packages: + +1. **fuzz-matrix job**: Discovers all fuzz tests using `go test -list Fuzz -json` and creates a matrix +2. **fuzz-test job**: Runs each discovered fuzz test in parallel with: + - Cached corpus stored in GitHub Actions cache (max 250MB) + - Automatic cache purging to maintain size limits + - Failed corpus uploaded as artifacts for 60 days + - Default fuzz time: 1m30s, minimize time: 5m + +### Release Process + +Releases can be triggered in two ways: + +1. **Manual bump** via `bump-release.yml`: + - Select patch/minor/major bump + - Creates GPG-signed tag using bot credentials + - Triggers release build automatically + +2. **Direct tag push** via `tag-release.yml`: + - Push a semver tag (signed tags preferred) + - Tag message is prepended to release notes + - Triggers release build + +Release notes are generated using [git-cliff](https://git-cliff.org/) with configuration in `.cliff.toml`. + +### Auto-merge Logic + +The `auto-merge.yml` workflow handles two bot types: +- **dependabot[bot]**: Auto-approves all PRs, auto-merges the following groups: + - `development-dependencies` + - `go-openapi-dependencies` (for minor and patch updates only) + - `golang-org-dependencies` +- **bot-go-openapi[bot]**: Auto-approves and auto-merges all PRs (for contributor updates, etc.) + +## Key Configuration Files + +- `.golangci.yml` - Linter configuration (many linters disabled to match go-openapi style) +- `.cliff.toml` - Release notes generation configuration +- `.github/dependabot.yaml` - Dependency update configuration +- `go.mod` - Requires Go 1.24.0 + +## Important Notes + +- All workflow action versions are pinned to commit SHAs for security +- Permissions are explicitly granted at job level to follow least-privilege principle +- This repo itself uses minimal Go code (just sample tests); it's primarily YAML workflows +- The `local-*` workflows serve as both tests and documentation of proper usage diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 7ec2260..2f9b602 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -17,6 +17,9 @@ jobs: pull-requests: write runs-on: ubuntu-latest if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} steps: - name: Dependabot metadata @@ -24,16 +27,18 @@ jobs: uses: dependabot/fetch-metadata@08eff52bf64351f401fb50d4972fa95b9f2c2d1b # v2.4.0 - name: Auto-approve all dependabot PRs - env: - PR_URL: ${{github.event.pull_request.html_url}} - GH_TOKEN: ${{secrets.GITHUB_TOKEN}} run: gh pr review --approve "$PR_URL" - name: Auto-merge dependabot PRs for development dependencies if: ${{ contains(steps.metadata.outputs.dependency-group, 'development-dependencies') }} - env: - PR_URL: ${{github.event.pull_request.html_url}} - GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + run: gh pr merge --auto --rebase "$PR_URL" + - + name: Auto-merge dependabot PRs for go-openapi patches + if: ${{ contains(steps.metadata.outputs.dependency-group, 'go-openapi-dependencies') && (steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch') }} + run: gh pr merge --auto --rebase "$PR_URL" + - + name: Auto-merge dependabot PRs for golang.org updates + if: ${{ contains(steps.metadata.outputs.dependency-group, 'golang-org-dependencies') }} run: gh pr merge --auto --rebase "$PR_URL" actions-bot: @@ -57,43 +62,10 @@ jobs: run: gh pr review --approve "$PR_URL" - name: Wait for all workflow runs to complete - run: | - PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$') - echo "Waiting for all workflow runs to complete on PR #${PR_NUMBER}..." - - # Get the PR's head SHA - HEAD_SHA=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid') - echo "Head SHA: ${HEAD_SHA}" - - MAX_WAIT=600 # 10 minutes max wait - ELAPSED=0 - SLEEP_INTERVAL=10 - - while [ $ELAPSED -lt $MAX_WAIT ]; do - # Get all workflow runs for this SHA using the GitHub API - RUNS=$(gh api "/repos/${{ github.repository }}/actions/runs?head_sha=${HEAD_SHA}&per_page=100" --jq '.workflow_runs') - - # Count how many are still in progress, queued, or waiting - IN_PROGRESS=$(echo "$RUNS" | jq '[.[] | select(.status == "in_progress" or .status == "queued" or .status == "waiting")] | length') - - if [ "$IN_PROGRESS" -eq 0 ]; then - echo "::notice::All workflow runs completed for SHA ${HEAD_SHA}" - TOTAL_RUNS=$(echo "$RUNS" | jq 'length') - echo "Total workflow runs: ${TOTAL_RUNS}" - break - fi - - # Show which workflows are still running - RUNNING=$(echo "$RUNS" | jq -r '.[] | select(.status == "in_progress" or .status == "queued" or .status == "waiting") | .name' | tr '\n' ', ' | sed 's/,$//') - echo "Still waiting for ${IN_PROGRESS} workflow run(s): ${RUNNING}" - - sleep $SLEEP_INTERVAL - ELAPSED=$((ELAPSED + SLEEP_INTERVAL)) - done - - if [ $ELAPSED -ge $MAX_WAIT ]; then - echo "::warning::Timeout waiting for all workflow runs to complete, proceeding anyway" - fi + uses: go-openapi/gh-actions/ci-jobs/wait-pending-jobs@583ffca16d371aa3df4b9d5ac58c88050df94a76 # v1.1.0 + with: + pr-url: ${{ env.PR_URL }} + gh-token: ${{ secrets.GITHUB_TOKEN }} - name: Auto-merge bot-go-openapi PRs run: gh pr merge --auto --rebase "$PR_URL" diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index 94d4a1a..de1bd83 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -85,43 +85,10 @@ jobs: run: gh pr review --approve "$PR_URL" - name: Wait for all workflow runs to complete - run: | - PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$') - echo "Waiting for all workflow runs to complete on PR #${PR_NUMBER}..." - - # Get the PR's head SHA - HEAD_SHA=$(gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid') - echo "Head SHA: ${HEAD_SHA}" - - MAX_WAIT=600 # 10 minutes max wait - ELAPSED=0 - SLEEP_INTERVAL=10 - - while [ $ELAPSED -lt $MAX_WAIT ]; do - # Get all workflow runs for this SHA using the GitHub API - RUNS=$(gh api "/repos/${{ github.repository }}/actions/runs?head_sha=${HEAD_SHA}&per_page=100" --jq '.workflow_runs') - - # Count how many are still in progress, queued, or waiting - IN_PROGRESS=$(echo "$RUNS" | jq '[.[] | select(.status == "in_progress" or .status == "queued" or .status == "waiting")] | length') - - if [ "$IN_PROGRESS" -eq 0 ]; then - echo "::notice::All workflow runs completed for SHA ${HEAD_SHA}" - TOTAL_RUNS=$(echo "$RUNS" | jq 'length') - echo "Total workflow runs: ${TOTAL_RUNS}" - break - fi - - # Show which workflows are still running - RUNNING=$(echo "$RUNS" | jq -r '.[] | select(.status == "in_progress" or .status == "queued" or .status == "waiting") | .name' | tr '\n' ', ' | sed 's/,$//') - echo "Still waiting for ${IN_PROGRESS} workflow run(s): ${RUNNING}" - - sleep $SLEEP_INTERVAL - ELAPSED=$((ELAPSED + SLEEP_INTERVAL)) - done - - if [ $ELAPSED -ge $MAX_WAIT ]; then - echo "::warning::Timeout waiting for all workflow runs to complete, proceeding anyway" - fi + uses: go-openapi/gh-actions/ci-jobs/wait-pending-jobs@583ffca16d371aa3df4b9d5ac58c88050df94a76 # v1.1.0 + with: + pr-url: ${{ env.PR_URL }} + gh-token: ${{ secrets.GITHUB_TOKEN }} - name: Auto-merge PR run: gh pr merge --auto --rebase "$PR_URL"