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

Add options to remove unconfigured teams and members #80

Merged
merged 3 commits into from
Feb 20, 2025
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Afterwards, the tool is executable with the command `gh-org-mgr`. The `--help` f

Inside [`config/example`](./config/example), you can find an example configuration that shall help you to understand the structure:

* `app.yaml`: Configuration necessary to run this tool
* `app.yaml`: Configuration necessary to run this tool and controlling some behaviour
* `org.yaml`: Organization-wide configuration
* `teams/*.yaml`: Configuration concerning the teams of your organization.

Expand Down
7 changes: 7 additions & 0 deletions config/example/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,10 @@ github_app_private_key: |
D9zgrlJ8D4bxPrwDrCuXHY7s/1/uCX3K+mS7CWpybOcJY4XzsNznOYcMQzw22fxl
u1ioG/s3Ahhd778VIjj5d32Xbjj8vbSFj8vJe5bBNblYbelWfETg
-----END RSA PRIVATE KEY-----

# Remove members from organisation who are not member of any team or
# organisation owners. Default: false
remove_members_without_team: false

# Delete teams that are not configured. Default: false
delete_unconfigured_teams: false
64 changes: 48 additions & 16 deletions gh_org_mgr/_gh_org.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,10 @@ def sync_teams_members(self, dry: bool = False) -> None: # pylint: disable=too-
open_invitations = [user.login.lower() for user in self.org.invitations()]

for team, team_attrs in self.current_teams.items():
# Ignore any team not being configured locally, will be handled later
if team.name not in self.configured_teams:
continue

# Update current team members with dict[NamedUser, str (role)]
team_attrs["members"] = self._get_current_team_members(team)

Expand All @@ -559,15 +563,6 @@ def sync_teams_members(self, dry: bool = False) -> None: # pylint: disable=too-
user.login.lower(): role for user, role in team_attrs["members"].items()
}

# Handle the team not being configured locally
if team.name not in self.configured_teams:
logging.warning(
"Team '%s' does not seem to be configured locally. "
"Taking no action about this team at all",
team.name,
)
continue

# Get configuration from current team
if team_configuration := self.configured_teams.get(team.name):
pass
Expand Down Expand Up @@ -666,8 +661,35 @@ def sync_teams_members(self, dry: bool = False) -> None: # pylint: disable=too-
team.name,
)

def get_members_without_team(self) -> None:
"""Get all organisation members without any team membership"""
def get_unconfigured_teams(
self, dry: bool = False, delete_unconfigured_teams: bool = False
) -> None:
"""Get all teams that are not configured locally and optionally remove them"""
# Get all teams that are not configured locally
unconfigured_teams: list[Team] = []
for team in self.current_teams:
if team.name not in self.configured_teams:
unconfigured_teams.append(team)

if unconfigured_teams:
if delete_unconfigured_teams:
for team in unconfigured_teams:
logging.info("Deleting team '%s' as it is not configured locally", team.name)
if not dry:
team.delete()
else:
unconfigured_teams_str = [team.name for team in unconfigured_teams]
logging.warning(
"The following teams of your GitHub organisation are not "
"configured locally: %s. Taking no action about these teams.",
", ".join(unconfigured_teams_str),
)

def get_members_without_team(
self, dry: bool = False, remove_members_without_team: bool = False
) -> None:
"""Get all organisation members without any team membership, and
optionally remove them"""
# Combine org owners and org members
all_org_members = set(self.org_members + self.current_org_owners)

Expand All @@ -685,11 +707,21 @@ def get_members_without_team(self) -> None:
members_without_team = all_org_members.difference(all_team_members)

if members_without_team:
members_without_team_str = [user.login for user in members_without_team]
logging.warning(
"The following members of your GitHub organisation are not member of any team: %s",
", ".join(members_without_team_str),
)
if remove_members_without_team:
for user in members_without_team:
logging.info(
"Removing user '%s' from organisation as they are not member of any team",
user.login,
)
if not dry:
self.org.remove_from_membership(user)
else:
members_without_team_str = [user.login for user in members_without_team]
logging.warning(
"The following members of your GitHub organisation are not "
"member of any team: %s",
", ".join(members_without_team_str),
)

# --------------------------------------------------------------------------
# Repos
Expand Down
12 changes: 10 additions & 2 deletions gh_org_mgr/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,16 @@ def main():
org.sync_current_teams_settings(dry=args.dry)
# Synchronise the team memberships
org.sync_teams_members(dry=args.dry)
# Report about organisation members that do not belong to any team
org.get_members_without_team()
# Report and act on teams that are not configured locally
org.get_unconfigured_teams(
dry=args.dry,
delete_unconfigured_teams=cfg_app.get("delete_unconfigured_teams", False),
)
# Report and act on organisation members that do not belong to any team
org.get_members_without_team(
dry=args.dry,
remove_members_without_team=cfg_app.get("remove_members_without_team", False),
)
# Synchronise the permissions of teams for all repositories
org.sync_repo_permissions(dry=args.dry, ignore_archived=args.ignore_archived)
# Remove individual collaborator permissions if they are higher than the one
Expand Down