mirror of
https://github.com/lukaszraczylo/shared-actions.git
synced 2026-06-05 22:43:43 +00:00
247 lines
7.3 KiB
YAML
247 lines
7.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: 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: 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: 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: 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: 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%"
|