Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 98 additions & 54 deletions .github/scripts/label_community_user.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,117 @@
import os
import time
import sys

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}",
}


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:
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})."
)
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_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:
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")
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")
Expand All @@ -87,22 +121,32 @@ 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.")
return
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.")
sys.exit(1)

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(
f"User '{pr_author}' is a community user. Adding label '{community_label}'."
)
add_label_to_pr(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.")
print("Intentionally failing the pipeline:")
sys.exit(100)


if __name__ == "__main__":
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/label_community_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ 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 }}
REPO_NAME: ${{ github.event.repository.name }}
COMMUNITY_LABEL: "Community want to contribute"
run: python .github/scripts/label_community_user.py
Loading