Files
shared-actions/.github/workflows/go-pr.yaml
T
lukaszraczylo dd4f84aebd Add workflow prepare script hook to all jobs
Allow repos to optionally run a ./workflow-prepare.sh script
after checkout to handle any pre-build setup like downloading
dependencies or generating files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 12:17:22 +00:00

282 lines
8.3 KiB
YAML

name: Go Pull Request
on:
workflow_call:
inputs:
go-version:
description: "Go version to use"
required: false
type: string
default: ">=1.24"
coverage-threshold:
description: "Minimum coverage percentage required (0 to disable)"
required: false
type: number
default: 0
# Caller must declare these permissions:
# permissions:
# contents: read
# pull-requests: write
# security-events: write
jobs:
static-analysis:
name: Static Analysis
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run workflow prepare script
run: |
if [ -f "./workflow-prepare.sh" ]; then
chmod +x ./workflow-prepare.sh
./workflow-prepare.sh
fi
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
cache: true
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@latest
- name: Run go vet
run: go vet ./...
- name: Run staticcheck
run: staticcheck ./...
security-scan:
name: Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run workflow prepare script
run: |
if [ -f "./workflow-prepare.sh" ]; then
chmod +x ./workflow-prepare.sh
./workflow-prepare.sh
fi
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
cache: true
- name: Run TruffleHog
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: |
# Run govulncheck and capture output
set +e
OUTPUT=$(govulncheck -show verbose ./... 2>&1)
EXIT_CODE=$?
echo "$OUTPUT"
# If there are vulnerabilities, check if they're only in stdlib
if [ $EXIT_CODE -ne 0 ]; then
# Check if there are any third-party package vulnerabilities
# Stdlib vulnerabilities show "Found in: <pkg>@go<version>" (e.g., crypto/x509@go1.25.4)
# Third-party vulns show "Found in: <pkg>@v<version>" (e.g., github.com/foo/bar@v1.2.3)
if echo "$OUTPUT" | grep -q "Found in:"; then
# If no "Found in:" lines contain @v (third-party version), only stdlib affected
if ! echo "$OUTPUT" | grep "Found in:" | grep -qE "@v[0-9]"; then
echo ""
echo "⚠️ Only stdlib vulnerabilities found - these require a Go version upgrade"
echo "✅ No vulnerabilities in third-party dependencies"
exit 0
fi
fi
exit $EXIT_CODE
fi
gosec:
name: Gosec SARIF
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run workflow prepare script
run: |
if [ -f "./workflow-prepare.sh" ]; then
chmod +x ./workflow-prepare.sh
./workflow-prepare.sh
fi
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
cache: true
- name: Install gosec
run: go install github.com/securego/gosec/v2/cmd/gosec@latest
- name: Run gosec
run: gosec -no-fail -fmt sarif -out gosec-results.sarif ./... || true
- name: Upload gosec SARIF
if: always() && hashFiles('gosec-results.sarif') != ''
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: gosec-results.sarif
category: gosec
continue-on-error: true
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run workflow prepare script
run: |
if [ -f "./workflow-prepare.sh" ]; then
chmod +x ./workflow-prepare.sh
./workflow-prepare.sh
fi
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
cache: true
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
test:
name: Tests & Coverage
runs-on: ubuntu-latest
outputs:
coverage: ${{ steps.coverage.outputs.coverage }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run workflow prepare script
run: |
if [ -f "./workflow-prepare.sh" ]; then
chmod +x ./workflow-prepare.sh
./workflow-prepare.sh
fi
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
cache: true
- name: Run tests with coverage
run: |
go test -race -coverprofile=coverage.out -covermode=atomic ./...
go tool cover -func=coverage.out -o=coverage.txt
- name: Calculate coverage
id: coverage
run: |
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
echo "Total Coverage: $COVERAGE%"
coverage-report:
name: Coverage Report
runs-on: ubuntu-latest
needs: test
if: always() && needs.test.result == 'success'
steps:
- name: Comment coverage on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const coverage = '${{ needs.test.outputs.coverage }}';
const threshold = ${{ inputs.coverage-threshold }};
const coverageNum = parseFloat(coverage);
const hasThreshold = threshold > 0;
const meetsThreshold = !hasThreshold || coverageNum >= threshold;
const emoji = meetsThreshold ? '✅' : '⚠️';
const status = hasThreshold ? (meetsThreshold ? 'meets' : 'below') + ' threshold' : 'reported';
let body = `## ${emoji} Test Coverage Report\n\n| Metric | Value |\n|--------|-------|\n| **Total Coverage** | ${coverage}% |`;
if (hasThreshold) {
body += `\n| **Threshold** | ${threshold}% |\n| **Status** | ${emoji} Coverage ${status} |`;
}
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Test Coverage Report')
);
if (botComment) {
await github.rest.issues.updateComment({
comment_id: botComment.id,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
} else {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
}
- name: Check coverage threshold
if: inputs.coverage-threshold > 0
run: |
COVERAGE=${{ needs.test.outputs.coverage }}
THRESHOLD=${{ inputs.coverage-threshold }}
echo "Coverage: $COVERAGE%"
echo "Threshold: $THRESHOLD%"
if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
echo "❌ Coverage $COVERAGE% is below threshold $THRESHOLD%"
exit 1
fi
echo "✅ Coverage $COVERAGE% meets threshold $THRESHOLD%"