Skip to content

Commit 1f04c74

Browse files
committed
fix: use GitHub App token when authed with GitHub App
Currently we are still trying to use the GH_TOKEN env var when making api/graphql calls even after authenticating with a GitHub App Installation. This change fixes that. Also fixed a few other things while in here: - [x] split authentication to auth.py file (like other actions) - [x] fix arguments to the count_comments_per_user function - [x] add `maintenance` to pull request template Signed-off-by: jmeridth <jmeridth@gmail.com>
1 parent 0b5e8b4 commit 1f04c74

File tree

6 files changed

+152
-82
lines changed

6 files changed

+152
-82
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ examples: "feat: add new logger" or "fix: remove unused imports"
2121

2222
### Reviewer
2323

24-
- [ ] Label as either `fix`, `documentation`, `enhancement`, `infrastructure`, or `breaking`
24+
- [ ] Label as either `fix`, `documentation`, `enhancement`, `infrastructure`, `maintenance`, or `breaking`

auth.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
This is the module that contains functions related to authenticating
3+
to GitHub.
4+
"""
5+
6+
import github3
7+
import requests
8+
9+
10+
def auth_to_github(
11+
gh_app_id: str,
12+
gh_app_installation_id: int,
13+
gh_app_private_key_bytes: bytes,
14+
token: str,
15+
ghe: str,
16+
) -> github3.GitHub:
17+
"""
18+
Connect to GitHub.com or GitHub Enterprise, depending on env variables.
19+
20+
Returns:
21+
github3.GitHub: A github api connection.
22+
"""
23+
24+
if gh_app_id and gh_app_private_key_bytes and gh_app_installation_id:
25+
gh = github3.github.GitHub()
26+
gh.login_as_app_installation(
27+
gh_app_private_key_bytes, gh_app_id, gh_app_installation_id
28+
)
29+
github_connection = gh
30+
elif ghe and token:
31+
github_connection = github3.github.GitHubEnterprise(ghe, token=token)
32+
elif token:
33+
github_connection = github3.login(token=token)
34+
else:
35+
raise ValueError(
36+
"GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set"
37+
)
38+
39+
return github_connection # type: ignore
40+
41+
42+
def get_github_app_installation_token(
43+
gh_app_id: str, gh_app_private_key_bytes: bytes, gh_app_installation_id: str
44+
) -> str | None:
45+
"""
46+
Get a GitHub App Installation token.
47+
Args:
48+
gh_app_id (str): the GitHub App ID
49+
gh_app_private_key_bytes (bytes): the GitHub App Private Key
50+
gh_app_installation_id (str): the GitHub App Installation ID
51+
Returns:
52+
str: the GitHub App token
53+
"""
54+
jwt_headers = github3.apps.create_jwt_headers(gh_app_private_key_bytes, gh_app_id)
55+
url = f"https://api.github.com/app/installations/{gh_app_installation_id}/access_tokens"
56+
57+
try:
58+
response = requests.post(url, headers=jwt_headers, json=None, timeout=5)
59+
response.raise_for_status()
60+
except requests.exceptions.RequestException as e:
61+
print(f"Request failed: {e}")
62+
return None
63+
return response.json().get("token")

issue_metrics.py

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
search_issues(search_query: str, github_connection: github3.GitHub)
1212
-> github3.structs.SearchIterator:
1313
Searches for issues in a GitHub repository that match the given search query.
14-
auth_to_github() -> github3.GitHub: Connect to GitHub API with token authentication.
1514
get_per_issue_metrics(issues: Union[List[dict], List[github3.issues.Issue]],
1615
discussions: bool = False), labels: Union[List[str], None] = None,
1716
ignore_users: List[str] = [] -> tuple[List, int, int]:
@@ -25,6 +24,7 @@
2524
from typing import List, Union
2625

2726
import github3
27+
from auth import auth_to_github, get_github_app_installation_token
2828
from classes import IssueWithMetrics
2929
from config import EnvVars, get_env_vars
3030
from discussions import get_discussions
@@ -92,38 +92,6 @@ def search_issues(
9292
return issues
9393

9494

95-
def auth_to_github(
96-
gh_app_id: str,
97-
gh_app_installation_id: int,
98-
gh_app_private_key_bytes: bytes,
99-
token: str,
100-
ghe: str,
101-
) -> github3.GitHub:
102-
"""
103-
Connect to GitHub.com or GitHub Enterprise, depending on env variables.
104-
105-
Returns:
106-
github3.GitHub: A github api connection.
107-
"""
108-
109-
if gh_app_id and gh_app_private_key_bytes and gh_app_installation_id:
110-
gh = github3.github.GitHub()
111-
gh.login_as_app_installation(
112-
gh_app_private_key_bytes, gh_app_id, gh_app_installation_id
113-
)
114-
github_connection = gh
115-
elif ghe and token:
116-
github_connection = github3.github.GitHubEnterprise(ghe, token=token)
117-
elif token:
118-
github_connection = github3.login(token=token)
119-
else:
120-
raise ValueError(
121-
"GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set"
122-
)
123-
124-
return github_connection # type: ignore
125-
126-
12795
def get_per_issue_metrics(
12896
issues: Union[List[dict], List[github3.search.IssueSearchResult]], # type: ignore
12997
env_vars: EnvVars,
@@ -175,9 +143,9 @@ def get_per_issue_metrics(
175143
issue_with_metrics.mentor_activity = count_comments_per_user(
176144
None,
177145
issue,
178-
ignore_users,
179146
None,
180147
None,
148+
ignore_users,
181149
max_comments_to_eval,
182150
heavily_involved,
183151
)
@@ -289,11 +257,20 @@ def main():
289257
token = env_vars.gh_token
290258
ignore_users = env_vars.ignore_users
291259

260+
gh_app_id = env_vars.gh_app_id
261+
gh_app_installation_id = env_vars.gh_app_installation_id
262+
gh_app_private_key_bytes = env_vars.gh_app_private_key_bytes
263+
264+
if not token and gh_app_id and gh_app_installation_id and gh_app_private_key_bytes:
265+
token = get_github_app_installation_token(
266+
gh_app_id, gh_app_private_key_bytes, gh_app_installation_id
267+
)
268+
292269
# Auth to GitHub.com
293270
github_connection = auth_to_github(
294-
env_vars.gh_app_id,
295-
env_vars.gh_app_installation_id,
296-
env_vars.gh_app_private_key_bytes,
271+
gh_app_id,
272+
gh_app_installation_id,
273+
gh_app_private_key_bytes,
297274
token,
298275
env_vars.ghe,
299276
)

most_active_mentors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ def count_comments_per_user(
5656
Args:
5757
issue (Union[github3.issues.Issue, None]): A GitHub issue.
5858
pull_request (Union[github3.pulls.PullRequest, None]): A GitHub pull
59-
request. ignore_users (List[str]): A list of GitHub usernames to
60-
ignore.
59+
request.
60+
ignore_users (List[str]): A list of GitHub usernames to ignore.
6161
max_comments_to_eval: Maximum number of comments per item to look at.
6262
heavily_involved: Maximum number of comments to count for one
6363
user per issue.

test_auth.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""A module containing unit tests for the auth module.
2+
3+
This module contains unit tests for the functions in the auth module
4+
that authenticate to github.
5+
6+
Classes:
7+
TestAuthToGithub: A class to test the auth_to_github function.
8+
9+
"""
10+
11+
import unittest
12+
from unittest.mock import MagicMock, patch
13+
14+
import github3
15+
from auth import auth_to_github, get_github_app_installation_token
16+
17+
18+
class TestAuthToGithub(unittest.TestCase):
19+
"""Test the auth_to_github function."""
20+
21+
@patch("github3.github.GitHub.login_as_app_installation")
22+
def test_auth_to_github_with_github_app(self, mock_login):
23+
"""
24+
Test the auth_to_github function when GitHub app
25+
parameters provided.
26+
"""
27+
mock_login.return_value = MagicMock()
28+
result = auth_to_github(12345, 678910, b"hello", "", "")
29+
30+
self.assertIsInstance(result, github3.github.GitHub)
31+
32+
def test_auth_to_github_with_token(self):
33+
"""
34+
Test the auth_to_github function when the token is provided.
35+
"""
36+
result = auth_to_github(None, None, b"", "token", "")
37+
38+
self.assertIsInstance(result, github3.github.GitHub)
39+
40+
def test_auth_to_github_without_authentication_information(self):
41+
"""
42+
Test the auth_to_github function when authentication information is not provided.
43+
Expect a ValueError to be raised.
44+
"""
45+
with self.assertRaises(ValueError):
46+
auth_to_github(None, None, b"", "", "")
47+
48+
def test_auth_to_github_with_ghe(self):
49+
"""
50+
Test the auth_to_github function when the GitHub Enterprise URL is provided.
51+
"""
52+
result = auth_to_github(None, None, b"", "token", "https://github.example.com")
53+
54+
self.assertIsInstance(result, github3.github.GitHubEnterprise)
55+
56+
@patch("github3.apps.create_jwt_headers", MagicMock(return_value="gh_token"))
57+
@patch("requests.post")
58+
def test_get_github_app_installation_token(self, mock_post):
59+
"""
60+
Test the get_github_app_installation_token function.
61+
"""
62+
dummy_token = "dummytoken"
63+
mock_response = MagicMock()
64+
mock_response.raise_for_status.return_value = None
65+
mock_response.json.return_value = {"token": dummy_token}
66+
mock_post.return_value = mock_response
67+
68+
result = get_github_app_installation_token(
69+
b"gh_private_token", "gh_app_id", "gh_installation_id"
70+
)
71+
72+
self.assertEqual(result, dummy_token)

test_issue_metrics.py

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
77
Classes:
88
TestSearchIssues: A class to test the search_issues function.
9-
TestAuthToGithub: A class to test the auth_to_github function.
109
TestGetPerIssueMetrics: A class to test the get_per_issue_metrics function.
1110
TestGetEnvVars: A class to test the get_env_vars function.
1211
TestMain: A class to test the main function.
@@ -18,11 +17,9 @@
1817
from datetime import datetime, timedelta
1918
from unittest.mock import MagicMock, patch
2019

21-
import github3
2220
import issue_metrics
2321
from issue_metrics import (
2422
IssueWithMetrics,
25-
auth_to_github,
2623
get_env_vars,
2724
get_owner_and_repository,
2825
get_per_issue_metrics,
@@ -94,45 +91,6 @@ def test_get_owner_and_repository_without_either_in_query(self):
9491
self.assertIsNone(result.get("repository"))
9592

9693

97-
class TestAuthToGithub(unittest.TestCase):
98-
"""Test the auth_to_github function."""
99-
100-
@patch("github3.github.GitHub.login_as_app_installation")
101-
def test_auth_to_github_with_github_app(self, mock_login):
102-
"""
103-
Test the auth_to_github function when GitHub app
104-
parameters provided.
105-
"""
106-
mock_login.return_value = MagicMock()
107-
result = auth_to_github(12345, 678910, b"hello", "", "")
108-
109-
self.assertIsInstance(result, github3.github.GitHub)
110-
111-
def test_auth_to_github_with_token(self):
112-
"""
113-
Test the auth_to_github function when the token is provided.
114-
"""
115-
result = auth_to_github(None, None, b"", "token", "")
116-
117-
self.assertIsInstance(result, github3.github.GitHub)
118-
119-
def test_auth_to_github_without_authentication_information(self):
120-
"""
121-
Test the auth_to_github function when authentication information is not provided.
122-
Expect a ValueError to be raised.
123-
"""
124-
with self.assertRaises(ValueError):
125-
auth_to_github(None, None, b"", "", "")
126-
127-
def test_auth_to_github_with_ghe(self):
128-
"""
129-
Test the auth_to_github function when the GitHub Enterprise URL is provided.
130-
"""
131-
result = auth_to_github(None, None, b"", "token", "https://github.example.com")
132-
133-
self.assertIsInstance(result, github3.github.GitHubEnterprise)
134-
135-
13694
class TestGetEnvVars(unittest.TestCase):
13795
"""Test suite for the get_env_vars function."""
13896

0 commit comments

Comments
 (0)