feat: add GH_ENTERPRISE_API_URL support for newer GHEC instances#524
Merged
feat: add GH_ENTERPRISE_API_URL support for newer GHEC instances#524
Conversation
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>
…d 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>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds support for GitHub Enterprise Cloud’s newer API endpoint format by introducing GH_ENTERPRISE_API_URL and centralizing API base URL construction, enabling REST/GraphQL calls (and github3 session usage) to bypass the hardcoded /api/v3 suffix.
Changes:
- Introduces
GH_ENTERPRISE_API_URLand a sharedenv.get_api_endpoint()helper to select the correct REST/GraphQL base URL. - Updates authentication and API-calling helpers to accept/use
ghe_api_url, including overridinggithub3session base URL for enterprise flows. - Expands unit test coverage across env parsing, auth, and evergreen REST/GraphQL URL construction; documents the new env var in README.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
env.py |
Adds get_api_endpoint() and reads/validates GH_ENTERPRISE_API_URL into get_env_vars() output. |
auth.py |
Uses get_api_endpoint() for installation token calls and optionally overrides github3 session base URL for enterprise connections. |
evergreen.py |
Threads ghe_api_url through REST/GraphQL helper functions and main flow; switches REST/GraphQL URL construction to get_api_endpoint(). |
test_env.py |
Adds tests for GH_ENTERPRISE_API_URL parsing/validation and for get_api_endpoint() behavior. |
test_auth.py |
Adds tests ensuring session.base_url override behavior and installation token URL usage with custom API URL. |
test_evergreen.py |
Updates call sites for new function signatures and adds coverage ensuring custom API URLs are used for REST/GraphQL calls. |
README.md |
Documents GH_ENTERPRISE_API_URL and when to use it. |
…ble-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>
zkoppert
approved these changes
Mar 29, 2026
Collaborator
zkoppert
left a comment
There was a problem hiding this comment.
I ran this branch of the action end-to-end against the staff GHEC using the new GH_ENTERPRISE_API_URL=https://api.github.ghe.com format in dry-run mode. It:
- Authenticated via the new API endpoint
- Enumerated all repos in the staff/engineering org via the github3.py
session.base_url override - Checked each repo for package manager files (pip, maven, npm, gomod
detected) - Generated dependabot configs for 4 eligible repos
- Correctly skipped PR creation due to DRY_RUN=True
- Exited cleanly with status 0
🎉
Code looks good to. I'll open another issue as I notice the env functions are getting lengthy. Not related to this PR, just noticed while reviewing.
jmeridth
added a commit
that referenced
this pull request
Mar 29, 2026
…enConfig dataclass ## What Replace the 30-element tuple returned by get_env_vars() with a frozen dataclass EvergreenConfig. Updated evergreen.py main() to use config.attribute access instead of tuple destructuring, and converted all 20 expected_result tuples in test_env.py to use named EvergreenConfig construction. Includes the ghe_api_url field from PR #524. ## Why The 30-element tuple was fragile — adding/removing fields required counting positions across 20+ tests, and positional access like result[8] was unreadable. Named attribute access eliminates these problems and makes future field additions trivial. ## Notes - This is a pure refactor with no behavior change. All 173 tests pass with identical coverage. - The dataclass uses frozen=True for immutability, matching the previous tuple's semantics. - pylint disable for too-many-instance-attributes is necessary since the config genuinely has 30 fields. - Rebased on main after PR #524 merge to include ghe_api_url field. Signed-off-by: jmeridth <jmeridth@gmail.com>
jmeridth
added a commit
that referenced
this pull request
Mar 30, 2026
…taclass (#525) * refactor: replace get_env_vars() 30-element tuple with frozen EvergreenConfig dataclass ## What Replace the 30-element tuple returned by get_env_vars() with a frozen dataclass EvergreenConfig. Updated evergreen.py main() to use config.attribute access instead of tuple destructuring, and converted all 20 expected_result tuples in test_env.py to use named EvergreenConfig construction. Includes the ghe_api_url field from PR #524. ## Why The 30-element tuple was fragile — adding/removing fields required counting positions across 20+ tests, and positional access like result[8] was unreadable. Named attribute access eliminates these problems and makes future field additions trivial. ## Notes - This is a pure refactor with no behavior change. All 173 tests pass with identical coverage. - The dataclass uses frozen=True for immutability, matching the previous tuple's semantics. - pylint disable for too-many-instance-attributes is necessary since the config genuinely has 30 fields. - Rebased on main after PR #524 merge to include ghe_api_url field. Signed-off-by: jmeridth <jmeridth@gmail.com> * fix: tighten type annotations for bool fields that never return None ## What Changed group_dependencies, enable_security_updates, and update_existing from bool | None to bool in the EvergreenConfig dataclass. ## Why get_bool_env_var() always returns bool, never None. The overly permissive annotations were carried over from the old tuple type hints. Signed-off-by: jmeridth <jmeridth@gmail.com> * fix: tighten search_query and repo_specific_exemptions type annotations ## What Changed search_query from str | None to str, and repo_specific_exemptions from bare dict to dict[str, list[str]] in EvergreenConfig. ## Why search_query always comes from os.getenv(..., "").strip() which never returns None. repo_specific_exemptions is built by parse_repo_specific_exemptions() which always returns dict[str, list[str]]. The tighter annotations improve IDE hints and mypy coverage. Signed-off-by: jmeridth <jmeridth@gmail.com> --------- Signed-off-by: jmeridth <jmeridth@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Proposed Changes
Fixes #513
What
Add a new
GH_ENTERPRISE_API_URLenvironment variable that allows users to specify a custom GitHub Enterprise API endpoint directly, bypassing the automatic/api/v3suffix thatgithub3.py'sGitHubEnterpriseclass appends. This supports newer GHEC instances that use thehttps://api.<server>.ghe.comformat.Why
GitHub Enterprise Cloud instances now follow a new API endpoint pattern (
https://api.<server>.ghe.com) instead of the traditionalhttps://<server>/api/v3. Thegithub3.pylibrary'sGitHubEnterpriseclass unconditionally appends/api/v3, making it impossible to use the new format without an override mechanism.Notes
session.base_urloverride onGitHubEnterpriseobjects reaches intogithub3.pyinternals. This is stable in the pinned 4.0.1 version. A full migration fromgithub3.pyto PyGithub will be considered in the near future, which would resolve this structurally since PyGithub acceptsbase_urlas a first-class parameter.GH_ENTERPRISE_API_URLrequiresGH_ENTERPRISE_URLto also be set, sincegheis used as a truthy flag throughout the codebase for enterprise mode detection.get_api_endpoint()helper inenv.pycentralizes the 7 previously duplicated endpoint construction patterns.Testing
get_api_endpoint()helper: all 3 branches (custom URL, GHE with/api/v3, defaultgithub.com) plus trailing slash stripping.auth_to_github():session.base_urloverride for both token auth and app auth paths.get_github_app_installation_token(): custom API URL used in installation token request.GH_ENTERPRISE_API_URLwithoutGH_ENTERPRISE_URLraisesValueError.is_dependabot_security_updates_enabled,enable_dependabot_security_updates,get_global_project_id, andlink_item_to_projectwith custom API URL.Readiness Checklist
Author/Contributor
make lintand fix any issues that you have introducedmake testand ensure you have test coverage for the lines you are introducing