github-org-manager 0.5.6__tar.gz → 0.6.0__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.
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/PKG-INFO +3 -4
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/README.md +1 -1
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/gh_org_mgr/_gh_org.py +65 -20
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/gh_org_mgr/manage.py +10 -2
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/pyproject.toml +4 -4
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/LICENSE.txt +0 -0
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/LICENSES/Apache-2.0.txt +0 -0
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/LICENSES/CC0-1.0.txt +0 -0
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/LICENSES/MIT.txt +0 -0
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/gh_org_mgr/__init__.py +0 -0
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/gh_org_mgr/_config.py +0 -0
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/gh_org_mgr/_gh_api.py +0 -0
- {github_org_manager-0.5.6 → github_org_manager-0.6.0}/gh_org_mgr/_setup_team.py +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: github-org-manager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Manage a GitHub Organization, its teams, repository permissions, and more
|
|
5
|
-
Home-page: https://github.com/OpenRailAssociation/github-org-manager
|
|
6
5
|
License: Apache-2.0
|
|
7
6
|
Keywords: github,github-management,permissions,access-control
|
|
8
7
|
Author: Max Mehl
|
|
@@ -77,7 +76,7 @@ Afterwards, the tool is executable with the command `gh-org-mgr`. The `--help` f
|
|
|
77
76
|
|
|
78
77
|
Inside [`config/example`](./config/example), you can find an example configuration that shall help you to understand the structure:
|
|
79
78
|
|
|
80
|
-
* `app.yaml`: Configuration necessary to run this tool
|
|
79
|
+
* `app.yaml`: Configuration necessary to run this tool and controlling some behaviour
|
|
81
80
|
* `org.yaml`: Organization-wide configuration
|
|
82
81
|
* `teams/*.yaml`: Configuration concerning the teams of your organization.
|
|
83
82
|
|
|
@@ -48,7 +48,7 @@ Afterwards, the tool is executable with the command `gh-org-mgr`. The `--help` f
|
|
|
48
48
|
|
|
49
49
|
Inside [`config/example`](./config/example), you can find an example configuration that shall help you to understand the structure:
|
|
50
50
|
|
|
51
|
-
* `app.yaml`: Configuration necessary to run this tool
|
|
51
|
+
* `app.yaml`: Configuration necessary to run this tool and controlling some behaviour
|
|
52
52
|
* `org.yaml`: Organization-wide configuration
|
|
53
53
|
* `teams/*.yaml`: Configuration concerning the teams of your organization.
|
|
54
54
|
|
|
@@ -15,10 +15,12 @@ from github import (
|
|
|
15
15
|
GithubIntegration,
|
|
16
16
|
UnknownObjectException,
|
|
17
17
|
)
|
|
18
|
+
from github.GithubException import BadCredentialsException
|
|
18
19
|
from github.NamedUser import NamedUser
|
|
19
20
|
from github.Organization import Organization
|
|
20
21
|
from github.Repository import Repository
|
|
21
22
|
from github.Team import Team
|
|
23
|
+
from jwt.exceptions import InvalidKeyError
|
|
22
24
|
|
|
23
25
|
from ._gh_api import get_github_secrets_from_env, run_graphql_query
|
|
24
26
|
|
|
@@ -65,6 +67,7 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
65
67
|
# supported, or multiple spaces etc.
|
|
66
68
|
return team.replace(" ", "-")
|
|
67
69
|
|
|
70
|
+
# amazonq-ignore-next-line
|
|
68
71
|
def login(
|
|
69
72
|
self, orgname: str, token: str = "", app_id: str | int = "", app_private_key: str = ""
|
|
70
73
|
) -> None:
|
|
@@ -81,7 +84,11 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
81
84
|
logging.debug("Logging in via app %s", self.gh_app_id)
|
|
82
85
|
auth = Auth.AppAuth(app_id=self.gh_app_id, private_key=self.gh_app_private_key)
|
|
83
86
|
app = GithubIntegration(auth=auth)
|
|
84
|
-
|
|
87
|
+
try:
|
|
88
|
+
installation = app.get_org_installation(org=orgname)
|
|
89
|
+
except InvalidKeyError:
|
|
90
|
+
logging.critical("Invalid private key provided for GitHub App")
|
|
91
|
+
sys.exit(1)
|
|
85
92
|
self.gh = installation.get_github_for_installation()
|
|
86
93
|
logging.debug("Logged in via app installation %s", installation.id)
|
|
87
94
|
|
|
@@ -90,7 +97,11 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
90
97
|
elif self.gh_token:
|
|
91
98
|
logging.debug("Logging in as user with PAT")
|
|
92
99
|
self.gh = Github(auth=Auth.Token(self.gh_token))
|
|
93
|
-
|
|
100
|
+
try:
|
|
101
|
+
logging.debug("Logged in as %s", self.gh.get_user().login)
|
|
102
|
+
except BadCredentialsException:
|
|
103
|
+
logging.critical("Invalid GitHub token provided")
|
|
104
|
+
sys.exit(1)
|
|
94
105
|
else:
|
|
95
106
|
logging.error("No GitHub token or App ID+private key provided")
|
|
96
107
|
sys.exit(1)
|
|
@@ -539,6 +550,10 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
539
550
|
open_invitations = [user.login.lower() for user in self.org.invitations()]
|
|
540
551
|
|
|
541
552
|
for team, team_attrs in self.current_teams.items():
|
|
553
|
+
# Ignore any team not being configured locally, will be handled later
|
|
554
|
+
if team.name not in self.configured_teams:
|
|
555
|
+
continue
|
|
556
|
+
|
|
542
557
|
# Update current team members with dict[NamedUser, str (role)]
|
|
543
558
|
team_attrs["members"] = self._get_current_team_members(team)
|
|
544
559
|
|
|
@@ -548,15 +563,6 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
548
563
|
user.login.lower(): role for user, role in team_attrs["members"].items()
|
|
549
564
|
}
|
|
550
565
|
|
|
551
|
-
# Handle the team not being configured locally
|
|
552
|
-
if team.name not in self.configured_teams:
|
|
553
|
-
logging.warning(
|
|
554
|
-
"Team '%s' does not seem to be configured locally. "
|
|
555
|
-
"Taking no action about this team at all",
|
|
556
|
-
team.name,
|
|
557
|
-
)
|
|
558
|
-
continue
|
|
559
|
-
|
|
560
566
|
# Get configuration from current team
|
|
561
567
|
if team_configuration := self.configured_teams.get(team.name):
|
|
562
568
|
pass
|
|
@@ -655,8 +661,35 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
655
661
|
team.name,
|
|
656
662
|
)
|
|
657
663
|
|
|
658
|
-
def
|
|
659
|
-
|
|
664
|
+
def get_unconfigured_teams(
|
|
665
|
+
self, dry: bool = False, delete_unconfigured_teams: bool = False
|
|
666
|
+
) -> None:
|
|
667
|
+
"""Get all teams that are not configured locally and optionally remove them"""
|
|
668
|
+
# Get all teams that are not configured locally
|
|
669
|
+
unconfigured_teams: list[Team] = []
|
|
670
|
+
for team in self.current_teams:
|
|
671
|
+
if team.name not in self.configured_teams:
|
|
672
|
+
unconfigured_teams.append(team)
|
|
673
|
+
|
|
674
|
+
if unconfigured_teams:
|
|
675
|
+
if delete_unconfigured_teams:
|
|
676
|
+
for team in unconfigured_teams:
|
|
677
|
+
logging.info("Deleting team '%s' as it is not configured locally", team.name)
|
|
678
|
+
if not dry:
|
|
679
|
+
team.delete()
|
|
680
|
+
else:
|
|
681
|
+
unconfigured_teams_str = [team.name for team in unconfigured_teams]
|
|
682
|
+
logging.warning(
|
|
683
|
+
"The following teams of your GitHub organisation are not "
|
|
684
|
+
"configured locally: %s. Taking no action about these teams.",
|
|
685
|
+
", ".join(unconfigured_teams_str),
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
def get_members_without_team(
|
|
689
|
+
self, dry: bool = False, remove_members_without_team: bool = False
|
|
690
|
+
) -> None:
|
|
691
|
+
"""Get all organisation members without any team membership, and
|
|
692
|
+
optionally remove them"""
|
|
660
693
|
# Combine org owners and org members
|
|
661
694
|
all_org_members = set(self.org_members + self.current_org_owners)
|
|
662
695
|
|
|
@@ -665,18 +698,30 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
|
|
|
665
698
|
for _, team_attrs in self.current_teams.items():
|
|
666
699
|
for member in team_attrs.get("members", {}):
|
|
667
700
|
all_team_members_lst.append(member)
|
|
668
|
-
# Also add users that have just been added to a team, and unify them
|
|
669
|
-
all_team_members: set[NamedUser] = set(
|
|
701
|
+
# Also add org owners and users that have just been added to a team, and unify them
|
|
702
|
+
all_team_members: set[NamedUser] = set(
|
|
703
|
+
all_team_members_lst + self.newly_added_users + self.current_org_owners
|
|
704
|
+
)
|
|
670
705
|
|
|
671
706
|
# Find members that are in org_members but not team_members
|
|
672
707
|
members_without_team = all_org_members.difference(all_team_members)
|
|
673
708
|
|
|
674
709
|
if members_without_team:
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
710
|
+
if remove_members_without_team:
|
|
711
|
+
for user in members_without_team:
|
|
712
|
+
logging.info(
|
|
713
|
+
"Removing user '%s' from organisation as they are not member of any team",
|
|
714
|
+
user.login,
|
|
715
|
+
)
|
|
716
|
+
if not dry:
|
|
717
|
+
self.org.remove_from_membership(user)
|
|
718
|
+
else:
|
|
719
|
+
members_without_team_str = [user.login for user in members_without_team]
|
|
720
|
+
logging.warning(
|
|
721
|
+
"The following members of your GitHub organisation are not "
|
|
722
|
+
"member of any team: %s",
|
|
723
|
+
", ".join(members_without_team_str),
|
|
724
|
+
)
|
|
680
725
|
|
|
681
726
|
# --------------------------------------------------------------------------
|
|
682
727
|
# Repos
|
|
@@ -129,8 +129,16 @@ def main():
|
|
|
129
129
|
org.sync_current_teams_settings(dry=args.dry)
|
|
130
130
|
# Synchronise the team memberships
|
|
131
131
|
org.sync_teams_members(dry=args.dry)
|
|
132
|
-
# Report
|
|
133
|
-
org.
|
|
132
|
+
# Report and act on teams that are not configured locally
|
|
133
|
+
org.get_unconfigured_teams(
|
|
134
|
+
dry=args.dry,
|
|
135
|
+
delete_unconfigured_teams=cfg_app.get("delete_unconfigured_teams", False),
|
|
136
|
+
)
|
|
137
|
+
# Report and act on organisation members that do not belong to any team
|
|
138
|
+
org.get_members_without_team(
|
|
139
|
+
dry=args.dry,
|
|
140
|
+
remove_members_without_team=cfg_app.get("remove_members_without_team", False),
|
|
141
|
+
)
|
|
134
142
|
# Synchronise the permissions of teams for all repositories
|
|
135
143
|
org.sync_repo_permissions(dry=args.dry, ignore_archived=args.ignore_archived)
|
|
136
144
|
# Remove individual collaborator permissions if they are higher than the one
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "github-org-manager"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
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"
|
|
@@ -33,13 +33,13 @@ requests = "^2.32.3"
|
|
|
33
33
|
python-slugify = "^8.0.4"
|
|
34
34
|
|
|
35
35
|
[tool.poetry.group.dev.dependencies]
|
|
36
|
-
black = "^
|
|
37
|
-
isort = "
|
|
36
|
+
black = "^25.1.0"
|
|
37
|
+
isort = ">=5.13.2,<7.0.0"
|
|
38
38
|
mypy = "^1.9.0"
|
|
39
39
|
pylint = "^3.1.0"
|
|
40
40
|
types-pyyaml = "^6.0.12.20240311"
|
|
41
41
|
types-requests = "^2.32.0.20240712"
|
|
42
|
-
bump-my-version = "^0.
|
|
42
|
+
bump-my-version = "^0.32.0"
|
|
43
43
|
|
|
44
44
|
[build-system]
|
|
45
45
|
requires = ["poetry-core"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|