github-org-manager 0.5.2__tar.gz → 0.5.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: github-org-manager
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: Manage a GitHub Organization, its teams, repository permissions, and more
5
5
  Home-page: https://github.com/OpenRailAssociation/github-org-manager
6
6
  License: Apache-2.0
@@ -83,6 +83,20 @@ Inside [`config/example`](./config/example), you can find an example configurati
83
83
 
84
84
  You may also be interested in the [live configuration of the OpenRail Association's organization](https://github.com/OpenRailAssociation/openrail-org-config).
85
85
 
86
+ ### Authentication via token or app
87
+
88
+ As this tool issues many API requests (both on REST and GraphQL API), authentication is highly recommended. This is supported via personal access tokens of a user (PAT) or a GitHub App which you can setup yourself.
89
+
90
+ Access tokens and apps need the following permissions:
91
+ * Repository permissions
92
+ * Administration: read and write
93
+ * Metadata: read
94
+ * Organization permissions:
95
+ * Administration: read and write
96
+ * Members: read and write
97
+
98
+ You can set the required secrets in `config/app.yaml` or via environment variables (`GITHUB_TOKEN` or `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`).
99
+
86
100
  ## Run the program
87
101
 
88
102
  You can execute the program using the command `gh-org-mgr`. `gh-org-mgr --help` shows all available arguments and options.
@@ -54,6 +54,20 @@ Inside [`config/example`](./config/example), you can find an example configurati
54
54
 
55
55
  You may also be interested in the [live configuration of the OpenRail Association's organization](https://github.com/OpenRailAssociation/openrail-org-config).
56
56
 
57
+ ### Authentication via token or app
58
+
59
+ As this tool issues many API requests (both on REST and GraphQL API), authentication is highly recommended. This is supported via personal access tokens of a user (PAT) or a GitHub App which you can setup yourself.
60
+
61
+ Access tokens and apps need the following permissions:
62
+ * Repository permissions
63
+ * Administration: read and write
64
+ * Metadata: read
65
+ * Organization permissions:
66
+ * Administration: read and write
67
+ * Members: read and write
68
+
69
+ You can set the required secrets in `config/app.yaml` or via environment variables (`GITHUB_TOKEN` or `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`).
70
+
57
71
  ## Run the program
58
72
 
59
73
  You can execute the program using the command `gh-org-mgr`. `gh-org-mgr --help` shows all available arguments and options.
@@ -13,20 +13,15 @@ import sys
13
13
  import requests
14
14
 
15
15
 
16
- def get_github_token(token: str = "") -> str:
17
- """Get the GitHub token from config or environment, while environment overrides"""
18
- if "GITHUB_TOKEN" in os.environ and os.environ["GITHUB_TOKEN"]:
19
- logging.debug("GitHub Token taken from environment variable GITHUB_TOKEN")
20
- token = os.environ["GITHUB_TOKEN"]
21
- elif token:
22
- logging.debug("GitHub Token taken from app configuration file")
23
- else:
24
- sys.exit(
25
- "No token set for GitHub authentication! Set it in config/app_config.yaml "
26
- "or via environment variable GITHUB_TOKEN"
27
- )
28
-
29
- return token
16
+ def get_github_secrets_from_env(env_variable: str, secret: str | int) -> str:
17
+ """Get GitHub secrets from config or environment, while environment overrides"""
18
+ if env_variable in os.environ and os.environ[env_variable]:
19
+ logging.debug("GitHub secret taken from environment variable %s", env_variable)
20
+ secret = os.environ[env_variable]
21
+ elif secret:
22
+ logging.debug("GitHub secret taken from app configuration file")
23
+
24
+ return str(secret)
30
25
 
31
26
 
32
27
  # Function to execute GraphQL query
@@ -51,10 +46,12 @@ def run_graphql_query(query, variables, token):
51
46
  return json_return
52
47
 
53
48
  # Debug information in case of errors
54
- print(
55
- f"Query failed with HTTP error code '{request.status_code}' when running "
56
- f"this query: {query}\n"
57
- f"Return: {json_return}\n"
58
- f"Headers: {request.headers}"
49
+ logging.error(
50
+ "Query failed with HTTP error code '%s' when running this query: %s\n"
51
+ "Return: %s\nHeaders: %s",
52
+ request.status_code,
53
+ query,
54
+ json_return,
55
+ request.headers,
59
56
  )
60
57
  sys.exit(1)
@@ -8,13 +8,19 @@ import logging
8
8
  import sys
9
9
  from dataclasses import asdict, dataclass, field
10
10
 
11
- from github import Github, GithubException, UnknownObjectException
11
+ from github import (
12
+ Auth,
13
+ Github,
14
+ GithubException,
15
+ GithubIntegration,
16
+ UnknownObjectException,
17
+ )
12
18
  from github.NamedUser import NamedUser
13
19
  from github.Organization import Organization
14
20
  from github.Repository import Repository
15
21
  from github.Team import Team
16
22
 
17
- from ._gh_api import get_github_token, run_graphql_query
23
+ from ._gh_api import get_github_secrets_from_env, run_graphql_query
18
24
 
19
25
 
20
26
  @dataclass
@@ -24,6 +30,8 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
24
30
  gh: Github = None # type: ignore
25
31
  org: Organization = None # type: ignore
26
32
  gh_token: str = ""
33
+ gh_app_id: str | int = ""
34
+ gh_app_private_key: str = ""
27
35
  default_repository_permission: str = ""
28
36
  current_org_owners: list[NamedUser] = field(default_factory=list)
29
37
  configured_org_owners: list[str] = field(default_factory=list)
@@ -56,18 +64,39 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
56
64
  # supported, or multiple spaces etc.
57
65
  return team.replace(" ", "-")
58
66
 
59
- def login(self, orgname: str, token: str) -> None:
60
- """Login to GH, gather org data"""
61
- self.gh_token = get_github_token(token)
62
- self.gh = Github(self.gh_token)
63
- logging.debug("Logged in as %s", self.gh.get_user().login)
67
+ def login(
68
+ self, orgname: str, token: str = "", app_id: str | int = "", app_private_key: str = ""
69
+ ) -> None:
70
+ """Login to GH via PAT or App, gather org data"""
71
+ # Get all login data from config and environment
72
+ self.gh_token = get_github_secrets_from_env(env_variable="GITHUB_TOKEN", secret=token)
73
+ self.gh_app_id = get_github_secrets_from_env(env_variable="GITHUB_APP_ID", secret=app_id)
74
+ self.gh_app_private_key = get_github_secrets_from_env(
75
+ env_variable="GITHUB_APP_PRIVATE_KEY", secret=app_private_key
76
+ )
77
+
78
+ # Decide how to login. If app set, prefer this
79
+ if self.gh_app_id and self.gh_app_private_key:
80
+ logging.debug("Logged in via app %s", self.gh_app_id)
81
+ auth = Auth.AppAuth(app_id=self.gh_app_id, private_key=self.gh_app_private_key)
82
+ app = GithubIntegration(auth=auth)
83
+ installation = app.get_installations()[0]
84
+ self.gh = installation.get_github_for_installation()
85
+ elif self.gh_token:
86
+ logging.debug("Logging in as user with PAT")
87
+ self.gh = Github(auth=Auth.Token(self.gh_token))
88
+ logging.debug("Logged in as %s", self.gh.get_user().login)
89
+ else:
90
+ logging.error("No GitHub token or App ID+private key provided")
91
+ sys.exit(1)
92
+
64
93
  self.org = self.gh.get_organization(orgname)
65
94
  logging.debug("Gathered data from organization '%s' (%s)", self.org.login, self.org.name)
66
95
 
67
96
  def ratelimit(self):
68
- """Get current rate limit"""
97
+ """Print current rate limit"""
69
98
  core = self.gh.get_rate_limit().core
70
- logging.debug(
99
+ logging.info(
71
100
  "Current rate limit: %s/%s (reset: %s)", core.remaining, core.limit, core.reset
72
101
  )
73
102
 
@@ -917,7 +946,7 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
917
946
  permissions using the GraphQL API"""
918
947
  # TODO: Consider doing this for all repositories at once, but calculate
919
948
  # costs beforehand
920
- query = """
949
+ graphql_query = """
921
950
  query($owner: String!, $name: String!, $cursor: String) {
922
951
  repository(owner: $owner, name: $name) {
923
952
  collaborators(first: 100, after: $cursor) {
@@ -944,7 +973,7 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
944
973
 
945
974
  while has_next_page:
946
975
  logging.debug("Requesting collaborators for %s", repo.name)
947
- result = run_graphql_query(query, variables, self.gh_token)
976
+ result = run_graphql_query(graphql_query, variables, self.gh_token)
948
977
  try:
949
978
  collaborators.extend(result["data"]["repository"]["collaborators"]["edges"])
950
979
  has_next_page = result["data"]["repository"]["collaborators"]["pageInfo"][
@@ -4,7 +4,7 @@
4
4
 
5
5
  [tool.poetry]
6
6
  name = "github-org-manager"
7
- version = "0.5.2"
7
+ version = "0.5.3"
8
8
  description = "Manage a GitHub Organization, its teams, repository permissions, and more"
9
9
  authors = ["Max Mehl <max.mehl@deutschebahn.com>"]
10
10
  readme = "README.md"