From 6037464ba9f6be70a9fe8e7e75c6188c5e387c48 Mon Sep 17 00:00:00 2001 From: "Po-Wei Wang (Vincent)" Date: Mon, 2 Jun 2025 21:17:01 -0700 Subject: [PATCH 1/6] Fix the labeling logic Signed-off-by: Po-Wei Wang (Vincent) --- .github/scripts/label_community_user.py | 17 ++++++++++++++--- .github/workflows/label_community_pr.yml | 3 +++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/scripts/label_community_user.py b/.github/scripts/label_community_user.py index d95f1bf372d7..000679c09d8a 100644 --- a/.github/scripts/label_community_user.py +++ b/.github/scripts/label_community_user.py @@ -59,12 +59,21 @@ def get_nvidia_members() -> list[str]: return members -def add_label_to_pr(repo_name: str, pr_number: str, label: str): +def add_label_to_pr(repo_owner: str, repo_name: str, pr_number: str, + label: str): """Adds a label to a pull request.""" - url = f"{GITHUB_API_URL}/repos/NVIDIA/{repo_name}/issues/{pr_number}/labels" + url = f"{GITHUB_API_URL}/repos/{repo_owner}/{repo_name}/issues/{pr_number}/labels" payload = {"labels": [label]} + print(f"Attempting to add label. URL: {url}, Payload: {payload}") try: response = requests.post(url, headers=HEADERS, json=payload) + print(f"API Response Status Code: {response.status_code}") + try: + response_json = response.json() + print(f"API Response JSON: {response_json}") + except requests.exceptions.JSONDecodeError: + print(f"API Response Text (not JSON): {response.text}") + response.raise_for_status() print(f"Successfully added label '{label}' to PR #{pr_number}.") except requests.exceptions.RequestException as e: @@ -78,6 +87,8 @@ def main(): assert pr_author, "PR_AUTHOR environment variable not set" pr_number = os.environ.get("PR_NUMBER") assert pr_number, "PR_NUMBER environment variable not set" + repo_owner = os.environ.get("REPO_OWNER") + assert repo_owner, "REPO_OWNER environment variable not set" repo_name = os.environ.get("REPO_NAME") assert repo_name, "REPO_NAME environment variable not set" community_label = os.environ.get("COMMUNITY_LABEL") @@ -99,7 +110,7 @@ def main(): print( f"User '{pr_author}' is a community user. Adding label '{community_label}'." ) - add_label_to_pr(repo_name, pr_number, community_label) + add_label_to_pr(repo_owner, repo_name, pr_number, community_label) else: print( f"User '{pr_author}' is an NVIDIA member. No label will be added.") diff --git a/.github/workflows/label_community_pr.yml b/.github/workflows/label_community_pr.yml index 6ae9322c4bf4..dd2fa3c30390 100644 --- a/.github/workflows/label_community_pr.yml +++ b/.github/workflows/label_community_pr.yml @@ -3,6 +3,8 @@ name: Label Community PR on: pull_request: types: [opened] +permissions: + pull-requests: write jobs: label_pr: @@ -24,6 +26,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_AUTHOR: ${{ github.event.pull_request.user.login }} PR_NUMBER: ${{ github.event.pull_request.number }} + REPO_OWNER: ${{ github.event.repository.owner.login }} REPO_NAME: ${{ github.event.repository.name }} COMMUNITY_LABEL: "Community want to contribute" run: python .github/scripts/label_community_user.py From b82c0072241cb2d25036b4e9a26db5b9ddfe7b48 Mon Sep 17 00:00:00 2001 From: "Po-Wei Wang (Vincent)" Date: Tue, 3 Jun 2025 16:45:38 -0700 Subject: [PATCH 2/6] update script to use better api endpoint Signed-off-by: Po-Wei Wang (Vincent) --- .github/scripts/label_community_user.py | 103 +++++++++++++----------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/.github/scripts/label_community_user.py b/.github/scripts/label_community_user.py index 000679c09d8a..c3952b27d6dd 100644 --- a/.github/scripts/label_community_user.py +++ b/.github/scripts/label_community_user.py @@ -1,5 +1,4 @@ import os -import time import requests @@ -14,49 +13,55 @@ } -def get_nvidia_members() -> list[str]: - """Fetches all NVIDIA organization members.""" - members = [] - page = 1 - per_page = 100 - - while True: - url = f"{GITHUB_API_URL}/orgs/NVIDIA/members?per_page={per_page}&page={page}" - try: - time.sleep(0.5) - response = requests.get(url, headers=HEADERS) - - if response.status_code == 404: - raise RuntimeError( - f"Organization 'NVIDIA' not found (404). Cannot fetch members." - ) - elif response.status_code == 403: +def check_user_membership(org: str, username: str) -> bool: + """Checks if a user is a member of an organization using a direct API call.""" + url = f"{GITHUB_API_URL}/orgs/{org}/members/{username}" + try: + response = requests.get(url, + headers=HEADERS, + timeout=10, + allow_redirects=False) + + if response.status_code == 204 or response.status_code == 302: + print( + f"Membership check for '{username}' in '{org}': Positive (Status {response.status_code})." + ) + return True + elif response.status_code == 404: + print( + f"Membership check for '{username}' in '{org}': Negative (Status {response.status_code})." + ) + return False + elif response.status_code == 403: + error_message = "Details not parsable from JSON." + try: error_message = response.json().get( - "message", "") if response.content else "" - raise RuntimeError( - f"Forbidden (403) when fetching members for 'NVIDIA'. " - f"This may be due to insufficient token permissions or rate limits. Details: {error_message}. Cannot fetch members." - ) - + "message", "No specific message from API.") + except requests.exceptions.JSONDecodeError: + if response.text: + error_message = response.text + detail = ( + f"Forbidden (403) checking membership for '{username}' in '{org}'. " + f"Token permissions (e.g., 'read:org' scope) or org restrictions likely. API msg: {error_message}" + ) + print(detail) + raise RuntimeError(detail) + else: + print( + f"Unexpected status {response.status_code} checking membership for '{username}' in '{org}'. Response: {response.text[:200]}" + ) response.raise_for_status() - page_data = response.json() - - if not page_data: - break - - for member_data in page_data: - if isinstance(member_data, dict) and "login" in member_data: - members.append(member_data["login"].lower()) - - if len(page_data) < per_page: - break - page += 1 - except Exception as e: - print(f"Error fetching NVIDIA members: {e}") - return [] - - print(f"Successfully fetched {len(members)} members for 'NVIDIA'.") - return members + return False + except requests.exceptions.Timeout: + print( + f"Timeout checking membership for '{username}' in '{org}'. Assuming not a member." + ) + return False + except requests.exceptions.RequestException as e: + print( + f"RequestException checking membership for '{username}' in '{org}': {e}. Assuming not a member." + ) + return False def add_label_to_pr(repo_owner: str, repo_name: str, pr_number: str, @@ -98,13 +103,17 @@ def main(): f"Starting NVIDIA membership check for PR author '{pr_author}' on PR #{pr_number}." ) - nvidia_members = get_nvidia_members() - if not nvidia_members: - print("Could not retrieve NVIDIA members list. Exiting.") + try: + is_member = check_user_membership("NVIDIA", pr_author) + except RuntimeError as e: + print( + f"Critical error during NVIDIA membership check for '{pr_author}': {e}" + ) + print("Halting script due to inability to determine membership status.") return - is_member = pr_author.lower() in nvidia_members - print(f"User '{pr_author}' is a member of NVIDIA: {is_member}") + print( + f"User '{pr_author}' is determined to be an NVIDIA member: {is_member}") if not is_member: print( From afd5bdbc241e14159a5332c07a1c721ddd5cebd1 Mon Sep 17 00:00:00 2001 From: "Po-Wei Wang (Vincent)" Date: Mon, 30 Jun 2025 14:54:24 -0700 Subject: [PATCH 3/6] Update to use the service account PAT Signed-off-by: Po-Wei Wang (Vincent) --- .github/scripts/label_community_user.py | 6 +++--- .github/workflows/label_community_pr.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/label_community_user.py b/.github/scripts/label_community_user.py index c3952b27d6dd..de309bcee493 100644 --- a/.github/scripts/label_community_user.py +++ b/.github/scripts/label_community_user.py @@ -3,13 +3,13 @@ import requests GITHUB_API_URL = "https://api.github.com" -GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") -assert GITHUB_TOKEN, "GITHUB_TOKEN environment variable not set" +AUTO_LABEL_COMMUNITY_TOKEN = os.environ.get("AUTO_LABEL_COMMUNITY_TOKEN") +assert AUTO_LABEL_COMMUNITY_TOKEN, "AUTO_LABEL_COMMUNITY_TOKEN environment variable not set" HEADERS = { "Accept": "application/vnd.github.v3+json", "User-Agent": "PythonGitHubAction-Labeler/1.0", - "Authorization": f"token {GITHUB_TOKEN}", + "Authorization": f"token {AUTO_LABEL_COMMUNITY_TOKEN}", } diff --git a/.github/workflows/label_community_pr.yml b/.github/workflows/label_community_pr.yml index dd2fa3c30390..42177cc2a2db 100644 --- a/.github/workflows/label_community_pr.yml +++ b/.github/workflows/label_community_pr.yml @@ -23,7 +23,7 @@ jobs: - name: Run labeling script env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AUTO_LABEL_COMMUNITY_TOKEN: ${{ secrets.AUTO_LABEL_COMMUNITY_TOKEN }} PR_AUTHOR: ${{ github.event.pull_request.user.login }} PR_NUMBER: ${{ github.event.pull_request.number }} REPO_OWNER: ${{ github.event.repository.owner.login }} From 1056ed033c1e344c617a293d7978fcf72fe5156c Mon Sep 17 00:00:00 2001 From: "Po-Wei Wang (Vincent)" Date: Tue, 1 Jul 2025 14:08:36 -0700 Subject: [PATCH 4/6] Handle 302 request Signed-off-by: Po-Wei Wang (Vincent) --- .github/scripts/label_community_user.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/scripts/label_community_user.py b/.github/scripts/label_community_user.py index de309bcee493..5e0545689475 100644 --- a/.github/scripts/label_community_user.py +++ b/.github/scripts/label_community_user.py @@ -22,11 +22,19 @@ def check_user_membership(org: str, username: str) -> bool: timeout=10, allow_redirects=False) - if response.status_code == 204 or response.status_code == 302: + if response.status_code == 204: print( f"Membership check for '{username}' in '{org}': Positive (Status {response.status_code})." ) return True + elif response.status_code == 302: + detail = ( + f"Cannot determine membership for '{username}' in '{org}' (Status 302). " + f"The requester token does not have organization membership privileges. " + f"This usually means the token is not associated with an org member." + ) + print(detail) + raise RuntimeError(detail) elif response.status_code == 404: print( f"Membership check for '{username}' in '{org}': Negative (Status {response.status_code})." From d9f442fc551dff351ffd16e743987c33a63275a4 Mon Sep 17 00:00:00 2001 From: "Po-Wei Wang (Vincent)" Date: Tue, 1 Jul 2025 14:17:24 -0700 Subject: [PATCH 5/6] fail the pipeline if the script fails Signed-off-by: Po-Wei Wang (Vincent) --- .github/scripts/label_community_user.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/scripts/label_community_user.py b/.github/scripts/label_community_user.py index 5e0545689475..63a67c6c243c 100644 --- a/.github/scripts/label_community_user.py +++ b/.github/scripts/label_community_user.py @@ -1,4 +1,5 @@ import os +import sys import requests @@ -93,9 +94,18 @@ def add_label_to_pr(repo_owner: str, repo_name: str, pr_number: str, print(f"Error adding label '{label}' to PR #{pr_number}: {e}") if e.response is not None: print(f"Response content: {e.response.content}") + raise e def main(): + """ + Main function to check user membership and apply community labels. + + Exit codes: + 0 - Success (user membership determined, appropriate action taken) + 1 - Failed to determine user membership (API permission issues) + 2 - Failed to add community label (labeling API issues) + """ pr_author = os.environ.get("PR_AUTHOR") assert pr_author, "PR_AUTHOR environment variable not set" pr_number = os.environ.get("PR_NUMBER") @@ -118,7 +128,7 @@ def main(): f"Critical error during NVIDIA membership check for '{pr_author}': {e}" ) print("Halting script due to inability to determine membership status.") - return + sys.exit(1) print( f"User '{pr_author}' is determined to be an NVIDIA member: {is_member}") @@ -127,7 +137,11 @@ def main(): print( f"User '{pr_author}' is a community user. Adding label '{community_label}'." ) - add_label_to_pr(repo_owner, repo_name, pr_number, community_label) + try: + add_label_to_pr(repo_owner, repo_name, pr_number, community_label) + except requests.exceptions.RequestException as e: + print(f"Failed to add community label: {e}") + sys.exit(2) else: print( f"User '{pr_author}' is an NVIDIA member. No label will be added.") From ad7d8ed575efad7b72b942a6b72d60841ed4e0cb Mon Sep 17 00:00:00 2001 From: "Po-Wei Wang (Vincent)" Date: Tue, 1 Jul 2025 14:33:08 -0700 Subject: [PATCH 6/6] Remove permission section in workflow yml Signed-off-by: Po-Wei Wang (Vincent) --- .github/workflows/label_community_pr.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/label_community_pr.yml b/.github/workflows/label_community_pr.yml index 42177cc2a2db..91f6f2c85e07 100644 --- a/.github/workflows/label_community_pr.yml +++ b/.github/workflows/label_community_pr.yml @@ -3,8 +3,6 @@ name: Label Community PR on: pull_request: types: [opened] -permissions: - pull-requests: write jobs: label_pr: