diff --git a/.github/workflows/pr-metadata-check.yml b/.github/workflows/pr-metadata-check.yml index d8cf91579d..8f6b915c5a 100644 --- a/.github/workflows/pr-metadata-check.yml +++ b/.github/workflows/pr-metadata-check.yml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -name: "CI: Enforce assignee/label/milestone on PRs" +name: "CI: Enforce PR metadata and cuda_bindings policy" on: pull_request_target: @@ -19,18 +19,31 @@ on: jobs: check-metadata: - name: PR has assignee, labels, and milestone + name: PR has required metadata and valid cuda_bindings author if: github.repository_owner == 'NVIDIA' runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read steps: - - name: Check for assignee, labels, and milestone + - name: Check PR metadata and cuda_bindings policy env: + # PR metadata inputs ASSIGNEES: ${{ toJson(github.event.pull_request.assignees) }} + AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association || 'NONE' }} LABELS: ${{ toJson(github.event.pull_request.labels) }} MILESTONE: ${{ github.event.pull_request.milestone && github.event.pull_request.milestone.title || '' }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + PR_NUMBER: ${{ github.event.pull_request.number }} PR_URL: ${{ github.event.pull_request.html_url }} + + # Gating booleans IS_BOT: ${{ github.actor == 'dependabot[bot]' || github.actor == 'pre-commit-ci[bot]' || github.actor == 'copy-pr-bot[bot]' }} IS_DRAFT: ${{ github.event.pull_request.draft }} + + # API request context/auth + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} run: | if [ "$IS_BOT" = "true" ] || [ "$IS_DRAFT" = "true" ]; then echo "Skipping check for bot or draft PR." @@ -103,10 +116,35 @@ jobs: ERRORS="${ERRORS}- **Blocked label detected**: label \`$label\` prevents merging. Remove it when the PR is ready.\n" done <<<"$BLOCKED_LABELS" + # Only NVIDIA organization members may change code under cuda_bindings. + if [ "$AUTHOR_ASSOCIATION" != "MEMBER" ] && [ "$AUTHOR_ASSOCIATION" != "OWNER" ]; then + if ! TOUCHES_CUDA_BINDINGS=$( + gh api \ + --paginate \ + --slurp \ + --jq ' + flatten + | any( + .[]; + (.filename | startswith("cuda_bindings/")) + or ((.previous_filename // "") | startswith("cuda_bindings/")) + ) + ' \ + "repos/$REPO/pulls/$PR_NUMBER/files" + ); then + echo "::error::Failed to inspect the PR file list." + exit 1 + fi + + if [ "$TOUCHES_CUDA_BINDINGS" = "true" ]; then + ERRORS="${ERRORS}- **cuda_bindings policy**: See \`cuda_bindings/LICENSE\`. Only NVIDIA organization members may modify files under \`cuda_bindings/\` (PR author \`$PR_AUTHOR\` has association \`$AUTHOR_ASSOCIATION\`).\n" + fi + fi + if [ -n "$ERRORS" ]; then - echo "::error::This PR is missing required metadata. See the job summary for details." + echo "::error::This PR failed the required metadata/policy checks. See the job summary for details." { - echo "## PR Metadata Check Failed" + echo "## PR Requirements Check Failed" echo "" printf '%b' "$ERRORS" echo "" @@ -118,9 +156,10 @@ jobs: ASSIGNEE_LIST=$(echo "$ASSIGNEES" | jq -r '.[].login' | paste -sd ', ' -) LABEL_LIST=$(echo "$LABELS" | jq -r '.[].name' | paste -sd ', ' -) { - echo "## PR Metadata Check Passed" + echo "## PR Requirements Check Passed" echo "" echo "- **Assignees**: $ASSIGNEE_LIST" echo "- **Labels**: $LABEL_LIST" echo "- **Milestone**: $MILESTONE" + echo "- **Author association**: $AUTHOR_ASSOCIATION" } >> "$GITHUB_STEP_SUMMARY"