Repository: relekang/python-semantic-release (python)

Issue:
support github enterprise server (GHE) in CI
## Description

It looks like `hvcs_domain` configuration seems to almost solve this but the same value is being used for the api and domain urls which is a problem.

https://github.com/relekang/python-semantic-release/blob/83798b7bf94569014e00a5f030ac46a5d4992fff/semantic_release/hvcs.py#L97-L104

https://github.com/relekang/python-semantic-release/blob/83798b7bf94569014e00a5f030ac46a5d4992fff/semantic_release/hvcs.py#L107-L116

Instead, it would be better to have a separate config value for api domain to use with custom domains.

Also, to provide built-in support for Github CI and Github enterprise server CI, it would be best to get these values from environment variables (without having to set `hvcs_domain` for enterprise CIs).

GITHUB_SERVER_URL | Returns the URL of the GitHub server. For example: https://github.com.
-- | --
GITHUB_API_URL | Returns the API URL. For example: https://api.github.com.

[Github CI Docs ref](https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables)


## Use cases

- Can be used in GHE CI and GH CI

## Possible implementation

- To support custom domains, a new configuration variable called `hvcs_api_domain` can be used and then the `api_url` function can be changed to: 

```python
class Github(Base):
    # rest of the code

    @staticmethod
    def api_url() -> str:
        """Github api_url property
        :return: The Github API URL
        """
        # not necessarily prefixed with api in the case of a custom domain, so
        # can't just default DEFAULT_DOMAIN to github.com
        hvcs_api_domain = config.get("hvcs_api_domain")
        hostname = hvcs_api_domain if hvcs_api_domain else "api." + Github.DEFAULT_DOMAIN
        return f"https://{hostname}"

    # rest of the code
```

- To support Github and GHE CI, the `domain` and `api_url` methods in Github class can be modified as:

```python
class Github(Base):
    """Github helper class"""

    DEFAULT_DOMAIN = "github.com"
    _fix_mime_types()

    @staticmethod
    def domain() -> str:
        """Github domain property
        :return: The Github domain
        """
        hvcs_domain = config.get("hvcs_domain", os.getenv("GITHUB_SERVER_URL", "").replace("https://", ""))
        domain = hvcs_domain if hvcs_domain else Github.DEFAULT_DOMAIN
        return domain

    @staticmethod
    def api_url() -> str:
        """Github api_url property
        :return: The Github API URL
        """
        # not necessarily prefixed with api in the case of a custom domain, so
        # can't just default DEFAULT_DOMAIN to github.com
        hvcs_api_domain = config.get("hvcs_api_domain", os.getenv("GITHUB_API_URL", "").replace("https://", ""))
        hostname = hvcs_api_domain if hvcs_api_domain else "api." + Github.DEFAULT_DOMAIN
        return f"https://{hostname}"

    ... # rest of the code
```


Pull request description:
feat: support github enterprise server
fixes #378 

Patch:
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 9dd93e2..3cd1ad4 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -468,6 +468,19 @@ The name of your hvcs. Currently only ``github`` and ``gitlab`` are supported.
 
 Default: `github`
 
+.. _config-hvcs_domain:
+
+``hvcs_domain``
+---------------
+The domain url (without https://) of your custom vcs server.
+
+.. _config-hvcs_api_domain:
+
+``hvcs_api_domain``
+-------------------
+The api url (without https://) of your custom vcs server.
+
+
 .. _config-check_build_status:
 
 ``check_build_status``
diff --git a/semantic_release/hvcs.py b/semantic_release/hvcs.py
index 6e63a6f..150f0a0 100644
--- a/semantic_release/hvcs.py
+++ b/semantic_release/hvcs.py
@@ -101,7 +101,10 @@ class Github(Base):
 
         :return: The Github domain
         """
-        hvcs_domain = config.get("hvcs_domain")
+        # ref: https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables
+        hvcs_domain = config.get(
+            "hvcs_domain", os.getenv("GITHUB_SERVER_URL", "").replace("https://", "")
+        )
         domain = hvcs_domain if hvcs_domain else Github.DEFAULT_DOMAIN
         return domain
 
@@ -113,8 +116,13 @@ class Github(Base):
         """
         # not necessarily prefixed with api in the case of a custom domain, so
         # can't just default DEFAULT_DOMAIN to github.com
-        hvcs_domain = config.get("hvcs_domain")
-        hostname = hvcs_domain if hvcs_domain else "api." + Github.DEFAULT_DOMAIN
+        # ref: https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables
+        hvcs_api_domain = config.get(
+            "hvcs_api_domain", os.getenv("GITHUB_API_URL", "").replace("https://", "")
+        )
+        hostname = (
+            hvcs_api_domain if hvcs_api_domain else "api." + Github.DEFAULT_DOMAIN
+        )
         return f"https://{hostname}"
 
     @staticmethod


Test patch:
diff --git a/tests/test_hvcs.py b/tests/test_hvcs.py
index cd0a53a..b95c35a 100644
--- a/tests/test_hvcs.py
+++ b/tests/test_hvcs.py
@@ -99,16 +99,17 @@ def test_check_custom_gitlab_token_should_return_false():
 
 
 @pytest.mark.parametrize(
-    "hvcs,hvcs_domain,expected_domain,api_url,ci_server_host",
+    "hvcs,hvcs_domain,expected_domain,api_url,ci_server_host,hvcs_api_domain",
     [
-        ("github", None, "github.com", "https://api.github.com", None),
-        ("gitlab", None, "gitlab.com", "https://gitlab.com", None),
+        ("github", None, "github.com", "https://api.github.com", None, None),
+        ("gitlab", None, "gitlab.com", "https://gitlab.com", None, None),
         (
             "github",
             "github.example.com",
             "github.example.com",
-            "https://github.example.com",
+            "https://api.github.enterprise",
             None,
+            "api.github.enterprise",
         ),
         (
             "gitlab",
@@ -116,6 +117,7 @@ def test_check_custom_gitlab_token_should_return_false():
             "example.gitlab.com",
             "https://example.gitlab.com",
             None,
+            None,
         ),
         (
             "gitlab",
@@ -123,6 +125,7 @@ def test_check_custom_gitlab_token_should_return_false():
             "example.gitlab.com/path",
             "https://example.gitlab.com/path",
             None,
+            None,
         ),
         (
             "gitlab",
@@ -130,17 +133,20 @@ def test_check_custom_gitlab_token_should_return_false():
             "example2.gitlab.com",
             "https://example2.gitlab.com",
             "ciserverhost.gitlab.com",
+            None,
         ),
     ],
 )
 @mock.patch("os.environ", {"GL_TOKEN": "token"})
 def test_get_domain_should_have_expected_domain(
-    hvcs, hvcs_domain, expected_domain, api_url, ci_server_host
+    hvcs, hvcs_domain, expected_domain, api_url, ci_server_host, hvcs_api_domain
 ):
 
     with mock.patch(
         "semantic_release.hvcs.config.get",
-        wrapped_config_get(hvcs_domain=hvcs_domain, hvcs=hvcs),
+        wrapped_config_get(
+            hvcs_domain=hvcs_domain, hvcs=hvcs, hvcs_api_domain=hvcs_api_domain
+        ),
     ):
         with mock.patch(
             "os.environ",
@@ -155,6 +161,19 @@ def test_get_domain_should_have_expected_domain(
             assert get_hvcs().api_url() == api_url
 
 
+@mock.patch("semantic_release.hvcs.config.get", wrapped_config_get(hvcs="github"))
+@mock.patch(
+    "os.environ",
+    {
+        "GITHUB_API_URL": "api.github.enterprise",
+        "GITHUB_SERVER_URL": "github.enterprise",
+    },
+)
+def test_ghe_domain_should_be_retrieved_from_env():
+    assert get_hvcs().domain() == "github.enterprise"
+    assert get_hvcs().api_url() == "https://api.github.enterprise"
+
+
 @mock.patch("semantic_release.hvcs.config.get", wrapped_config_get(hvcs="gitlab"))
 @mock.patch("os.environ", {"GL_TOKEN": "token"})
 def test_get_token():