44name : " CI: Check PR author organization for restricted paths"
55
66on :
7+ # TODO BEFORE MERGING: change to pull_request_target
78 pull_request :
89 types :
910 - opened
@@ -17,32 +18,37 @@ jobs:
1718 if : github.repository_owner == 'NVIDIA'
1819 runs-on : ubuntu-latest
1920 permissions :
21+ issues : write
2022 pull-requests : read
2123 steps :
2224 - name : Check PR author organization for restricted paths
2325 env :
2426 # PR metadata inputs
2527 AUTHOR_ASSOCIATION : ${{ github.event.pull_request.author_association || 'NONE' }}
28+ LABELS : ${{ toJson(github.event.pull_request.labels) }}
2629 PR_AUTHOR : ${{ github.event.pull_request.user.login }}
2730 PR_NUMBER : ${{ github.event.pull_request.number }}
2831 PR_URL : ${{ github.event.pull_request.html_url }}
2932
3033 # API request context/auth
3134 GH_TOKEN : ${{ github.token }}
35+ ORG_NAME : NVIDIA
36+ ORG_REVIEW_LABEL : Check-PR-Author-ORG
3237 REPO : ${{ github.repository }}
3338 run : |
3439 if ! MATCHING_RESTRICTED_PATHS=$(
3540 gh api \
3641 --paginate \
3742 --jq '
3843 .[]
39- | select(
40- (.filename | startswith("cuda_bindings/"))
41- or ((.previous_filename // "") | startswith("cuda_bindings/"))
44+ | if (.filename | startswith("cuda_bindings/"))
4245 or (.filename | startswith("cuda_python/"))
46+ then .filename
47+ elif ((.previous_filename // "") | startswith("cuda_bindings/"))
4348 or ((.previous_filename // "") | startswith("cuda_python/"))
44- )
45- | .filename
49+ then "\(.previous_filename) -> \(.filename)"
50+ else empty
51+ end
4652 ' \
4753 "repos/$REPO/pulls/$PR_NUMBER/files"
4854 ); then
@@ -64,47 +70,161 @@ jobs:
6470 TOUCHES_RESTRICTED_PATHS=true
6571 fi
6672
73+ HAS_ORG_REVIEW_LABEL=false
74+ while IFS= read -r label; do
75+ [ -n "$label" ] || continue
76+ if [ "$label" = "$ORG_REVIEW_LABEL" ]; then
77+ HAS_ORG_REVIEW_LABEL=true
78+ break
79+ fi
80+ done < <(echo "$LABELS" | jq -r '.[].name')
81+
82+ REVIEW_LABEL_ACTION="none"
83+
6784 write_matching_restricted_paths() {
6885 echo "- **Matched restricted paths**:"
6986 echo '```text'
7087 printf '%s\n' "$MATCHING_RESTRICTED_PATHS"
7188 echo '```'
7289 }
7390
74- IS_ALLOWED=false
75- case "$AUTHOR_ASSOCIATION" in
76- MEMBER|OWNER)
77- IS_ALLOWED=true
78- ;;
79- esac
91+ write_review_label_action() {
92+ [ "$REVIEW_LABEL_ACTION" != "none" ] || return 0
93+ echo "- **Review label action**: $REVIEW_LABEL_ACTION \`$ORG_REVIEW_LABEL\`"
94+ }
95+
96+ ensure_org_review_label_present() {
97+ if [ "$HAS_ORG_REVIEW_LABEL" = "false" ]; then
98+ if ! gh issue edit "$PR_NUMBER" --repo "$REPO" --add-label "$ORG_REVIEW_LABEL" >/dev/null; then
99+ echo "::error::Failed to add label $ORG_REVIEW_LABEL."
100+ exit 1
101+ fi
102+ HAS_ORG_REVIEW_LABEL=true
103+ REVIEW_LABEL_ACTION="added"
104+ fi
105+ }
106+
107+ ensure_org_review_label_absent() {
108+ if [ "$HAS_ORG_REVIEW_LABEL" = "true" ]; then
109+ if ! gh issue edit "$PR_NUMBER" --repo "$REPO" --remove-label "$ORG_REVIEW_LABEL" >/dev/null; then
110+ echo "::error::Failed to remove label $ORG_REVIEW_LABEL."
111+ exit 1
112+ fi
113+ HAS_ORG_REVIEW_LABEL=false
114+ REVIEW_LABEL_ACTION="removed"
115+ fi
116+ }
117+
118+ if [ "$TOUCHES_RESTRICTED_PATHS" = "false" ]; then
119+ ensure_org_review_label_absent
120+ {
121+ echo "## PR Author Organization Check Passed"
122+ echo ""
123+ echo "- **Author**: $PR_AUTHOR"
124+ echo "- **Author association**: $AUTHOR_ASSOCIATION"
125+ echo "- **Touches restricted paths**: false"
126+ echo "- **Restricted paths**: \`cuda_bindings/\`, \`cuda_python/\`"
127+ write_review_label_action
128+ } >> "$GITHUB_STEP_SUMMARY"
129+ exit 0
130+ fi
80131
81- if [ "$TOUCHES_RESTRICTED_PATHS" = "true" ] && [ "$IS_ALLOWED" = "false" ]; then
82- echo "::error::This PR failed the author organization check. See the job summary for details."
132+ # Use curl here because the 204/404/302 HTTP status is the signal we
133+ # need. gh api treats 404 as a command failure, but 404 is an expected
134+ # "not a member" outcome for this check.
135+ if ! MEMBERSHIP_STATUS=$(
136+ curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
137+ -H "Authorization: Bearer $GH_TOKEN" \
138+ -H "Accept: application/vnd.github+json" \
139+ -H "X-GitHub-Api-Version: 2022-11-28" \
140+ "https://api.github.com/orgs/$ORG_NAME/members/$PR_AUTHOR"
141+ ); then
142+ echo "::error::Failed to query organization membership."
83143 {
84144 echo "## PR Author Organization Check Failed"
85145 echo ""
146+ echo "- **Error**: Failed to query organization membership."
86147 echo "- **Author**: $PR_AUTHOR"
87148 echo "- **Author association**: $AUTHOR_ASSOCIATION"
149+ echo "- **Organization membership query**: failed"
88150 echo "- **Restricted paths**: \`cuda_bindings/\`, \`cuda_python/\`"
89151 echo ""
90152 write_matching_restricted_paths
91153 echo ""
92- echo "- **Policy**: See \`cuda_bindings/LICENSE\` and \`cuda_python/LICENSE\`. Only NVIDIA organization members may modify files under \`cuda_bindings/\` or \`cuda_python/\`."
93- echo ""
94154 echo "Please update the PR at: $PR_URL"
95155 } >> "$GITHUB_STEP_SUMMARY"
96156 exit 1
97157 fi
98158
99- {
100- echo "## PR Author Organization Check Passed"
101- echo ""
102- echo "- **Author**: $PR_AUTHOR"
103- echo "- **Author association**: $AUTHOR_ASSOCIATION"
104- echo "- **Touches restricted paths**: $TOUCHES_RESTRICTED_PATHS"
105- echo "- **Restricted paths**: \`cuda_bindings/\`, \`cuda_python/\`"
106- if [ "$TOUCHES_RESTRICTED_PATHS" = "true" ]; then
107- echo ""
108- write_matching_restricted_paths
109- fi
110- } >> "$GITHUB_STEP_SUMMARY"
159+ case "$MEMBERSHIP_STATUS" in
160+ 204)
161+ ensure_org_review_label_absent
162+ {
163+ echo "## PR Author Organization Check Passed"
164+ echo ""
165+ echo "- **Author**: $PR_AUTHOR"
166+ echo "- **Author association**: $AUTHOR_ASSOCIATION"
167+ echo "- **Organization membership query**: \`204\` (member)"
168+ echo "- **Restricted paths**: \`cuda_bindings/\`, \`cuda_python/\`"
169+ echo ""
170+ write_matching_restricted_paths
171+ write_review_label_action
172+ } >> "$GITHUB_STEP_SUMMARY"
173+ ;;
174+ 302)
175+ ensure_org_review_label_present
176+ echo "::warning::Could not determine conclusively whether $PR_AUTHOR is a member of $ORG_NAME. Added label $ORG_REVIEW_LABEL for manual review."
177+ {
178+ echo "## PR Author Organization Check Inconclusive"
179+ echo ""
180+ echo "- **Author**: $PR_AUTHOR"
181+ echo "- **Author association**: $AUTHOR_ASSOCIATION"
182+ echo "- **Organization membership query**: \`302\` (inconclusive)"
183+ echo "- **Restricted paths**: \`cuda_bindings/\`, \`cuda_python/\`"
184+ echo ""
185+ write_matching_restricted_paths
186+ write_review_label_action
187+ echo ""
188+ echo "- **Next step**: Review whether the PR author should be allowed to modify these restricted paths."
189+ echo ""
190+ echo "Please update the PR at: $PR_URL"
191+ } >> "$GITHUB_STEP_SUMMARY"
192+ ;;
193+ 404)
194+ ensure_org_review_label_absent
195+ echo "::error::This PR failed the author organization check. See the job summary for details."
196+ {
197+ echo "## PR Author Organization Check Failed"
198+ echo ""
199+ echo "- **Author**: $PR_AUTHOR"
200+ echo "- **Author association**: $AUTHOR_ASSOCIATION"
201+ echo "- **Organization membership query**: \`404\` (not a member)"
202+ echo "- **Restricted paths**: \`cuda_bindings/\`, \`cuda_python/\`"
203+ echo ""
204+ write_matching_restricted_paths
205+ write_review_label_action
206+ echo ""
207+ echo "- **Policy**: See \`cuda_bindings/LICENSE\` and \`cuda_python/LICENSE\`. Only NVIDIA organization members may modify files under \`cuda_bindings/\` or \`cuda_python/\`."
208+ echo ""
209+ echo "Please update the PR at: $PR_URL"
210+ } >> "$GITHUB_STEP_SUMMARY"
211+ exit 1
212+ ;;
213+ *)
214+ echo "::error::Unexpected response from organization membership query: HTTP $MEMBERSHIP_STATUS."
215+ {
216+ echo "## PR Author Organization Check Failed"
217+ echo ""
218+ echo "- **Error**: Unexpected response from organization membership query."
219+ echo "- **Author**: $PR_AUTHOR"
220+ echo "- **Author association**: $AUTHOR_ASSOCIATION"
221+ echo "- **Organization membership query**: \`$MEMBERSHIP_STATUS\`"
222+ echo "- **Restricted paths**: \`cuda_bindings/\`, \`cuda_python/\`"
223+ echo ""
224+ write_matching_restricted_paths
225+ echo ""
226+ echo "Please update the PR at: $PR_URL"
227+ } >> "$GITHUB_STEP_SUMMARY"
228+ exit 1
229+ ;;
230+ esac
0 commit comments