From 014b544a39457de4b8c8a2367cc1f6fd9676b959 Mon Sep 17 00:00:00 2001 From: jmeridth Date: Mon, 11 Mar 2024 10:23:41 -0500 Subject: [PATCH 01/11] chore: allow github app authentication - [x] add github app env variables to env.py and test - [x] setup requirements-test.txt for local testing - [x] change python-ci workflows to use new requirements-test.txt file - [x] add all possible env vars to .env-example, group and alphabetize - [x] purposefully clear out env vars before each env var test, just in case developer loaded .env into local shell manually (like I did) - [x] add flag to get_env_vars method to determine when to load .env file - [x] Update README with example and variable information Signed-off-by: jmeridth --- .env-example | 18 +++- .github/workflows/python-ci.yml | 3 +- README.md | 50 +++++++-- auth.py | 25 ++++- cleanowners.py | 10 +- env.py | 67 +++++++++++-- requirements-test.txt | 4 + requirements.txt | 2 - test_auth.py | 33 +++--- test_env.py | 173 +++++++++++++++++++++++++------- 10 files changed, 305 insertions(+), 80 deletions(-) create mode 100644 requirements-test.txt diff --git a/.env-example b/.env-example index dfb184d..6baf9c9 100644 --- a/.env-example +++ b/.env-example @@ -1,2 +1,16 @@ -GH_TOKEN = " " -ORGANIZATION = "organization" +DRY_RUN = "false" # true or false +EXEMPT_REPOS = "" # comma separated list of repositories to exempt +GH_ENTERPRISE_URL = "" +GH_TOKEN = "" +ORGANIZATION = "" +REPOSITORY = "" # comma separated list of repositories in the format org/repo + +# GITHUB APP +GH_APP_ID = "" +GH_INSTALLATION_ID = "" +GH_PRIVATE_KEY = "" + +# OPTIONAL SETTINGS +BODY = "" +COMMIT_MESSAGE = "" +TITLE = "" diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 336198d..b00ee6a 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -27,8 +27,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 pylint pytest pytest-cov - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install -r requirements.txt -r requirements-test.txt - name: Lint with flake8 and pylint run: | make lint diff --git a/README.md b/README.md index 1ef331b..57a4ddc 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,17 @@ If you need support using this project or have questions about it, please [open Below are the allowed configuration options: -| field | required | default | description | -|-----------------------|----------|---------|-------------| -| `GH_TOKEN` | True | "" | The GitHub Token used to scan the repository or organization. Must have write access to all repository you are interested in scanning so that an issue or pull request can be created. | -| `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | -| `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want this action to work from. ie. github.com/github would be `github` | -| `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want this action to work from. ie. `github/cleanowners` or a comma separated list of multiple repositories `github/cleanowners,super-linter/super-linter` | -| `EXEMPT_REPOS` | False | "" | These repositories will be exempt from this action. ex: If my org is set to `github` then I might want to exempt a few of the repos but get the rest by setting `EXEMPT_REPOS` to `github/cleanowners,github/contributors` | -| `DRY_RUN` | False | false | If set to true, this action will not create any pull requests. It will only log the repositories that could have the `CODEOWNERS` file updated. This is useful for testing or discovering the scope of this issue in your organization. | +| field | required | default | description | +|---------------------------|----------|---------|-------------| +| `GH_TOKEN` | True | "" | The GitHub Token used to scan the repository or organization. Must have write access to all repository you are interested in scanning so that an issue or pull request can be created. | +| `GH_APP_ID` | false | `""` | GitHub Application ID. | +| `GH_APP_INSTALLATION_ID` | false | `""` | GitHub Application Installation ID. | +| `GH_APP_PRIVATE_KEY` | false | `""` | GitHub Application Private Key | +| `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | +| `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want this action to work from. ie. github.com/github would be `github` | +| `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want this action to work from. ie. `github/cleanowners` or a comma separated list of multiple repositories `github/cleanowners,super-linter/super-linter` | +| `EXEMPT_REPOS` | False | "" | These repositories will be exempt from this action. ex: If my org is set to `github` then I might want to exempt a few of the repos but get the rest by setting `EXEMPT_REPOS` to `github/cleanowners,github/contributors` | +| `DRY_RUN` | False | false | If set to true, this action will not create any pull requests. It will only log the repositories that could have the `CODEOWNERS` file updated. This is useful for testing or discovering the scope of this issue in your organization. | ### Example workflows @@ -90,6 +93,37 @@ jobs: ``` +### Authenticating with a GitHub App and Installation + +You can authenticate as a GitHub App Installation by providing additional environment variables. If `GH_TOKEN` is set alongside these GitHub App Installation variables, the `GH_TOKEN` will be ignored and not used. + +```yaml +--- +name: Weekly codeowners cleanup via GitHub App +on: + workflow_dispatch: + schedule: + - cron: '3 2 1 * *' + +permissions: + issues: write + +jobs: + cleanowners: + name: cleanowners + runs-on: ubuntu-latest + + steps: + - name: Run cleanowners action + uses: github/cleanowners@v1 + env: + GH_APP_ID: ${{ secrets.GH_APP_ID }} + GH_APP_INSTALLATION_ID: ${{ secrets.GH_APP_INSTALLATION_ID }} + GH_APP_PRIVATE_KEY: ${{ secrets.GH_APP_PRIVATE_KEY }} + ORGANIZATION: + EXEMPT_REPOS: "org_name/repo_name_1, org_name/repo_name_2" +``` + ## Local usage without Docker 1. Make sure you have at least Python3.11 installed diff --git a/auth.py b/auth.py index 7d39446..ef0b4ee 100644 --- a/auth.py +++ b/auth.py @@ -3,24 +3,39 @@ import github3 -def auth_to_github(token: str, ghe: str) -> github3.GitHub: +def auth_to_github( + gh_app_id: str, + gh_app_installation_id: int, + gh_app_private_key_bytes: bytes, + token: str, + ghe: str +) -> 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 ghe (str): the GitHub Enterprise URL Returns: github3.GitHub: the GitHub connection object """ - if not token: - raise ValueError("GH_TOKEN environment variable not set") - if ghe: + if gh_app_id and gh_app_private_key_bytes and gh_app_installation_id: + 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) - else: + elif token: github_connection = github3.login(token=token) + else: + raise ValueError("GH_TOKEN environment variable not set") if not github_connection: raise ValueError("Unable to authenticate to GitHub") diff --git a/cleanowners.py b/cleanowners.py index f7fc22f..6f8be51 100644 --- a/cleanowners.py +++ b/cleanowners.py @@ -14,6 +14,9 @@ def main(): # pragma: no cover ( organization, repository_list, + gh_app_id, + gh_app_installation_id, + gh_app_private_key_bytes, token, ghe, exempt_repositories_list, @@ -24,7 +27,12 @@ def main(): # pragma: no cover ) = env.get_env_vars() # Auth to GitHub.com or GHE - github_connection = auth.auth_to_github(token, ghe) + github_connection = auth.auth_to_github( + gh_app_id, + gh_app_installation_id, + gh_app_private_key_bytes, + token, + ghe) pull_count = 0 eligble_for_pr_count = 0 no_codeowners_count = 0 diff --git a/env.py b/env.py index ce65aeb..2506440 100644 --- a/env.py +++ b/env.py @@ -8,18 +8,52 @@ from dotenv import load_dotenv -def get_env_vars() -> ( - tuple[str | None, list[str], str, str, list[str], bool, str, str, str] +def get_int_env_var(env_var_name: str) -> (int | None): + """Get an integer environment variable. + + Args: + env_var_name: The name of the environment variable to retrieve. + + Returns: + The value of the environment variable as an integer or None. + """ + env_var = os.environ.get(env_var_name) + if env_var is None or not env_var.strip(): + return None + try: + return int(env_var) + except ValueError: + return None + + +def get_env_vars(test=False) -> ( + tuple[ + str | None, + list[str], + int | None, + int | None, + bytes, + str, + str, + list[str], + bool, + str, + str, + str, + ] ): """ Get the environment variables for use in the action. Args: - None + test (bool): Whether or not to load the environment variables from a .env file (default: False) Returns: organization (str): The organization to search for repositories in repository_list (list[str]): A list of repositories to search for + gh_app_id (int): The GitHub App ID to use for authentication + gh_app_installation_id (int): 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 token (str): 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 @@ -29,9 +63,10 @@ def get_env_vars() -> ( message (str): Commit message to use """ - # Load from .env file if it exists - dotenv_path = join(dirname(__file__), ".env") - load_dotenv(dotenv_path) + if not test: + # Load from .env file if it exists + dotenv_path = join(dirname(__file__), ".env") + load_dotenv(dotenv_path) organization = os.getenv("ORGANIZATION") repositories_str = os.getenv("REPOSITORY") @@ -53,9 +88,22 @@ def get_env_vars() -> ( repository.strip() for repository in repositories_str.split(",") ] + 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") + + if gh_app_id and (not gh_app_private_key_bytes or not gh_app_installation_id): + raise ValueError( + "GH_APP_ID set and GH_APP_INSTALLATION_ID or GH_APP_PRIVATE_KEY variable not set" + ) + token = os.getenv("GH_TOKEN") - # required env variable - if not token: + if ( + not gh_app_id and + not gh_app_private_key_bytes and + not gh_app_installation_id and + not token + ): raise ValueError("GH_TOKEN environment variable not set") ghe = os.getenv("GH_ENTERPRISE_URL", default="").strip() @@ -110,6 +158,9 @@ def get_env_vars() -> ( return ( organization, repositories_list, + gh_app_id, + gh_app_installation_id, + gh_app_private_key_bytes, token, ghe, exempt_repositories_list, diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..d950ff9 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,4 @@ +flake8==7.0.0 +pylint==3.1.0 +pytest==8.1.1 +pytest-cov==4.1.0 diff --git a/requirements.txt b/requirements.txt index 66304ed..820c2d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,2 @@ github3.py==4.0.1 python-dotenv==1.0.1 -pytest==8.1.1 -pytest-cov==4.1.0 \ No newline at end of file diff --git a/test_auth.py b/test_auth.py index 9c8786c..0e307bc 100644 --- a/test_auth.py +++ b/test_auth.py @@ -1,8 +1,9 @@ """Test cases for the auth module.""" import unittest -from unittest.mock import patch +from unittest.mock import MagicMock, patch import auth +import github3.github class TestAuth(unittest.TestCase): @@ -10,16 +11,24 @@ class TestAuth(unittest.TestCase): Test case for the auth module. """ - @patch("github3.login") - def test_auth_to_github_with_token(self, mock_login): + @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 the token is provided. + Test the auth_to_github function when GitHub app + parameters provided. """ - mock_login.return_value = "Authenticated to GitHub.com" + mock_login.return_value = MagicMock() + result = auth.auth_to_github(12345, 678910, b"hello", "", "") + + self.assertIsInstance(result, github3.github.GitHub) - result = auth.auth_to_github("token", "") + def test_auth_to_github_with_token(self): + """ + Test the auth_to_github function when the token is provided. + """ + result = auth.auth_to_github(None, None, b"", "token", "") - self.assertEqual(result, "Authenticated to GitHub.com") + self.assertIsInstance(result, github3.github.GitHub) def test_auth_to_github_without_token(self): """ @@ -27,17 +36,15 @@ def test_auth_to_github_without_token(self): Expect a ValueError to be raised. """ with self.assertRaises(ValueError): - auth.auth_to_github("", "") + auth.auth_to_github(None, None, b"", "", "") - @patch("github3.github.GitHubEnterprise") - def test_auth_to_github_with_ghe(self, mock_ghe): + def test_auth_to_github_with_ghe(self): """ 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", "https://github.example.com") + result = auth.auth_to_github(None, None, b"", "token", "https://github.example.com") - self.assertEqual(result, "Authenticated to GitHub Enterprise") + self.assertIsInstance(result, github3.github.GitHubEnterprise) if __name__ == "__main__": diff --git a/test_env.py b/test_env.py index 28d3558..1f957d9 100644 --- a/test_env.py +++ b/test_env.py @@ -6,120 +6,215 @@ from env import get_env_vars +BODY = "Consider these updates to the CODEOWNERS file to remove users no longer in this organization." +COMMIT_MESSAGE = "Remove users no longer in this organization from CODEOWNERS file" +ORGANIZATION = "Organization01" +TITLE = "Clean up CODEOWNERS file" +TOKEN = "Token01" + class TestEnv(unittest.TestCase): """Test the get_env_vars function""" + def setUp(self): + env_keys = [ + "BODY", + "COMMIT_MESSAGE", + "DRY_RUN", + "EXEMPT_REPOS", + "GH_APP_ID", + "GH_ENTERPRISE_URL", + "GH_APP_INSTALLATION_ID", + "GH_APP_PRIVATE_KEY", + "GH_TOKEN", + "ORGANIZATION", + "REPOSITORY", + "TITLE" + ] + for key in env_keys: + if key in os.environ: + del os.environ[key] + @patch.dict( os.environ, { - "ORGANIZATION": "my_organization", - "GH_TOKEN": "my_token", + "BODY": BODY, + "COMMIT_MESSAGE": COMMIT_MESSAGE, + "DRY_RUN": "false", "EXEMPT_REPOS": "repo4,repo5", - "DRY_RUN": "False", - "TITLE": "Title01", - "BODY": "Body01", - "COMMIT_MESSAGE": "Commit01", + "GH_APP_ID": "", + "GH_ENTERPRISE_URL": "", + "GH_APP_INSTALLATION_ID": "", + "GH_APP_PRIVATE_KEY": "", + "GH_TOKEN": TOKEN, + "ORGANIZATION": ORGANIZATION, + "REPOSITORY": "org/repo1,org2/repo2", + "TITLE": TITLE, }, ) def test_get_env_vars_with_org(self): """Test that all environment variables are set correctly using an organization""" expected_result = ( - "my_organization", - [], - "my_token", + ORGANIZATION, + ["org/repo1", "org2/repo2"], + None, + None, + b"", + TOKEN, "", ["repo4", "repo5"], False, - "Title01", - "Body01", - "Commit01", + TITLE, + BODY, + COMMIT_MESSAGE, ) - result = get_env_vars() + result = get_env_vars(True) self.assertEqual(result, expected_result) @patch.dict( os.environ, { - "REPOSITORY": "org/repo1,org2/repo2", - "GH_TOKEN": "my_token", + "BODY": BODY, + "COMMIT_MESSAGE": COMMIT_MESSAGE, + "DRY_RUN": "true", "EXEMPT_REPOS": "repo4,repo5", + "GH_APP_ID": "12345", + "GH_ENTERPRISE_URL": "", + "GH_APP_INSTALLATION_ID": "678910", + "GH_APP_PRIVATE_KEY": "hello", + "GH_TOKEN": "", + "ORGANIZATION": "", + "REPOSITORY": "org/repo1,org2/repo2", + "TITLE": TITLE, + }, + clear=True, + ) + def test_get_env_vars_with_github_app_and_repos(self): + """Test that all environment variables are set correctly using a list of repositories""" + expected_result = ( + "", + ["org/repo1", "org2/repo2"], + 12345, + 678910, + b"hello", + "", + "", + ["repo4", "repo5"], + True, + TITLE, + BODY, + COMMIT_MESSAGE, + ) + result = get_env_vars(True) + self.assertEqual(result, expected_result) + + @patch.dict( + os.environ, + { + "BODY": BODY, + "COMMIT_MESSAGE": COMMIT_MESSAGE, "DRY_RUN": "true", + "EXEMPT_REPOS": "repo4,repo5", + "GH_APP_ID": "", + "GH_ENTERPRISE_URL": "", + "GH_APP_INSTALLATION_ID": "", + "GH_APP_PRIVATE_KEY": "", + "GH_TOKEN": TOKEN, + "ORGANIZATION": "", + "REPOSITORY": "org/repo1,org2/repo2", + "TITLE": TITLE, }, clear=True, ) - def test_get_env_vars_with_repos(self): + def test_get_env_vars_with_token_and_repos(self): """Test that all environment variables are set correctly using a list of repositories""" expected_result = ( - None, + "", ["org/repo1", "org2/repo2"], - "my_token", + None, + None, + b"", + TOKEN, "", ["repo4", "repo5"], True, - "Clean up CODEOWNERS file", - "Consider these updates to the CODEOWNERS file to remove users no longer in this organization.", - "Remove users no longer in this organization from CODEOWNERS file", + TITLE, + BODY, + COMMIT_MESSAGE, ) - result = get_env_vars() + result = get_env_vars(True) self.assertEqual(result, expected_result) @patch.dict( os.environ, { - "ORGANIZATION": "my_organization", - "GH_TOKEN": "my_token", + "BODY": BODY, + "COMMIT_MESSAGE": COMMIT_MESSAGE, + "GH_APP_ID": "", + "GH_APP_INSTALLATION_ID": "", + "GH_APP_PRIVATE_KEY": "", + "GH_TOKEN": TOKEN, + "ORGANIZATION": ORGANIZATION, + "TITLE": TITLE, }, ) def test_get_env_vars_optional_values(self): """Test that optional values are set to their default values if not provided""" expected_result = ( - "my_organization", + ORGANIZATION, [], - "my_token", + None, + None, + b"", + TOKEN, "", [], False, - "Clean up CODEOWNERS file", - "Consider these updates to the CODEOWNERS file to remove users no longer in this organization.", - "Remove users no longer in this organization from CODEOWNERS file", + TITLE, + BODY, + COMMIT_MESSAGE, ) - result = get_env_vars() + result = get_env_vars(True) self.assertEqual(result, expected_result) @patch.dict(os.environ, {}) def test_get_env_vars_missing_org_or_repo(self): """Test that an error is raised if required environment variables are not set""" with self.assertRaises(ValueError): - get_env_vars() + get_env_vars(True) @patch.dict( os.environ, { - "ORGANIZATION": "my_organization", + "ORGANIZATION": ORGANIZATION, }, clear=True, ) def test_get_env_vars_missing_token(self): """Test that an error is raised if required environment variables are not set""" with self.assertRaises(ValueError): - get_env_vars() + get_env_vars(True) @patch.dict( os.environ, { - "ORGANIZATION": "my_organization", - "GH_TOKEN": "my_token", - "DRY_RUN": "false", + "GH_APP_ID": "", + "GH_APP_INSTALLATION_ID": "", + "GH_APP_PRIVATE_KEY": "", + "GH_TOKEN": TOKEN, + "ORGANIZATION": ORGANIZATION, }, clear=True, ) def test_get_env_vars_with_repos_no_dry_run(self): """Test that all environment variables are set correctly when DRY_RUN is false""" expected_result = ( - "my_organization", + ORGANIZATION, [], - "my_token", + None, + None, + b"", + TOKEN, "", [], False, @@ -127,7 +222,7 @@ def test_get_env_vars_with_repos_no_dry_run(self): "Consider these updates to the CODEOWNERS file to remove users no longer in this organization.", "Remove users no longer in this organization from CODEOWNERS file", ) - result = get_env_vars() + result = get_env_vars(True) self.assertEqual(result, expected_result) From 63a517c33a72eeeb279780fcd815d77f78a4ff81 Mon Sep 17 00:00:00 2001 From: jmeridth Date: Tue, 12 Mar 2024 13:34:01 -0500 Subject: [PATCH 02/11] fix: linting errors Signed-off-by: jmeridth --- auth.py | 10 +++++----- cleanowners.py | 7 ++----- env.py | 42 +++++++++++++++++++++--------------------- test_auth.py | 4 +++- test_env.py | 2 +- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/auth.py b/auth.py index ef0b4ee..33e52df 100644 --- a/auth.py +++ b/auth.py @@ -4,11 +4,11 @@ def auth_to_github( - gh_app_id: str, - gh_app_installation_id: int, - gh_app_private_key_bytes: bytes, - token: str, - ghe: str + gh_app_id: str, + gh_app_installation_id: int, + gh_app_private_key_bytes: bytes, + token: str, + ghe: str ) -> github3.GitHub: """ Connect to GitHub.com or GitHub Enterprise, depending on env variables. diff --git a/cleanowners.py b/cleanowners.py index 6f8be51..b5891ad 100644 --- a/cleanowners.py +++ b/cleanowners.py @@ -28,11 +28,8 @@ 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) + gh_app_id, gh_app_installation_id, gh_app_private_key_bytes, token, ghe + ) pull_count = 0 eligble_for_pr_count = 0 no_codeowners_count = 0 diff --git a/env.py b/env.py index 2506440..f8d57d4 100644 --- a/env.py +++ b/env.py @@ -8,7 +8,7 @@ from dotenv import load_dotenv -def get_int_env_var(env_var_name: str) -> (int | None): +def get_int_env_var(env_var_name: str) -> int | None: """Get an integer environment variable. Args: @@ -26,22 +26,22 @@ def get_int_env_var(env_var_name: str) -> (int | None): return None -def get_env_vars(test=False) -> ( - tuple[ - str | None, - list[str], - int | None, - int | None, - bytes, - str, - str, - list[str], - bool, - str, - str, - str, - ] -): +def get_env_vars( + test=False +) -> tuple[ + str | None, + list[str], + int | None, + int | None, + bytes, + str, + str, + list[str], + bool, + str, + str, + str, +]: """ Get the environment variables for use in the action. @@ -99,10 +99,10 @@ def get_env_vars(test=False) -> ( token = os.getenv("GH_TOKEN") if ( - not gh_app_id and - not gh_app_private_key_bytes and - not gh_app_installation_id and - not token + not gh_app_id + and not gh_app_private_key_bytes + and not gh_app_installation_id + and not token ): raise ValueError("GH_TOKEN environment variable not set") diff --git a/test_auth.py b/test_auth.py index 0e307bc..760cad4 100644 --- a/test_auth.py +++ b/test_auth.py @@ -42,7 +42,9 @@ def test_auth_to_github_with_ghe(self): """ Test the auth_to_github function when the GitHub Enterprise URL is provided. """ - result = auth.auth_to_github(None, None, b"", "token", "https://github.example.com") + result = auth.auth_to_github( + None, None, b"", "token", "https://github.example.com" + ) self.assertIsInstance(result, github3.github.GitHubEnterprise) diff --git a/test_env.py b/test_env.py index 1f957d9..264ea36 100644 --- a/test_env.py +++ b/test_env.py @@ -29,7 +29,7 @@ def setUp(self): "GH_TOKEN", "ORGANIZATION", "REPOSITORY", - "TITLE" + "TITLE", ] for key in env_keys: if key in os.environ: From 6b75709914021495404e7a028f402a1ede8986b4 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Tue, 12 Mar 2024 14:27:14 -0700 Subject: [PATCH 03/11] run black formmater Signed-off-by: Zack Koppert --- auth.py | 2 +- env.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/auth.py b/auth.py index 33e52df..1f8e460 100644 --- a/auth.py +++ b/auth.py @@ -8,7 +8,7 @@ def auth_to_github( gh_app_installation_id: int, gh_app_private_key_bytes: bytes, token: str, - ghe: str + ghe: str, ) -> github3.GitHub: """ Connect to GitHub.com or GitHub Enterprise, depending on env variables. diff --git a/env.py b/env.py index f8d57d4..45f4904 100644 --- a/env.py +++ b/env.py @@ -27,7 +27,7 @@ def get_int_env_var(env_var_name: str) -> int | None: def get_env_vars( - test=False + test=False, ) -> tuple[ str | None, list[str], From 9338ff645ace2875ee0f2b893e477975f49255b1 Mon Sep 17 00:00:00 2001 From: jmeridth Date: Tue, 12 Mar 2024 18:00:29 -0500 Subject: [PATCH 04/11] fix: linting error fix when using os.getenv it can return string or None. Changed token variable to be same type (str | None) Signed-off-by: jmeridth --- env.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/env.py b/env.py index 45f4904..38d2678 100644 --- a/env.py +++ b/env.py @@ -27,14 +27,14 @@ def get_int_env_var(env_var_name: str) -> int | None: def get_env_vars( - test=False, + test: bool = False ) -> tuple[ str | None, list[str], int | None, int | None, bytes, - str, + str | None, str, list[str], bool, @@ -49,12 +49,12 @@ def get_env_vars( test (bool): Whether or not to load the environment variables from a .env file (default: False) Returns: - organization (str): The organization to search for repositories in + organization (str | None): The organization to search for repositories in repository_list (list[str]): A list of repositories to search for - gh_app_id (int): The GitHub App ID to use for authentication - gh_app_installation_id (int): The GitHub App Installation ID to use for authentication + 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 - token (str): The GitHub token to use for authentication + 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 dry_run (bool): Whether or not to actually open issues/pull requests From 8b4258a9d1048802fcadd06b5d6d28db9456e9f1 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Wed, 13 Mar 2024 15:15:37 -0700 Subject: [PATCH 05/11] run black formatter Signed-off-by: Zack Koppert --- env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env.py b/env.py index 38d2678..6f36d98 100644 --- a/env.py +++ b/env.py @@ -27,7 +27,7 @@ def get_int_env_var(env_var_name: str) -> int | None: def get_env_vars( - test: bool = False + test: bool = False, ) -> tuple[ str | None, list[str], From 1dc386a5fd644a5fd3e9108554283004ef89b875 Mon Sep 17 00:00:00 2001 From: Jason Meridth Date: Thu, 14 Mar 2024 16:03:35 -0500 Subject: [PATCH 06/11] Update README.md Co-authored-by: Zack Koppert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57a4ddc..762a94e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Below are the allowed configuration options: | field | required | default | description | |---------------------------|----------|---------|-------------| | `GH_TOKEN` | True | "" | The GitHub Token used to scan the repository or organization. Must have write access to all repository you are interested in scanning so that an issue or pull request can be created. | -| `GH_APP_ID` | false | `""` | GitHub Application ID. | +| `GH_APP_ID` | False | `""` | 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` | false | `""` | GitHub Application Installation ID. | | `GH_APP_PRIVATE_KEY` | false | `""` | GitHub Application Private Key | | `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | From 08949c3f7c81ba34cbd65b31118dc63da42abac2 Mon Sep 17 00:00:00 2001 From: Jason Meridth Date: Thu, 14 Mar 2024 16:03:55 -0500 Subject: [PATCH 07/11] Update README.md Co-authored-by: Zack Koppert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 762a94e..5766c6b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Below are the allowed configuration options: | `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want this action to work from. ie. github.com/github would be `github` | | `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want this action to work from. ie. `github/cleanowners` or a comma separated list of multiple repositories `github/cleanowners,super-linter/super-linter` | | `EXEMPT_REPOS` | False | "" | These repositories will be exempt from this action. ex: If my org is set to `github` then I might want to exempt a few of the repos but get the rest by setting `EXEMPT_REPOS` to `github/cleanowners,github/contributors` | -| `DRY_RUN` | False | false | If set to true, this action will not create any pull requests. It will only log the repositories that could have the `CODEOWNERS` file updated. This is useful for testing or discovering the scope of this issue in your organization. | +| `DRY_RUN` | False | False | If set to true, this action will not create any pull requests. It will only log the repositories that could have the `CODEOWNERS` file updated. This is useful for testing or discovering the scope of this issue in your organization. | ### Example workflows From fe456807555f17d41317bee6feea6fe0415360cb Mon Sep 17 00:00:00 2001 From: Jason Meridth Date: Thu, 14 Mar 2024 16:04:13 -0500 Subject: [PATCH 08/11] Update auth.py Co-authored-by: Zack Koppert --- auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth.py b/auth.py index 1f8e460..5f23cdf 100644 --- a/auth.py +++ b/auth.py @@ -35,7 +35,7 @@ def auth_to_github( elif token: github_connection = github3.login(token=token) else: - raise ValueError("GH_TOKEN environment variable not set") + raise ValueError("GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set") if not github_connection: raise ValueError("Unable to authenticate to GitHub") From a4459bdd6e6fd94011251ce95d3aba16359d45cc Mon Sep 17 00:00:00 2001 From: Jason Meridth Date: Thu, 14 Mar 2024 16:04:24 -0500 Subject: [PATCH 09/11] Update README.md Co-authored-by: Zack Koppert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5766c6b..922cc71 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Below are the allowed configuration options: |---------------------------|----------|---------|-------------| | `GH_TOKEN` | True | "" | The GitHub Token used to scan the repository or organization. Must have write access to all repository you are interested in scanning so that an issue or pull request can be created. | | `GH_APP_ID` | False | `""` | 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` | false | `""` | GitHub Application Installation ID. | +| `GH_APP_INSTALLATION_ID` | False | `""` | 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` | false | `""` | GitHub Application Private Key | | `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | | `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want this action to work from. ie. github.com/github would be `github` | From 2ec464f614c3781bf5d2f48b00dc44d5774caa5c Mon Sep 17 00:00:00 2001 From: Jason Meridth Date: Thu, 14 Mar 2024 16:04:32 -0500 Subject: [PATCH 10/11] Update README.md Co-authored-by: Zack Koppert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 922cc71..103cf14 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Below are the allowed configuration options: | `GH_TOKEN` | True | "" | The GitHub Token used to scan the repository or organization. Must have write access to all repository you are interested in scanning so that an issue or pull request can be created. | | `GH_APP_ID` | False | `""` | 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` | False | `""` | 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` | false | `""` | GitHub Application Private Key | +| `GH_APP_PRIVATE_KEY` | False | `""` | 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. | | `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | | `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want this action to work from. ie. github.com/github would be `github` | | `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want this action to work from. ie. `github/cleanowners` or a comma separated list of multiple repositories `github/cleanowners,super-linter/super-linter` | From a6daac03f5053338b3d8e03bd69229b2fcc4e7ab Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Thu, 14 Mar 2024 16:46:13 -0700 Subject: [PATCH 11/11] fix: run black formmater Signed-off-by: Zack Koppert --- auth.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/auth.py b/auth.py index 5f23cdf..217d8a2 100644 --- a/auth.py +++ b/auth.py @@ -35,7 +35,9 @@ def auth_to_github( elif token: github_connection = github3.login(token=token) else: - raise ValueError("GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set") + raise ValueError( + "GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set" + ) if not github_connection: raise ValueError("Unable to authenticate to GitHub")