Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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
58 changes: 15 additions & 43 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,28 @@ 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}}
Comment on lines +21 to +22
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's inconsistent spacing in the GitHub Actions expressions. Lines 21-22 use ${{ without a space after the opening braces, while the action parameters at lines 67-68 use ${{ with spaces. For consistency and readability, consider using consistent spacing throughout (e.g., ${{ github.event.pull_request.html_url }} and ${{ secrets.GITHUB_TOKEN }}).

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok forthcoming PR following up

steps:
-
name: Dependabot metadata
id: metadata
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:
Expand All @@ -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"
41 changes: 4 additions & 37 deletions .github/workflows/contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"