Skip to content

Commit 29b62e3

Browse files
fear: support github enterprise api
1 parent c5775b2 commit 29b62e3

File tree

4 files changed

+147
-212
lines changed

4 files changed

+147
-212
lines changed

auth.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,31 @@ def auth_to_github(
2525
github3.GitHub: the GitHub connection object
2626
"""
2727
if gh_app_id and gh_app_private_key_bytes and gh_app_installation_id:
28-
gh = github3.github.GitHub()
29-
gh.login_as_app_installation(
30-
gh_app_private_key_bytes, gh_app_id, gh_app_installation_id
31-
)
28+
if ghe:
29+
gh = github3.github.GitHubEnterprise(url=ghe)
30+
else:
31+
gh = github3.github.GitHub()
32+
gh.login_as_app_installation(gh_app_private_key_bytes, gh_app_id, gh_app_installation_id)
3233
github_connection = gh
3334
elif ghe and token:
34-
github_connection = github3.github.GitHubEnterprise(ghe, token=token)
35+
github_connection = github3.github.GitHubEnterprise(url=ghe, token=token)
3536
elif token:
3637
github_connection = github3.login(token=token)
3738
else:
38-
raise ValueError(
39-
"GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set"
40-
)
39+
raise ValueError("GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set")
4140

4241
if not github_connection:
4342
raise ValueError("Unable to authenticate to GitHub")
4443
return github_connection # type: ignore
4544

4645

47-
def get_github_app_installation_token(
48-
gh_app_id: str, gh_app_private_key_bytes: bytes, gh_app_installation_id: str
49-
) -> str | None:
46+
def get_github_app_installation_token(ghe: str, gh_app_id: str, gh_app_private_key_bytes: bytes, gh_app_installation_id: str) -> str | None:
5047
"""
5148
Get a GitHub App Installation token.
49+
API: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation
5250
5351
Args:
52+
ghe (str): the GitHub Enterprise endpoint
5453
gh_app_id (str): the GitHub App ID
5554
gh_app_private_key_bytes (bytes): the GitHub App Private Key
5655
gh_app_installation_id (str): the GitHub App Installation ID
@@ -59,7 +58,8 @@ def get_github_app_installation_token(
5958
str: the GitHub App token
6059
"""
6160
jwt_headers = github3.apps.create_jwt_headers(gh_app_private_key_bytes, gh_app_id)
62-
url = f"https://api.github.com/app/installations/{gh_app_installation_id}/access_tokens"
61+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
62+
url = f"{api_endpoint}/app/installations/{gh_app_installation_id}/access_tokens"
6363

6464
try:
6565
response = requests.post(url, headers=jwt_headers, json=None, timeout=5)

evergreen.py

Lines changed: 74 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,20 @@ def main(): # pragma: no cover
4545
) = env.get_env_vars()
4646

4747
# Auth to GitHub.com or GHE
48-
github_connection = auth.auth_to_github(
49-
token, gh_app_id, gh_app_installation_id, gh_app_private_key, ghe
50-
)
48+
github_connection = auth.auth_to_github(token, gh_app_id, gh_app_installation_id, gh_app_private_key, ghe)
5149

5250
if not token and gh_app_id and gh_app_installation_id and gh_app_private_key:
53-
token = auth.get_github_app_installation_token(
54-
gh_app_id, gh_app_private_key, gh_app_installation_id
55-
)
51+
token = auth.get_github_app_installation_token(ghe, gh_app_id, gh_app_private_key, gh_app_installation_id)
5652

5753
# If Project ID is set, lookup the global project ID
5854
if project_id:
5955
# Check Organization is set as it is required for linking to a project
6056
if not organization:
61-
raise ValueError(
62-
"ORGANIZATION environment variable was not set. Please set it"
63-
)
64-
project_id = get_global_project_id(token, organization, project_id)
57+
raise ValueError("ORGANIZATION environment variable was not set. Please set it")
58+
project_id = get_global_project_id(ghe, token, organization, project_id)
6559

6660
# Get the repositories from the organization, team name, or list of repositories
67-
repos = get_repos_iterator(
68-
organization, team_name, repository_list, github_connection
69-
)
61+
repos = get_repos_iterator(organization, team_name, repository_list, github_connection)
7062

7163
# Iterate through the repositories and open an issue/PR if dependabot is not enabled
7264
count_eligible = 0
@@ -78,13 +70,13 @@ def main(): # pragma: no cover
7870

7971
# Check all the things to see if repo is eligble for a pr/issue
8072
if repo.full_name in exempt_repositories_list:
81-
print("Skipping " + repo.full_name + " (exempted)")
73+
print(f"Skipping {repo.full_name} (exempted)")
8274
continue
8375
if repo.archived:
84-
print("Skipping " + repo.full_name + " (archived)")
76+
print(f"Skipping {repo.full_name} (archived)")
8577
continue
8678
if repo.visibility.lower() not in filter_visibility:
87-
print("Skipping " + repo.full_name + " (visibility-filtered)")
79+
print(f"Skipping {repo.full_name} (visibility-filtered)")
8880
continue
8981
existing_config = None
9082
filename_list = [".github/dependabot.yaml", ".github/dependabot.yml"]
@@ -96,20 +88,14 @@ def main(): # pragma: no cover
9688
break
9789

9890
if existing_config and not update_existing:
99-
print(
100-
"Skipping "
101-
+ repo.full_name
102-
+ " (dependabot file already exists and update_existing is False)"
103-
)
91+
print(f"Skipping {repo.full_name} (dependabot file already exists and update_existing is False)")
10492
continue
10593

106-
if created_after_date and is_repo_created_date_before(
107-
repo.created_at, created_after_date
108-
):
109-
print("Skipping " + repo.full_name + " (created after filter)")
94+
if created_after_date and is_repo_created_date_before(repo.created_at, created_after_date):
95+
print(f"Skipping {repo.full_name} (created after filter)")
11096
continue
11197

112-
print("Checking " + repo.full_name + " for compatible package managers")
98+
print(f"Checking {repo.full_name} for compatible package managers")
11399
# Try to detect package managers and build a dependabot file
114100
dependabot_file = build_dependabot_file(
115101
repo,
@@ -133,42 +119,32 @@ def main(): # pragma: no cover
133119
if not skip:
134120
print("\tEligible for configuring dependabot.")
135121
count_eligible += 1
136-
print("\tConfiguration:\n" + dependabot_file)
122+
print(f"\tConfiguration:\n {dependabot_file}")
137123
if follow_up_type == "pull":
138124
# Try to detect if the repo already has an open pull request for dependabot
139125
skip = check_pending_pulls_for_duplicates(title, repo)
140126
if not skip:
141127
print("\tEligible for configuring dependabot.")
142128
count_eligible += 1
143-
print("\tConfiguration:\n" + dependabot_file)
129+
print(f"\tConfiguration:\n {dependabot_file}")
144130
continue
145131

146132
# Get dependabot security updates enabled if possible
147133
if enable_security_updates:
148-
if not is_dependabot_security_updates_enabled(repo.owner, repo.name, token):
149-
enable_dependabot_security_updates(repo.owner, repo.name, token)
134+
if not is_dependabot_security_updates_enabled(ghe, repo.owner, repo.name, token):
135+
enable_dependabot_security_updates(ghe, repo.owner, repo.name, token)
150136

151137
if follow_up_type == "issue":
152138
skip = check_pending_issues_for_duplicates(title, repo)
153139
if not skip:
154140
count_eligible += 1
155-
body_issue = (
156-
body
157-
+ "\n\n```yaml\n"
158-
+ "# "
159-
+ dependabot_filename_to_use
160-
+ "\n"
161-
+ dependabot_file
162-
+ "\n```"
163-
)
141+
body_issue = f"{body}\n\n```yaml\n# {dependabot_filename_to_use} \n{dependabot_file}\n```"
164142
issue = repo.create_issue(title, body_issue)
165-
print("\tCreated issue " + issue.html_url)
143+
print(f"\tCreated issue {issue.html_url}")
166144
if project_id:
167-
issue_id = get_global_issue_id(
168-
token, organization, repo.name, issue.number
169-
)
170-
link_item_to_project(token, project_id, issue_id)
171-
print("\tLinked issue to project " + project_id)
145+
issue_id = get_global_issue_id(ghe, token, organization, repo.name, issue.number)
146+
link_item_to_project(ghe, token, project_id, issue_id)
147+
print(f"\tLinked issue to project {project_id}")
172148
else:
173149
# Try to detect if the repo already has an open pull request for dependabot
174150
skip = check_pending_pulls_for_duplicates(title, repo)
@@ -186,34 +162,32 @@ def main(): # pragma: no cover
186162
dependabot_filename_to_use,
187163
existing_config,
188164
)
189-
print("\tCreated pull request " + pull.html_url)
165+
print(f"\tCreated pull request {pull.html_url}")
190166
if project_id:
191-
pr_id = get_global_pr_id(
192-
token, organization, repo.name, pull.number
193-
)
194-
response = link_item_to_project(token, project_id, pr_id)
167+
pr_id = get_global_pr_id(ghe, token, organization, repo.name, pull.number)
168+
response = link_item_to_project(ghe, token, project_id, pr_id)
195169
if response:
196-
print("\tLinked pull request to project " + project_id)
170+
print(f"\tLinked pull request to project {project_id}")
197171
except github3.exceptions.NotFoundError:
198172
print("\tFailed to create pull request. Check write permissions.")
199173
continue
200174

201-
print("Done. " + str(count_eligible) + " repositories were eligible.")
175+
print(f"Done. {str(count_eligible)} repositories were eligible.")
202176

203177

204178
def is_repo_created_date_before(repo_created_at: str, created_after_date: str):
205179
"""Check if the repository was created before the created_after_date"""
206180
repo_created_at_date = datetime.fromisoformat(repo_created_at).replace(tzinfo=None)
207-
return created_after_date and repo_created_at_date < datetime.strptime(
208-
created_after_date, "%Y-%m-%d"
209-
)
181+
return created_after_date and repo_created_at_date < datetime.strptime(created_after_date, "%Y-%m-%d")
210182

211183

212-
def is_dependabot_security_updates_enabled(owner, repo, access_token):
213-
"""Check if Dependabot security updates are enabled at the
214-
/repos/:owner/:repo/automated-security-fixes endpoint using the requests library
184+
def is_dependabot_security_updates_enabled(ghe, owner, repo, access_token):
185+
"""
186+
Check if Dependabot security updates are enabled at the /repos/:owner/:repo/automated-security-fixes endpoint using the requests library
187+
API: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#check-if-automated-security-fixes-are-enabled-for-a-repository
215188
"""
216-
url = f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
189+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
190+
url = f"{api_endpoint}/repos/{owner}/{repo}/automated-security-fixes"
217191
headers = {
218192
"Authorization": f"Bearer {access_token}",
219193
"Accept": "application/vnd.github.london-preview+json",
@@ -247,9 +221,13 @@ def check_existing_config(repo, filename):
247221
return None
248222

249223

250-
def enable_dependabot_security_updates(owner, repo, access_token):
251-
"""Enable Dependabot security updates at the /repos/:owner/:repo/automated-security-fixes endpoint using the requests library"""
252-
url = f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
224+
def enable_dependabot_security_updates(ghe, owner, repo, access_token):
225+
"""
226+
Enable Dependabot security updates at the /repos/:owner/:repo/automated-security-fixes endpoint using the requests library
227+
API: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#enable-automated-security-fixes
228+
"""
229+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
230+
url = f"{api_endpoint}/repos/{owner}/{repo}/automated-security-fixes"
253231
headers = {
254232
"Authorization": f"Bearer {access_token}",
255233
"Accept": "application/vnd.github.london-preview+json",
@@ -277,9 +255,7 @@ def get_repos_iterator(organization, team_name, repository_list, github_connecti
277255
else:
278256
# Get the repositories from the repository_list
279257
for repo in repository_list:
280-
repos.append(
281-
github_connection.repository(repo.split("/")[0], repo.split("/")[1])
282-
)
258+
repos.append(github_connection.repository(repo.split("/")[0], repo.split("/")[1]))
283259

284260
return repos
285261

@@ -290,7 +266,7 @@ def check_pending_pulls_for_duplicates(title, repo) -> bool:
290266
skip = False
291267
for pull_request in pull_requests:
292268
if pull_request.title.startswith(title):
293-
print("\tPull request already exists: " + pull_request.html_url)
269+
print(f"\tPull request already exists: {pull_request.html_url}")
294270
skip = True
295271
break
296272
return skip
@@ -302,7 +278,7 @@ def check_pending_issues_for_duplicates(title, repo) -> bool:
302278
skip = False
303279
for issue in issues:
304280
if issue.title.startswith(title):
305-
print("\tIssue already exists: " + issue.html_url)
281+
print(f"\tIssue already exists: {issue.html_url}")
306282
skip = True
307283
break
308284
return skip
@@ -338,19 +314,19 @@ def commit_changes(
338314
branch=branch_name,
339315
)
340316

341-
pull = repo.create_pull(
342-
title=title, body=body, head=branch_name, base=repo.default_branch
343-
)
317+
pull = repo.create_pull(title=title, body=body, head=branch_name, base=repo.default_branch)
344318
return pull
345319

346320

347-
def get_global_project_id(token, organization, number):
348-
"""Fetches the project ID from GitHub's GraphQL API."""
349-
url = "https://api.github.com/graphql"
321+
def get_global_project_id(ghe, token, organization, number):
322+
"""
323+
Fetches the project ID from GitHub's GraphQL API.
324+
API: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql
325+
"""
326+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
327+
url = f"{api_endpoint}/graphql"
350328
headers = {"Authorization": f"Bearer {token}"}
351-
data = {
352-
"query": f'query{{organization(login: "{organization}") {{projectV2(number: {number}){{id}}}}}}'
353-
}
329+
data = {"query": f'query{{organization(login: "{organization}") {{projectV2(number: {number}){{id}}}}}}'}
354330

355331
try:
356332
response = requests.post(url, headers=headers, json=data, timeout=20)
@@ -366,9 +342,13 @@ def get_global_project_id(token, organization, number):
366342
return None
367343

368344

369-
def get_global_issue_id(token, organization, repository, issue_number):
370-
"""Fetches the issue ID from GitHub's GraphQL API"""
371-
url = "https://api.github.com/graphql"
345+
def get_global_issue_id(ghe, token, organization, repository, issue_number):
346+
"""
347+
Fetches the issue ID from GitHub's GraphQL API
348+
API: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql
349+
"""
350+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
351+
url = f"{api_endpoint}/graphql"
372352
headers = {"Authorization": f"Bearer {token}"}
373353
data = {
374354
"query": f"""
@@ -396,9 +376,13 @@ def get_global_issue_id(token, organization, repository, issue_number):
396376
return None
397377

398378

399-
def get_global_pr_id(token, organization, repository, pr_number):
400-
"""Fetches the pull request ID from GitHub's GraphQL API"""
401-
url = "https://api.github.com/graphql"
379+
def get_global_pr_id(ghe, token, organization, repository, pr_number):
380+
"""
381+
Fetches the pull request ID from GitHub's GraphQL API
382+
API: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql
383+
"""
384+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
385+
url = f"{api_endpoint}/graphql"
402386
headers = {"Authorization": f"Bearer {token}"}
403387
data = {
404388
"query": f"""
@@ -426,13 +410,15 @@ def get_global_pr_id(token, organization, repository, pr_number):
426410
return None
427411

428412

429-
def link_item_to_project(token, project_id, item_id):
430-
"""Links an item (issue or pull request) to a project in GitHub."""
431-
url = "https://api.github.com/graphql"
413+
def link_item_to_project(ghe, token, project_id, item_id):
414+
"""
415+
Links an item (issue or pull request) to a project in GitHub.
416+
API: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql
417+
"""
418+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
419+
url = f"{api_endpoint}/graphql"
432420
headers = {"Authorization": f"Bearer {token}"}
433-
data = {
434-
"query": f'mutation {{addProjectV2ItemById(input: {{projectId: "{project_id}", contentId: "{item_id}"}}) {{item {{id}}}}}}'
435-
}
421+
data = {"query": f'mutation {{addProjectV2ItemById(input: {{projectId: "{project_id}", contentId: "{item_id}"}}) {{item {{id}}}}}}'}
436422

437423
try:
438424
response = requests.post(url, headers=headers, json=data, timeout=20)

test_auth.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ def test_get_github_app_installation_token(self, mock_post):
5757
mock_response.json.return_value = {"token": dummy_token}
5858
mock_post.return_value = mock_response
5959

60-
result = auth.get_github_app_installation_token(
61-
b"gh_private_token", "gh_app_id", "gh_installation_id"
62-
)
60+
result = auth.get_github_app_installation_token(b"ghe", "gh_private_token", "gh_app_id", "gh_installation_id")
6361

6462
self.assertEqual(result, dummy_token)
6563

0 commit comments

Comments
 (0)