Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support github enterprise api #155

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env-example
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
22 changes: 13 additions & 9 deletions auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 11 additions & 4 deletions cleanowners.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions env.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def get_env_vars(
int | None,
int | None,
bytes,
bool,
str | None,
str,
list[str],
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
64 changes: 44 additions & 20 deletions test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,74 @@
from unittest.mock import MagicMock, patch

import auth
import github3.github


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__":
Expand Down
26 changes: 26 additions & 0 deletions test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def test_get_env_vars_with_org(self):
None,
None,
b"",
False,
TOKEN,
"",
["repo4", "repo5"],
Expand Down Expand Up @@ -99,6 +100,7 @@ def test_get_env_vars_with_github_app_and_repos(self):
12345,
678910,
b"hello",
False,
"",
"",
["repo4", "repo5"],
Expand All @@ -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,
{
Expand All @@ -137,6 +160,7 @@ def test_get_env_vars_with_token_and_repos(self):
None,
None,
b"",
False,
TOKEN,
"",
["repo4", "repo5"],
Expand Down Expand Up @@ -171,6 +195,7 @@ def test_get_env_vars_optional_values(self):
None,
None,
b"",
False,
TOKEN,
"",
[],
Expand Down Expand Up @@ -220,6 +245,7 @@ def test_get_env_vars_with_repos_no_dry_run(self):
None,
None,
b"",
False,
TOKEN,
"",
[],
Expand Down
Loading