Skip to content

Commit 90922d5

Browse files
authored
Merge pull request #197 from ricardojdsilva87/feat/support-github-enterprise-api
feat: support GitHub enterprise api
2 parents 345717c + 80b8c21 commit 90922d5

13 files changed

+418
-101
lines changed

.coveragerc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[run]
2+
omit =
3+
# omit test files
4+
test_*.py

.env-example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
GH_ENTERPRISE_URL=""
1+
GH_ENTERPRISE_URL = ""
22
GH_TOKEN = ""
33
END_DATE = ""
44
ORGANIZATION = "organization"
@@ -9,3 +9,4 @@ START_DATE = ""
99
GH_APP_ID = ""
1010
GH_INSTALLATION_ID = ""
1111
GH_PRIVATE_KEY = ""
12+
GITHUB_APP_ENTERPRISE_ONLY = ""

README.md

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,19 @@ Find out more in the [GitHub API documentation](https://docs.github.com/en/rest/
3838
1. Create a repository to host this GitHub Action or select an existing repository.
3939
1. Select a best fit workflow file from the [examples below](#example-workflows).
4040
1. Copy that example into your repository (from step 1) and into the proper directory for GitHub Actions: `.github/workflows/` directory with the file extension `.yml` (ie. `.github/workflows/contributors.yml`)
41-
1. Edit the values (`ORGANIZATION`, `REPOSITORY`, `START_DATE`, `END_DATE`) from the sample workflow with your information.
42-
- If no start and end date are supplied, the action will consider the entire repository history and be unable to determine if contributors are new or returning.
43-
- If running on a whole organization then no repository is needed.
44-
- If running the action on just one repository or a list of repositories, then no organization is needed.
45-
1. Also edit the value for `GH_ENTERPRISE_URL` if you are using a GitHub Server and not using github.com. For github.com users, don't put anything in here.
41+
1. Edit the values below from the sample workflow with your information:
42+
43+
- `ORGANIZATION`
44+
- `REPOSITORY`
45+
- `START_DATE`
46+
- `END_DATE`
47+
48+
If no **start and end date** are supplied, the action will consider the entire repository history and be unable to determine if contributors are new or returning.
49+
If running on a whole **organization** then no repository is needed.
50+
If running the action on just **one repository** or a **list of repositories**, then no organization is needed.
51+
52+
1. Also edit the value for `GH_ENTERPRISE_URL` if you are using a GitHub Server and not using github.com.
53+
For github.com users, leave it empty.
4654
1. If you are running this action on an organization or repository other than the one where the workflow file is going to be, then update the value of `GH_TOKEN`.
4755
- Do this by creating a [GitHub API token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) with permissions to read the repository/organization and write issues.
4856
- Then take the value of the API token you just created, and [create a repository secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets) where the name of the secret is `GH_TOKEN` and the value of the secret the API token.
@@ -62,11 +70,12 @@ This action can be configured to authenticate with GitHub App Installation or Pe
6270

6371
##### GitHub App Installation
6472

65-
| field | required | default | description |
66-
| ------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
67-
| `GH_APP_ID` | True | `""` | GitHub Application ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. |
68-
| `GH_APP_INSTALLATION_ID` | True | `""` | GitHub Application Installation ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. |
69-
| `GH_APP_PRIVATE_KEY` | True | `""` | GitHub Application Private Key. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. |
73+
| field | required | default | description |
74+
| ---------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
75+
| `GH_APP_ID` | True | `""` | GitHub Application ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. |
76+
| `GH_APP_INSTALLATION_ID` | True | `""` | GitHub Application Installation ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. |
77+
| `GH_APP_PRIVATE_KEY` | True | `""` | GitHub Application Private Key. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. |
78+
| `GITHUB_APP_ENTERPRISE_ONLY` | False | false | Set this input to `true` if your app is created in GHE and communicates with GHE. |
7079

7180
##### Personal Access Token (PAT)
7281

@@ -143,6 +152,62 @@ jobs:
143152
assignees: <YOUR_GITHUB_HANDLE_HERE>
144153
```
145154
155+
#### Using GitHub app
156+
157+
```yaml
158+
name: Monthly contributor report
159+
on:
160+
workflow_dispatch:
161+
schedule:
162+
- cron: "3 2 1 * *"
163+
164+
permissions:
165+
contents: read
166+
167+
jobs:
168+
contributor_report:
169+
name: contributor report
170+
runs-on: ubuntu-latest
171+
permissions:
172+
issues: write
173+
174+
steps:
175+
- name: Get dates for last month
176+
shell: bash
177+
run: |
178+
# Calculate the first day of the previous month
179+
start_date=$(date -d "last month" +%Y-%m-01)
180+
181+
# Calculate the last day of the previous month
182+
end_date=$(date -d "$start_date +1 month -1 day" +%Y-%m-%d)
183+
184+
#Set an environment variable with the date range
185+
echo "START_DATE=$start_date" >> "$GITHUB_ENV"
186+
echo "END_DATE=$end_date" >> "$GITHUB_ENV"
187+
188+
- name: Run contributor action
189+
uses: github/contributors@v1
190+
env:
191+
GH_APP_ID: ${{ secrets.GH_APP_ID }}
192+
GH_APP_INSTALLATION_ID: ${{ secrets.GH_APP_INSTALLATION_ID }}
193+
GH_APP_PRIVATE_KEY: ${{ secrets.GH_APP_PRIVATE_KEY }}
194+
# GITHUB_APP_ENTERPRISE_ONLY: True --> Set to true when created GHE App needs to communicate with GHE api
195+
GH_ENTERPRISE_URL: ${{ github.server_url }}
196+
# GH_TOKEN: ${{ steps.app-token.outputs.token }} --> the token input is not used if the github app inputs are set
197+
START_DATE: ${{ env.START_DATE }}
198+
END_DATE: ${{ env.END_DATE }}
199+
ORGANIZATION: <YOUR_ORGANIZATION_GOES_HERE>
200+
SPONSOR_INFO: "true"
201+
202+
- name: Create issue
203+
uses: peter-evans/create-issue-from-file@v5
204+
with:
205+
title: Monthly contributor report
206+
token: ${{ secrets.GITHUB_TOKEN }}
207+
content-filepath: ./contributors.md
208+
assignees: <YOUR_GITHUB_HANDLE_HERE>
209+
```
210+
146211
## Example Markdown output with `start_date` and `end_date` supplied
147212

148213
```markdown

auth.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,42 @@
11
"""This is the module that contains functions related to authenticating to GitHub with a personal access token."""
22

33
import github3
4+
import requests
45

56

67
def auth_to_github(
7-
gh_app_id: str,
8-
gh_app_installation_id: int,
9-
gh_app_private_key_bytes: bytes,
108
token: str,
9+
gh_app_id: int | None,
10+
gh_app_installation_id: int | None,
11+
gh_app_private_key_bytes: bytes,
1112
ghe: str,
13+
gh_app_enterprise_only: bool,
1214
) -> github3.GitHub:
1315
"""
1416
Connect to GitHub.com or GitHub Enterprise, depending on env variables.
1517
1618
Args:
17-
gh_app_id (str): the GitHub App ID
18-
gh_installation_id (int): the GitHub App Installation ID
19-
gh_app_private_key (bytes): the GitHub App Private Key
2019
token (str): the GitHub personal access token
20+
gh_app_id (int | None): the GitHub App ID
21+
gh_app_installation_id (int | None): the GitHub App Installation ID
22+
gh_app_private_key_bytes (bytes): the GitHub App Private Key
2123
ghe (str): the GitHub Enterprise URL
24+
gh_app_enterprise_only (bool): Set this to true if the GH APP is created on GHE and needs to communicate with GHE api only
2225
2326
Returns:
2427
github3.GitHub: the GitHub connection object
2528
"""
2629
if gh_app_id and gh_app_private_key_bytes and gh_app_installation_id:
27-
gh = github3.github.GitHub()
30+
if ghe and gh_app_enterprise_only:
31+
gh = github3.github.GitHubEnterprise(url=ghe)
32+
else:
33+
gh = github3.github.GitHub()
2834
gh.login_as_app_installation(
2935
gh_app_private_key_bytes, gh_app_id, gh_app_installation_id
3036
)
3137
github_connection = gh
3238
elif ghe and token:
33-
github_connection = github3.github.GitHubEnterprise(ghe, token=token)
39+
github_connection = github3.github.GitHubEnterprise(url=ghe, token=token)
3440
elif token:
3541
github_connection = github3.login(token=token)
3642
else:
@@ -41,3 +47,35 @@ def auth_to_github(
4147
if not github_connection:
4248
raise ValueError("Unable to authenticate to GitHub")
4349
return github_connection # type: ignore
50+
51+
52+
def get_github_app_installation_token(
53+
ghe: str,
54+
gh_app_id: str,
55+
gh_app_private_key_bytes: bytes,
56+
gh_app_installation_id: str,
57+
) -> str | None:
58+
"""
59+
Get a GitHub App Installation token.
60+
API: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation
61+
62+
Args:
63+
ghe (str): the GitHub Enterprise endpoint
64+
gh_app_id (str): the GitHub App ID
65+
gh_app_private_key_bytes (bytes): the GitHub App Private Key
66+
gh_app_installation_id (str): the GitHub App Installation ID
67+
68+
Returns:
69+
str: the GitHub App token
70+
"""
71+
jwt_headers = github3.apps.create_jwt_headers(gh_app_private_key_bytes, gh_app_id)
72+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
73+
url = f"{api_endpoint}/app/installations/{gh_app_installation_id}/access_tokens"
74+
75+
try:
76+
response = requests.post(url, headers=jwt_headers, json=None, timeout=5)
77+
response.raise_for_status()
78+
except requests.exceptions.RequestException as e:
79+
print(f"Request to get GitHub App Installation Token failed: {e}")
80+
return None
81+
return response.json().get("token")

contributor_stats.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,7 @@ def merge_contributors(contributors: list) -> list:
114114
)
115115
# Merge the commit urls via concatenation
116116
merged_contributor.commit_url = (
117-
merged_contributor.commit_url
118-
+ ", "
119-
+ contributor.commit_url
117+
f"{merged_contributor.commit_url}, {contributor.commit_url}"
120118
)
121119
# Merge the new_contributor attribute via OR
122120
merged_contributor.new_contributor = (
@@ -130,7 +128,7 @@ def merge_contributors(contributors: list) -> list:
130128
return merged_contributors
131129

132130

133-
def get_sponsor_information(contributors: list, token: str) -> list:
131+
def get_sponsor_information(contributors: list, token: str, ghe: str) -> list:
134132
"""
135133
Get the sponsor information for each contributor
136134
@@ -155,9 +153,10 @@ def get_sponsor_information(contributors: list, token: str) -> list:
155153
variables = {"username": contributor.username}
156154

157155
# Send the GraphQL request
156+
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
158157
headers = {"Authorization": f"Bearer {token}"}
159158
response = requests.post(
160-
"https://api.github.com/graphql",
159+
f"{api_endpoint}/graphql",
161160
json={"query": query, "variables": variables},
162161
headers=headers,
163162
timeout=60,
@@ -169,10 +168,9 @@ def get_sponsor_information(contributors: list, token: str) -> list:
169168

170169
data = response.json()["data"]
171170

171+
endpoint = ghe if ghe else "https://github.com"
172172
# if the user has a sponsor page, add it to the contributor object
173173
if data["repositoryOwner"]["hasSponsorsListing"]:
174-
contributor.sponsor_info = (
175-
f"https://github.com/sponsors/{contributor.username}"
176-
)
174+
contributor.sponsor_info = f"{endpoint}/sponsors/{contributor.username}"
177175

178176
return contributors

contributors.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def main():
1919
repository_list,
2020
gh_app_id,
2121
gh_app_installation_id,
22-
gh_app_private_key_bytes,
22+
gh_app_private_key,
23+
gh_app_enterprise_only,
2324
token,
2425
ghe,
2526
start_date,
@@ -30,16 +31,22 @@ def main():
3031

3132
# Auth to GitHub.com
3233
github_connection = auth.auth_to_github(
33-
gh_app_id, gh_app_installation_id, gh_app_private_key_bytes, token, ghe
34+
token,
35+
gh_app_id,
36+
gh_app_installation_id,
37+
gh_app_private_key,
38+
ghe,
39+
gh_app_enterprise_only,
3440
)
3541

42+
if not token and gh_app_id and gh_app_installation_id and gh_app_private_key:
43+
token = auth.get_github_app_installation_token(
44+
ghe, gh_app_id, gh_app_private_key, gh_app_installation_id
45+
)
46+
3647
# Get the contributors
3748
contributors = get_all_contributors(
38-
organization,
39-
repository_list,
40-
start_date,
41-
end_date,
42-
github_connection,
49+
organization, repository_list, start_date, end_date, github_connection, ghe
4350
)
4451

4552
# Check for new contributor if user provided start_date and end_date
@@ -52,6 +59,7 @@ def main():
5259
start_date="2008-02-29", # GitHub was founded on 2008-02-29
5360
end_date=start_date,
5461
github_connection=github_connection,
62+
ghe=ghe,
5563
)
5664
for contributor in contributors:
5765
contributor.new_contributor = contributor_stats.is_new_contributor(
@@ -60,7 +68,9 @@ def main():
6068

6169
# Get sponsor information on the contributor
6270
if sponsor_info == "true":
63-
contributors = contributor_stats.get_sponsor_information(contributors, token)
71+
contributors = contributor_stats.get_sponsor_information(
72+
contributors, token, ghe
73+
)
6474
# Output the contributors information
6575
# print(contributors)
6676
markdown.write_to_markdown(
@@ -72,6 +82,7 @@ def main():
7282
repository_list,
7383
sponsor_info,
7484
link_to_profile,
85+
ghe,
7586
)
7687
json_writer.write_to_json(
7788
filename="contributors.json",
@@ -91,6 +102,7 @@ def get_all_contributors(
91102
start_date: str,
92103
end_date: str,
93104
github_connection: object,
105+
ghe: str,
94106
):
95107
"""
96108
Get all contributors from the organization or repository
@@ -118,7 +130,7 @@ def get_all_contributors(
118130
all_contributors = []
119131
if repos:
120132
for repo in repos:
121-
repo_contributors = get_contributors(repo, start_date, end_date)
133+
repo_contributors = get_contributors(repo, start_date, end_date, ghe)
122134
if repo_contributors:
123135
all_contributors.append(repo_contributors)
124136

@@ -128,11 +140,7 @@ def get_all_contributors(
128140
return all_contributors
129141

130142

131-
def get_contributors(
132-
repo: object,
133-
start_date: str,
134-
end_date: str,
135-
):
143+
def get_contributors(repo: object, start_date: str, end_date: str, ghe: str):
136144
"""
137145
Get contributors from a single repository and filter by start end dates if present.
138146
@@ -165,12 +173,11 @@ def get_contributors(
165173
continue
166174

167175
# Store the contributor information in a ContributorStats object
176+
endpoint = ghe if ghe else "https://github.com"
168177
if start_date and end_date:
169-
commit_url = f"https://github.com/{repo.full_name}/commits?author={user.login}&since={start_date}&until={end_date}"
178+
commit_url = f"{endpoint}/{repo.full_name}/commits?author={user.login}&since={start_date}&until={end_date}"
170179
else:
171-
commit_url = (
172-
f"https://github.com/{repo.full_name}/commits?author={user.login}"
173-
)
180+
commit_url = f"{endpoint}/{repo.full_name}/commits?author={user.login}"
174181
contributor = contributor_stats.ContributorStats(
175182
user.login,
176183
False,
@@ -181,7 +188,7 @@ def get_contributors(
181188
)
182189
contributors.append(contributor)
183190
except Exception as e:
184-
print("Error getting contributors for repository: " + repo.full_name)
191+
print(f"Error getting contributors for repository: {repo.full_name}")
185192
print(e)
186193
return None
187194

0 commit comments

Comments
 (0)