Files
trivy/.github/workflows/apidiff.yaml
2025-12-11 08:17:40 +00:00

183 lines
7.1 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: API Diff Check
on:
# SECURITY: Using pull_request_target to support fork PRs with write permissions.
# PR code is checked out but only for static analysis - it is never executed.
# If modifying this workflow, ensure PR code is never executed and user inputs are not used unsafely.
pull_request_target:
types: [opened, synchronize]
paths:
- 'pkg/**/*.go'
- 'rpc/**/*.go'
permissions:
contents: read
pull-requests: write
issues: write
jobs:
apidiff:
runs-on: ubuntu-24.04
name: API Diff Check
steps:
# Check if PR has conflicts. When conflicts exist, the merge commit becomes
# frozen at an old state and apidiff cannot run correctly.
- name: Check for merge conflicts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
# pull_request_target and mergeability are processed asynchronously.
# As a result, its possible that we start the check before GitHub has finished calculating the mergeability.
# To handle this, a retry mechanism has been added — it waits for 2 seconds after each attempt.
# If mergeable_state isnt obtained after 5 attempts, an error is returned.
run: |
MAX=5
for i in $(seq 1 "$MAX"); do
state=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .mergeable_state)
echo "mergeable_state=$state"
if [ "$state" = "dirty" ]; then
echo "::error::This PR has merge conflicts. Please resolve conflicts before running apidiff."
exit 1
fi
if [ -n "$state" ] && [ "$state" != "unknown" ] && [ "$state" != "null" ]; then
break
fi
if [ "$i" -lt "$MAX" ] && { [ -z "$state" ] || [ "$state" = "unknown" ] || [ "$state" = "null" ]; }; then
echo "::error::Could not determine mergeability after $i tries."
exit 1
fi
sleep 2
done
# Checkout PR merge commit to compare against base branch
# This ensures we compare the actual merge result with the base branch,
# avoiding false positives when PR is not rebased with latest main
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Set up Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
check-latest: true # Ensure we use the latest Go patch version
cache: false
# Ensure the base commit exists locally for go-apidiff to compare against.
# Even though we checkout the merge commit, go-apidiff needs the base ref to exist.
# Use base.ref instead of base.sha, since base.sha is outdated (not updated after every commit).
# cf. https://github.com/orgs/community/discussions/59677
- name: Fetch base commit
id: fetch_base
run: |
set -euo pipefail
BASE_REF="${{ github.event.pull_request.base.ref || github.event.merge_group.base_ref }}"
if [ -z "${BASE_REF:-}" ]; then
echo "::error::BASE_REF is empty (no base ref in event payload)"; exit 1
fi
git fetch --depth=1 origin "$BASE_REF"
BASE_SHA="$(git rev-parse "origin/$BASE_REF")"
if [ -z "${BASE_SHA:-}" ]; then
echo "::error::BASE_SHA is empty (failed to resolve origin/$BASE_REF)"; exit 1
fi
echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT"
# NOTE: go-apidiff is not managed in go.mod because installing it via `go get -tool`
# would cause `mage tool:install` to attempt building it on Windows, which currently
# fails due to platform-specific issues.
- name: Run go-apidiff
id: apidiff
continue-on-error: true
uses: joelanford/go-apidiff@60c4206be8f84348ebda2a3e0c3ac9cb54b8f685 # v0.8.3
with:
base-ref: ${{ steps.fetch_base.outputs.base_sha }}
version: v0.8.3
- name: Add apidiff label
if: ${{ steps.apidiff.outputs.semver-type == 'major' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const label = 'apidiff';
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: [label],
});
- name: Comment API diff
if: ${{ steps.apidiff.outputs.semver-type == 'major' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
APIDIFF_OUTPUT: ${{ steps.apidiff.outputs.output }}
SEMVER_TYPE: ${{ steps.apidiff.outputs.semver-type }}
with:
script: |
const header = '## 📊 API Changes Detected';
const diff = process.env.APIDIFF_OUTPUT.trim();
const semver = process.env.SEMVER_TYPE || 'unknown';
const body = [
header,
'',
`Semver impact: \`${semver}\``,
'',
'```',
diff,
'```',
].join('\n');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.startsWith(header),
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
# Attempt to request the premium reviewers; needs org-scoped token because GITHUB_TOKEN lacks read:org.
- name: Request trivy-premium review
if: ${{ steps.apidiff.outputs.semver-type == 'major' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{ secrets.ORG_REPO_TOKEN }}
script: |
try {
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
team_reviewers: ['trivy-premium'],
});
console.log('Requested review from aquasecurity/trivy-premium team');
} catch (error) {
core.error(`Failed to request trivy-premium reviewers: ${error.message}`);
throw error;
}