From d4300ab13161c84d43faf8400b4454e287cae600 Mon Sep 17 00:00:00 2001 From: ricardojdsilva87 Date: Tue, 29 Oct 2024 11:18:29 +0000 Subject: [PATCH 1/3] feat: support github enterprise api --- auth.py | 22 ++++++++++------- cleanowners.py | 15 ++++++++---- env.py | 4 ++++ test_auth.py | 64 ++++++++++++++++++++++++++++++++++---------------- test_env.py | 26 ++++++++++++++++++++ 5 files changed, 98 insertions(+), 33 deletions(-) diff --git a/auth.py b/auth.py index 217d8a2..b472ade 100644 --- a/auth.py +++ b/auth.py @@ -4,34 +4,38 @@ def auth_to_github( - gh_app_id: str, - gh_app_installation_id: int, - gh_app_private_key_bytes: bytes, token: str, + gh_app_id: int | None, + gh_app_installation_id: int | None, + gh_app_private_key_bytes: bytes, ghe: str, + gh_app_enterprise_only: bool, ) -> github3.GitHub: """ Connect to GitHub.com or GitHub Enterprise, depending on env variables. Args: - gh_app_id (str): the GitHub App ID - gh_installation_id (int): the GitHub App Installation ID - gh_app_private_key (bytes): the GitHub App Private Key token (str): the GitHub personal access token + gh_app_id (int | None): the GitHub App ID + gh_app_installation_id (int | None): the GitHub App Installation ID + gh_app_private_key_bytes (bytes): the GitHub App Private Key ghe (str): the GitHub Enterprise URL + 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 Returns: github3.GitHub: the GitHub connection object """ - if gh_app_id and gh_app_private_key_bytes and gh_app_installation_id: - gh = github3.github.GitHub() + if ghe and gh_app_enterprise_only: + gh = github3.github.GitHubEnterprise(url=ghe) + else: + gh = github3.github.GitHub() gh.login_as_app_installation( gh_app_private_key_bytes, gh_app_id, gh_app_installation_id ) github_connection = gh elif ghe and token: - github_connection = github3.github.GitHubEnterprise(ghe, token=token) + github_connection = github3.github.GitHubEnterprise(url=ghe, token=token) elif token: github_connection = github3.login(token=token) else: diff --git a/cleanowners.py b/cleanowners.py index d7410a5..bc9e1b1 100644 --- a/cleanowners.py +++ b/cleanowners.py @@ -27,6 +27,7 @@ def main(): # pragma: no cover gh_app_id, gh_app_installation_id, gh_app_private_key_bytes, + gh_app_enterprise_only, token, ghe, exempt_repositories_list, @@ -39,8 +40,14 @@ def main(): # pragma: no cover # Auth to GitHub.com or GHE github_connection = auth.auth_to_github( - gh_app_id, gh_app_installation_id, gh_app_private_key_bytes, token, ghe + token, + gh_app_id, + gh_app_installation_id, + gh_app_private_key_bytes, + ghe, + gh_app_enterprise_only, ) + pull_count = 0 eligble_for_pr_count = 0 no_codeowners_count = 0 @@ -240,12 +247,12 @@ def commit_changes( commit_message, codeowners_filepath, ): - """Commit the changes to the repo and open a pull reques and return the pull request object""" + """Commit the changes to the repo and open a pull request and return the pull request object""" default_branch = repo.default_branch # Get latest commit sha from default branch - default_branch_commit = repo.ref("heads/" + default_branch).object.sha + default_branch_commit = repo.ref(f"heads/{default_branch}").object.sha front_matter = "refs/heads/" - branch_name = "codeowners-" + str(uuid.uuid4()) + branch_name = f"codeowners-{str(uuid.uuid4())}" repo.create_ref(front_matter + branch_name, default_branch_commit) repo.file_contents(codeowners_filepath).update( message=commit_message, diff --git a/env.py b/env.py index cc6d6b9..136ec0d 100644 --- a/env.py +++ b/env.py @@ -50,6 +50,7 @@ def get_env_vars( int | None, int | None, bytes, + bool, str | None, str, list[str], @@ -71,6 +72,7 @@ def get_env_vars( gh_app_id (int | None): The GitHub App ID to use for authentication gh_app_installation_id (int | None): The GitHub App Installation ID to use for authentication gh_app_private_key_bytes (bytes): The GitHub App Private Key as bytes to use for authentication + 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 token (str | None): The GitHub token to use for authentication ghe (str): The GitHub Enterprise URL to use for authentication exempt_repositories_list (list[str]): A list of repositories to exempt from the action @@ -109,6 +111,7 @@ def get_env_vars( gh_app_id = get_int_env_var("GH_APP_ID") gh_app_private_key_bytes = os.environ.get("GH_APP_PRIVATE_KEY", "").encode("utf8") gh_app_installation_id = get_int_env_var("GH_APP_INSTALLATION_ID") + gh_app_enterprise_only = get_bool_env_var("GITHUB_APP_ENTERPRISE_ONLY") if gh_app_id and (not gh_app_private_key_bytes or not gh_app_installation_id): raise ValueError( @@ -174,6 +177,7 @@ def get_env_vars( gh_app_id, gh_app_installation_id, gh_app_private_key_bytes, + gh_app_enterprise_only, token, ghe, exempt_repositories_list, diff --git a/test_auth.py b/test_auth.py index bcf9a71..6133859 100644 --- a/test_auth.py +++ b/test_auth.py @@ -4,7 +4,6 @@ from unittest.mock import MagicMock, patch import auth -import github3.github class TestAuth(unittest.TestCase): @@ -12,42 +11,67 @@ class TestAuth(unittest.TestCase): Test case for the auth module. """ - @patch("github3.github.GitHub.login_as_app_installation") - def test_auth_to_github_with_github_app(self, mock_login): - """ - Test the auth_to_github function when GitHub app - parameters provided. - """ - mock_login.return_value = MagicMock() - result = auth.auth_to_github(12345, 678910, b"hello", "", "") - - self.assertIsInstance(result, github3.github.GitHub) - - def test_auth_to_github_with_token(self): + @patch("github3.login") + def test_auth_to_github_with_token(self, mock_login): """ Test the auth_to_github function when the token is provided. """ - result = auth.auth_to_github(None, None, b"", "token", "") + mock_login.return_value = "Authenticated to GitHub.com" - self.assertIsInstance(result, github3.github.GitHub) + result = auth.auth_to_github("token", "", "", b"", "", False) + + self.assertEqual(result, "Authenticated to GitHub.com") def test_auth_to_github_without_token(self): """ Test the auth_to_github function when the token is not provided. Expect a ValueError to be raised. """ - with self.assertRaises(ValueError): - auth.auth_to_github(None, None, b"", "", "") + with self.assertRaises(ValueError) as context_manager: + auth.auth_to_github("", "", "", b"", "", False) + the_exception = context_manager.exception + self.assertEqual( + str(the_exception), + "GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set", + ) - def test_auth_to_github_with_ghe(self): + @patch("github3.github.GitHubEnterprise") + def test_auth_to_github_with_ghe(self, mock_ghe): """ Test the auth_to_github function when the GitHub Enterprise URL is provided. """ + mock_ghe.return_value = "Authenticated to GitHub Enterprise" + result = auth.auth_to_github( + "token", "", "", b"", "https://github.example.com", False + ) + + self.assertEqual(result, "Authenticated to GitHub Enterprise") + + @patch("github3.github.GitHubEnterprise") + def test_auth_to_github_with_ghe_and_ghe_app(self, mock_ghe): + """ + Test the auth_to_github function when the GitHub Enterprise URL is provided and the app was created in GitHub Enterprise URL. + """ + mock = mock_ghe.return_value + mock.login_as_app_installation = MagicMock(return_value=True) result = auth.auth_to_github( - None, None, b"", "token", "https://github.example.com" + "", "123", "123", b"123", "https://github.example.com", True ) + mock.login_as_app_installation.assert_called_once() + self.assertEqual(result, mock) - self.assertIsInstance(result, github3.github.GitHubEnterprise) + @patch("github3.github.GitHub") + def test_auth_to_github_with_app(self, mock_gh): + """ + Test the auth_to_github function when app credentials are provided + """ + mock = mock_gh.return_value + mock.login_as_app_installation = MagicMock(return_value=True) + result = auth.auth_to_github( + "", "123", "123", b"123", "https://github.example.com", False + ) + mock.login_as_app_installation.assert_called_once() + self.assertEqual(result, mock) if __name__ == "__main__": diff --git a/test_env.py b/test_env.py index 3d06b98..17f1b3f 100644 --- a/test_env.py +++ b/test_env.py @@ -61,6 +61,7 @@ def test_get_env_vars_with_org(self): None, None, b"", + False, TOKEN, "", ["repo4", "repo5"], @@ -99,6 +100,7 @@ def test_get_env_vars_with_github_app_and_repos(self): 12345, 678910, b"hello", + False, "", "", ["repo4", "repo5"], @@ -111,6 +113,27 @@ def test_get_env_vars_with_github_app_and_repos(self): result = get_env_vars(True) self.assertEqual(result, expected_result) + @patch.dict( + os.environ, + { + "ORGANIZATION": "my_organization", + "GH_APP_ID": "12345", + "GH_APP_INSTALLATION_ID": "", + "GH_APP_PRIVATE_KEY": "", + "GH_TOKEN": "", + }, + clear=True, + ) + def test_get_env_vars_auth_with_github_app_installation_missing_inputs(self): + """Test that an error is raised there are missing inputs for the gh app""" + with self.assertRaises(ValueError) as context_manager: + get_env_vars(True) + the_exception = context_manager.exception + self.assertEqual( + str(the_exception), + "GH_APP_ID set and GH_APP_INSTALLATION_ID or GH_APP_PRIVATE_KEY variable not set", + ) + @patch.dict( os.environ, { @@ -137,6 +160,7 @@ def test_get_env_vars_with_token_and_repos(self): None, None, b"", + False, TOKEN, "", ["repo4", "repo5"], @@ -171,6 +195,7 @@ def test_get_env_vars_optional_values(self): None, None, b"", + False, TOKEN, "", [], @@ -220,6 +245,7 @@ def test_get_env_vars_with_repos_no_dry_run(self): None, None, b"", + False, TOKEN, "", [], From 7930aaea1d02e5eabfecc75c2f6bc2fc1f7ac0e0 Mon Sep 17 00:00:00 2001 From: ricardojdsilva87 Date: Tue, 29 Oct 2024 11:24:30 +0000 Subject: [PATCH 2/3] feat: update README --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 17abd99..0089245 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,12 @@ This action can be configured to authenticate with GitHub App Installation or Pe ##### GitHub App Installation -| field | required | default | description | -| ------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `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. | -| `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. | -| `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. | +| field | required | default | description | +| ---------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `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. | +| `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. | +| `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. | +| `GITHUB_APP_ENTERPRISE_ONLY` | False | `false` | Set this input to `true` if your app is created in GHE and communicates with GHE. | ##### Personal Access Token (PAT) From e2d2cd3cc9f974e4773c176300a87bd7cedd3cac Mon Sep 17 00:00:00 2001 From: ricardojdsilva87 Date: Tue, 29 Oct 2024 16:46:44 +0000 Subject: [PATCH 3/3] fix: add GITHUB_APP_ENTERPRISE_ONLY to .env-example --- .env-example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env-example b/.env-example index 6baf9c9..b917f1e 100644 --- a/.env-example +++ b/.env-example @@ -9,6 +9,7 @@ REPOSITORY = "" # comma separated list of repositories in the format org/repo GH_APP_ID = "" GH_INSTALLATION_ID = "" GH_PRIVATE_KEY = "" +GITHUB_APP_ENTERPRISE_ONLY = "" # OPTIONAL SETTINGS BODY = ""