163 lines
5.7 KiB
YAML
163 lines
5.7 KiB
YAML
name: Check Approvals
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, ready_for_review]
|
|
pull_request_review:
|
|
types: [submitted]
|
|
|
|
jobs:
|
|
check_owners:
|
|
name: Check OWNERS approval
|
|
runs-on: ubuntu-latest
|
|
# Skip draft PRs.
|
|
if: github.event.pull_request.draft == false
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
fetch-depth: 0
|
|
# We don't actually need the contents of the files, just their names.
|
|
filter: blob:none
|
|
|
|
- name: Get changed files
|
|
id: changed_files
|
|
run: |
|
|
HEAD_SHA=${{ github.event.pull_request.head.sha }}
|
|
BASE_SHA=${{ github.event.pull_request.base.sha }}
|
|
MERGE_BASE=$(git merge-base "$BASE_SHA" "$HEAD_SHA")
|
|
CHANGED_FILES=$(git diff --name-only "$MERGE_BASE" "$HEAD_SHA" | tr '\n' ' ')
|
|
echo "files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Get PR approvers
|
|
id: approvers
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
PR_AUTHOR=${{ github.event.pull_request.user.login }}
|
|
|
|
# Get all approved reviews.
|
|
REVIEWERS=$(gh pr view "$PR_NUMBER" --json reviews --jq '[.reviews[] | select(.state == "APPROVED") | .author.login] | unique | join(" ")')
|
|
|
|
# Combine PR author (self-approves) with reviewers.
|
|
ALL_APPROVERS="$PR_AUTHOR $REVIEWERS"
|
|
echo "approvers=$ALL_APPROVERS" >> "$GITHUB_OUTPUT"
|
|
echo "has_reviewers=$( [[ -n "$REVIEWERS" ]] && echo true || echo false )" >> "$GITHUB_OUTPUT"
|
|
echo "PR author: $PR_AUTHOR"
|
|
echo "Reviewers who approved: $REVIEWERS"
|
|
echo "All approvers: $ALL_APPROVERS"
|
|
|
|
- name: Check OWNERS approval
|
|
env:
|
|
CHANGED_FILES: ${{ steps.changed_files.outputs.files }}
|
|
APPROVERS: ${{ steps.approvers.outputs.approvers }}
|
|
run: |
|
|
set -e
|
|
|
|
# Convert approvers to an array.
|
|
read -ra APPROVER_ARRAY <<< "$APPROVERS"
|
|
|
|
# Function to get owners for a file by walking up the directory tree.
|
|
get_owners_for_file() {
|
|
local file="$1"
|
|
local dir
|
|
dir=$(dirname "$file")
|
|
local owners=()
|
|
|
|
# Walk up the directory tree.
|
|
while [[ "$dir" != "." && "$dir" != "/" ]]; do
|
|
if [[ -f "$dir/OWNERS" ]]; then
|
|
# Read owners file, skip comments and empty lines.
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
# Skip comments and empty lines.
|
|
line=$(echo "$line" | sed 's/#.*//' | xargs)
|
|
if [[ -n "$line" ]]; then
|
|
owners+=("$line")
|
|
fi
|
|
done < "$dir/OWNERS"
|
|
fi
|
|
dir=$(dirname "$dir")
|
|
done
|
|
|
|
# Check root directory.
|
|
if [[ -f "OWNERS" ]]; then
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
line=$(echo "$line" | sed 's/#.*//' | xargs)
|
|
if [[ -n "$line" ]]; then
|
|
owners+=("$line")
|
|
fi
|
|
done < "OWNERS"
|
|
fi
|
|
|
|
# Return unique owners.
|
|
printf '%s\n' "${owners[@]}" | sort -u | tr '\n' ' ' | sed 's/ $//'
|
|
}
|
|
|
|
# Function to check if any approver is in the owners list.
|
|
has_owner_approval() {
|
|
local owners="$1"
|
|
for approver in "${APPROVER_ARRAY[@]}"; do
|
|
if echo "$owners" | grep -qw "$approver"; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
MISSING_APPROVAL=()
|
|
FILES_CHECKED=0
|
|
|
|
for file in $CHANGED_FILES; do
|
|
# Skip if file doesn't exist (deleted files).
|
|
FILES_CHECKED=$((FILES_CHECKED + 1))
|
|
|
|
OWNERS=$(get_owners_for_file "$file")
|
|
|
|
if [[ -z "$OWNERS" ]]; then
|
|
# No OWNERS files in ancestry - automatically passes.
|
|
echo "✓ $file (no OWNERS requirement)"
|
|
continue
|
|
fi
|
|
|
|
if has_owner_approval "$OWNERS"; then
|
|
echo "✓ $file"
|
|
else
|
|
echo "✗ $file (owners: $OWNERS)"
|
|
MISSING_APPROVAL+=("$file")
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo "Files checked: $FILES_CHECKED"
|
|
echo "Approvers: ${APPROVER_ARRAY[*]}"
|
|
|
|
# Save missing files for comment step (must happen before exit).
|
|
echo "MISSING_FILES<<EOF" >> "$GITHUB_ENV"
|
|
for file in "${MISSING_APPROVAL[@]}"; do
|
|
OWNERS=$(get_owners_for_file "$file")
|
|
echo "- \`$file\` (needs approval from: $OWNERS)" >> "$GITHUB_ENV"
|
|
done
|
|
echo "EOF" >> "$GITHUB_ENV"
|
|
|
|
if [[ ${#MISSING_APPROVAL[@]} -gt 0 ]]; then
|
|
echo ""
|
|
echo "::error::The following files are missing OWNERS approval:"
|
|
for file in "${MISSING_APPROVAL[@]}"; do
|
|
OWNERS=$(get_owners_for_file "$file")
|
|
echo "::error:: - $file (needs approval from: $OWNERS)"
|
|
done
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "All files have required OWNERS approval."
|
|
|
|
- name: Comment on PR about missing approvals
|
|
if: failure() && github.event_name == 'pull_request_review' && steps.approvers.outputs.has_reviewers == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
gh pr comment "$PR_NUMBER" --body "The following files still need approval from their OWNERS:\n\n$MISSING_FILES"
|