Skip to content

Commit

Permalink
Merge pull request #225 from ricardojdsilva87/feat/support-github-ent…
Browse files Browse the repository at this point in the history
…erprise-api

feat: support github enterprise api
  • Loading branch information
jmeridth authored Jan 6, 2025
2 parents f827303 + 6613b90 commit d6c4576
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 190 deletions.
1 change: 1 addition & 0 deletions .env-example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ ADDITIONAL_METRICS = ""
GH_APP_ID = ""
GH_APP_INSTALLATION_ID = ""
GH_APP_PRIVATE_KEY = ""
GITHUB_APP_ENTERPRISE_ONLY = ""
GH_ENTERPRISE_URL = ""
GH_TOKEN = ""
INACTIVE_DAYS = 365
Expand Down
2 changes: 2 additions & 0 deletions .github/linters/.isort.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[settings]
profile = black
known_third_party = github3,dateutil,dotenv
known_first_party = auth
5 changes: 4 additions & 1 deletion .github/linters/.python-lint
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,10 @@ disable=raw-checker-failed,
use-symbolic-message-instead,
use-implicit-booleaness-not-comparison-to-string,
use-implicit-booleaness-not-comparison-to-zero,
import-error
import-error,
line-too-long,
too-many-arguments,
too-many-positional-arguments

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ All feedback regarding our GitHub Actions, as a whole, should be communicated th
## Use as a GitHub Action

1. Create a repository to host this GitHub Action or select an existing repository.
1. Create the env values from the sample workflow below (GH_TOKEN, ORGANIZATION, EXEMPT_TOPICS) with your information as plain text or repository secrets. More info on creating secrets can be found [here](https://docs.github.com/en/actions/security-guides/encrypted-secrets).
1. Create the env values from the sample workflow below (`GH_TOKEN`, `ORGANIZATION`, `EXEMPT_TOPICS`) with your information as plain text or repository secrets. More info on creating secrets can be found [here](https://docs.github.com/en/actions/security-guides/encrypted-secrets).
Note: Your GitHub token will need to have read access to all the repositories in the organization that you want evaluated
1. Copy the below example workflow to your repository and put it in the `.github/workflows/` directory with the file extension `.yml` (ie. `.github/workflows/stale_repos.yml`)

Expand All @@ -45,11 +45,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 Expand Up @@ -243,6 +244,7 @@ jobs:
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 }}
#GITHUB_APP_ENTERPRISE_ONLY: true --> Set this if the gh app was created in GHE and the endpoint is also a GHE instance
ORGANIZATION: ${{ secrets.ORGANIZATION }}
EXEMPT_TOPICS: "keep,template"
INACTIVE_DAYS: 365
Expand Down
48 changes: 48 additions & 0 deletions auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""This is the module that contains functions related to authenticating to GitHub with a personal access token."""

import github3


def auth_to_github(
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:
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:
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(url=ghe, token=token)
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"
)

if not github_connection:
raise ValueError("Unable to authenticate to GitHub")
return github_connection # type: ignore
47 changes: 19 additions & 28 deletions stale_repos.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python
""" Find stale repositories in a GitHub organization. """

import fnmatch
import json
import os
Expand All @@ -10,6 +11,8 @@
from dateutil.parser import parse
from dotenv import load_dotenv

import auth


def main(): # pragma: no cover
"""
Expand All @@ -32,8 +35,22 @@ def main(): # pragma: no cover
dotenv_path = join(dirname(__file__), ".env")
load_dotenv(dotenv_path)

# Auth to GitHub.com
github_connection = auth_to_github()
token = os.getenv("ORGANIZATION")
gh_app_id = os.getenv("GH_APP_ID")
gh_app_installation_id = os.getenv("GH_APP_INSTALLATION_ID")
gh_app_private_key = os.getenv("GH_APP_PRIVATE_KEY").encode("utf8")
ghe = os.getenv("GH_ENTERPRISE_URL")
gh_app_enterprise_only = os.getenv("GITHUB_APP_ENTERPRISE_ONLY")

# Auth to GitHub.com or GHE
github_connection = auth.auth_to_github(
token,
gh_app_id,
gh_app_installation_id,
gh_app_private_key,
ghe,
gh_app_enterprise_only,
)

# Set the threshold for inactive days
inactive_days_threshold = os.getenv("INACTIVE_DAYS")
Expand Down Expand Up @@ -346,32 +363,6 @@ def get_int_env_var(env_var_name):
return None


def auth_to_github():
"""Connect to GitHub.com or GitHub Enterprise, depending on env variables."""
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")
ghe = os.getenv("GH_ENTERPRISE_URL", default="").strip()
token = os.getenv("GH_TOKEN")

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)
elif token:
github_connection = github3.login(token=os.getenv("GH_TOKEN"))
else:
raise ValueError("GH_TOKEN environment variable not set")

if not github_connection:
raise ValueError("Unable to authenticate to GitHub")
return github_connection # type: ignore


def set_repo_data(
repo, days_inactive, active_date_disp, visibility, additional_metrics
):
Expand Down
78 changes: 78 additions & 0 deletions test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Test cases for the auth module."""

import unittest
from unittest.mock import MagicMock, patch

import auth


class TestAuth(unittest.TestCase):
"""
Test case for the auth module.
"""

@patch("github3.login")
def test_auth_to_github_with_token(self, mock_login):
"""
Test the auth_to_github function when the token is provided.
"""
mock_login.return_value = "Authenticated to GitHub.com"

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) 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",
)

@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(
"", "123", "123", b"123", "https://github.example.com", True
)
mock.login_as_app_installation.assert_called_once()
self.assertEqual(result, mock)

@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__":
unittest.main()
Loading

0 comments on commit d6c4576

Please sign in to comment.