Skip to content

Commit 26faf5b

Browse files
authored
feat: add GH_ENTERPRISE_API_URL support for newer GHEC instances (#524)
* feat: add GH_ENTERPRISE_API_URL support for newer GHEC instances Fixes #513 ## What Add a new GH_ENTERPRISE_API_URL environment variable that allows users to specify a custom GitHub Enterprise API endpoint directly, bypassing the automatic /api/v3 suffix that github3.py's GitHubEnterprise class appends. This supports newer GHEC instances that use the https://api.<server>.ghe.com format. ## Why GitHub Enterprise Cloud instances now follow a new API endpoint pattern (https://api.<server>.ghe.com) instead of the traditional https://<server>/api/v3. The github3.py library's GitHubEnterprise class unconditionally appends /api/v3, making it impossible to use the new format without an override mechanism. ## Notes - The session.base_url override on GitHubEnterprise objects is reaching into github3.py internals. This is stable in the pinned 4.0.1 version but worth noting for the planned PyGithub migration. - GH_ENTERPRISE_API_URL requires GH_ENTERPRISE_URL to also be set, since ghe is used as a truthy flag throughout the codebase for enterprise mode detection. - The get_api_endpoint helper in env.py centralizes the 7 previously duplicated endpoint construction patterns. Signed-off-by: jmeridth <jmeridth@gmail.com> * fix: strip trailing slash from GH_ENTERPRISE_API_URL at parse time and add missing tests ## What Strip trailing slashes from GH_ENTERPRISE_API_URL during env var parsing in get_env_vars() so both session.base_url and get_api_endpoint() receive a consistent value. Added tests for get_global_issue_id and get_global_pr_id with custom API URL, trailing slash/whitespace stripping, and documented the gh_app_enterprise_only=False behavior. ## Why The get_api_endpoint() helper stripped trailing slashes but auth.py set session.base_url from the raw value, creating an inconsistency that could cause double-slash issues in github3.py-routed requests. ## Notes - The gh_app_enterprise_only=False + ghe_api_url case is NOT a bug: when the flag is False, the GH App authenticates against github.com by design. The ghe_api_url still applies to direct REST/GraphQL calls via get_api_endpoint(). A test now documents this behavior. Signed-off-by: jmeridth <jmeridth@gmail.com> * fix: strip trailing slash from ghe in get_api_endpoint to prevent double-slash URLs ## What Strip trailing slash from the ghe parameter in get_api_endpoint() to prevent constructing URLs like https://example.com//api/v3. Also fixed a pre-existing test bug where bytes were passed as the ghe parameter. ## Why Copilot review flagged that get_api_endpoint() normalized ghe_api_url but not ghe, which could produce malformed URLs if GH_ENTERPRISE_URL had a trailing slash. ## Notes - The pre-existing test_get_github_app_installation_token test passed b"ghe" (bytes) as the first positional arg instead of a string. This was masked because f-string formatting of bytes produced a technically valid string, but .rstrip('/') on bytes raises TypeError. Fixed to use named kwargs with correct types. Signed-off-by: jmeridth <jmeridth@gmail.com> --------- Signed-off-by: jmeridth <jmeridth@gmail.com>
1 parent f1a2991 commit 26faf5b

File tree

7 files changed

+476
-42
lines changed

7 files changed

+476
-42
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ All feedback regarding our GitHub Actions, as a whole, should be communicated th
4747
- `TITLE`
4848
- `BODY`
4949
50-
If running on a whole **organization** then no repository is needed.
51-
If running the action on just **one repository** or a **list of repositories**, then no organization is needed.
52-
If running the action on a **team**, then an organization is required and no repository is needed.
50+
If running on a whole **organization** then no repository is needed.
51+
If running the action on just **one repository** or a **list of repositories**, then no organization is needed.
52+
If running the action on a **team**, then an organization is required and no repository is needed.
5353
The type should be either `issue` or `pull` representing the action that you want taken after discovering a repository that should enable dependabot.
5454
5555
1. Optionally, edit the value `CREATED_AFTER_DATE` if you are setting up this action to run regularly and only want newly created repositories to be considered.
@@ -59,6 +59,7 @@ All feedback regarding our GitHub Actions, as a whole, should be communicated th
5959
If set to `false`, the action will only create a new dependabot configuration file if there is not an existing one.
6060
1. Also edit the value for `GH_ENTERPRISE_URL` if you are using a GitHub Server and not using github.com.
6161
For github.com users, leave it empty.
62+
If your GitHub Enterprise Cloud instance uses the newer API format (e.g., `https://api.mycompany.ghe.com`), also set `GH_ENTERPRISE_API_URL`.
6263
1. Update the value of `GH_TOKEN`. 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 the following permissions:
6364
- If using **classic tokens**:
6465
- `workflow`, this will set also all permissions for `repo`
@@ -113,6 +114,7 @@ The needed GitHub app permissions are the following under `Repository permission
113114
| field | required | default | description |
114115
| -------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
115116
| `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub, ex: `https://yourgheserver.com`.<br>github.com users should not enter anything here. |
117+
| `GH_ENTERPRISE_API_URL` | False | "" | Full API endpoint URL for GitHub Enterprise Cloud instances that use the newer API format (e.g., `https://api.mycompany.ghe.com`). When set, this overrides the default `/api/v3` suffix behavior. Requires `GH_ENTERPRISE_URL` to also be set. |
116118
| `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` or `REPOSITORY_SEARCH_QUERY` | | The name of the GitHub organization which you want this action to work from. ie. github.com/github would be `github` |
117119
| `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` or `REPOSITORY_SEARCH_QUERY` | | The name of the repository and organization which you want this action to work from. ie. `github-community-projects/evergreen` or a comma separated list of multiple repositories `github-community-projects/evergreen,super-linter/super-linter` |
118120
| `REPOSITORY_SEARCH_QUERY` | Required to have `ORGANIZATION` or `REPOSITORY` or `REPOSITORY_SEARCH_QUERY` | "" | When set, directs the action to use the GitHub Search API to search repositories matching this query instead of enumerating all organization repositories. This overrides anything set in the `REPOSITORY` and `ORGANIZATION` variables. Example: `org:my-org is:repository archived:false created:>2025-07-01`. |
@@ -138,7 +140,7 @@ The needed GitHub app permissions are the following under `Repository permission
138140
139141
### Private repositories configuration
140142
141-
Dependabot allows the configuration of [private registries](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#configuration-options-for-private-registries) for dependabot to use.
143+
Dependabot allows the configuration of [private registries](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#configuration-options-for-private-registries) for dependabot to use.
142144
To add a private registry configuration to the dependabot file the `DEPENDABOT_CONFIG_FILE` needs to be set with the path of the configuration file.
143145
144146
This configuration file needs to exist on the repository where the action runs. It can also be created locally to test some configurations (if created locally it takes precedence over the file on the repository).

auth.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""This is the module that contains functions related to authenticating to GitHub with a personal access token."""
22

3+
import env
34
import github3
45
import requests
56

@@ -11,6 +12,7 @@ def auth_to_github(
1112
gh_app_private_key_bytes: bytes,
1213
ghe: str,
1314
gh_app_enterprise_only: bool,
15+
ghe_api_url: str = "",
1416
) -> github3.GitHub:
1517
"""
1618
Connect to GitHub.com or GitHub Enterprise, depending on env variables.
@@ -22,13 +24,16 @@ def auth_to_github(
2224
gh_app_private_key_bytes (bytes): the GitHub App Private Key
2325
ghe (str): the GitHub Enterprise URL
2426
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
27+
ghe_api_url (str): the full GitHub Enterprise API endpoint URL override
2528
2629
Returns:
2730
github3.GitHub: the GitHub connection object
2831
"""
2932
if gh_app_id and gh_app_private_key_bytes and gh_app_installation_id:
3033
if ghe and gh_app_enterprise_only:
3134
gh = github3.github.GitHubEnterprise(url=ghe)
35+
if ghe_api_url:
36+
gh.session.base_url = ghe_api_url
3237
else:
3338
gh = github3.github.GitHub()
3439
gh.login_as_app_installation(
@@ -37,6 +42,8 @@ def auth_to_github(
3742
github_connection = gh
3843
elif ghe and token:
3944
github_connection = github3.github.GitHubEnterprise(url=ghe, token=token)
45+
if ghe_api_url:
46+
github_connection.session.base_url = ghe_api_url
4047
elif token:
4148
github_connection = github3.login(token=token)
4249
else:
@@ -54,6 +61,7 @@ def get_github_app_installation_token(
5461
gh_app_id: int | None,
5562
gh_app_private_key_bytes: bytes,
5663
gh_app_installation_id: int | None,
64+
ghe_api_url: str = "",
5765
) -> str | None:
5866
"""
5967
Get a GitHub App Installation token.
@@ -64,14 +72,15 @@ def get_github_app_installation_token(
6472
gh_app_id (int | None): the GitHub App ID
6573
gh_app_private_key_bytes (bytes): the GitHub App Private Key
6674
gh_app_installation_id (int | None): the GitHub App Installation ID
75+
ghe_api_url (str): the full GitHub Enterprise API endpoint URL override
6776
6877
Returns:
6978
str: the GitHub App token
7079
"""
7180
jwt_headers = github3.apps.create_jwt_headers(
7281
gh_app_private_key_bytes, str(gh_app_id)
7382
)
74-
api_endpoint = f"{ghe}/api/v3" if ghe else "https://api.github.com"
83+
api_endpoint = env.get_api_endpoint(ghe, ghe_api_url)
7584
url = f"{api_endpoint}/app/installations/{gh_app_installation_id}/access_tokens"
7685

7786
try:

env.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@
4545
]
4646

4747

48+
def get_api_endpoint(ghe: str, ghe_api_url: str) -> str:
49+
"""Return the GitHub API endpoint URL.
50+
51+
Args:
52+
ghe: The GitHub Enterprise URL (e.g., https://github.example.com)
53+
ghe_api_url: Optional override for the full API endpoint URL
54+
55+
Returns:
56+
The API endpoint URL to use for REST/GraphQL calls.
57+
"""
58+
if ghe_api_url:
59+
return ghe_api_url.rstrip("/")
60+
if ghe:
61+
return f"{ghe.rstrip('/')}/api/v3"
62+
return "https://api.github.com"
63+
64+
4865
def get_bool_env_var(env_var_name: str, default: bool = False) -> bool:
4966
"""Get a boolean environment variable.
5067
@@ -147,6 +164,7 @@ def get_env_vars(
147164
str | None,
148165
list[str],
149166
str | None,
167+
str,
150168
]:
151169
"""
152170
Get the environment variables for use in the action.
@@ -183,6 +201,7 @@ def get_env_vars(
183201
team_name (str): The team to search for repositories in
184202
labels (list[str]): A list of labels to be added to dependabot configuration
185203
dependabot_config_file (str): Dependabot extra configuration file location path
204+
ghe_api_url (str): The full GitHub Enterprise API endpoint URL override
186205
"""
187206

188207
if not test: # pragma: no cover
@@ -233,6 +252,12 @@ def get_env_vars(
233252

234253
ghe = os.getenv("GH_ENTERPRISE_URL", default="").strip()
235254

255+
ghe_api_url = os.getenv("GH_ENTERPRISE_API_URL", default="").strip().rstrip("/")
256+
if ghe_api_url and not ghe:
257+
raise ValueError(
258+
"GH_ENTERPRISE_API_URL requires GH_ENTERPRISE_URL to also be set"
259+
)
260+
236261
exempt_repos = os.getenv("EXEMPT_REPOS")
237262
exempt_repositories_list = []
238263
if exempt_repos:
@@ -409,4 +434,5 @@ def get_env_vars(
409434
team_name,
410435
labels_list,
411436
dependabot_config_file,
437+
ghe_api_url,
412438
)

0 commit comments

Comments
 (0)