Skip to content

Commit

Permalink
CBD-5212: Add github group replacer
Browse files Browse the repository at this point in the history
Change-Id: I4d922e966bd53ff9f92f8d4ed3a196ce9cf90e34
Reviewed-on: https://review.couchbase.org/c/build-tools/+/183134
Tested-by: Blair Watt <[email protected]>
Reviewed-by: Ming Ho <[email protected]>
  • Loading branch information
udkyo committed Aug 11, 2023
1 parent fd4c9f8 commit 49d1f41
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 0 deletions.
9 changes: 9 additions & 0 deletions github/group-replacer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Github group replacer

The purpose of this helper script is to replace an existing 'from' team with
a given 'to' team for all repositories in a specified GitHub organisation.

It requires an access token with repo read/write access, present in the
environment as GITHUB_TOKEN, once present, run with

./app.py --org [org] --from-team-slug=[from] --to-team-slug=[to] [--dry-run]
100 changes: 100 additions & 0 deletions github/group-replacer/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env python3

"""This script is used to switch out one team for another across all the
repositories in a given Github organisation.
Note: Only the source team's permissions are migrated (members are not added
or removed) and the destination team must already exist
"""

import argparse
import sys
from github import Github
from os import environ


class GitHubTeamMover():
def __init__(self, org, dry_run):
self.connect()
self.get_org(org)
self.dry_run = dry_run

def connect(self):
try:
self.g = Github(environ["GITHUB_TOKEN"])
except KeyError:
print("ERROR: Ensure GITHUB_TOKEN is present in environment")
exit(1)

def get_org(self, org):
self.org = self.g.get_organization(org)

def get_repos_team_present_in(self, team_slug):
repos = []
for repo in self.org.get_repos():
if(team_slug in [t.slug for t in repo.get_teams()]):
repos.append(repo)
return repos

def remove_from_repo(self, team_slug, repo):
team = self.org.get_team_by_slug(team_slug)
print("Removing", team.slug, "from", repo.name, "...", end=" ")
if not self.dry_run:
team.remove_from_repos(repo)
print("ok!")
else:
print("no action (dry run)")

def add_to_repo(self, team_slug, repo, role):
team = self.org.get_team_by_slug(team_slug)
print("Adding", team.slug, "to", repo.name,
f"({role} access) ...", end=" ")
if not self.dry_run:
team.add_to_repos(repo)
self.set_repo_role(team, repo, role)
print("ok!")
else:
print("no action (dry run)")

def get_repo_role(self, team_slug, repo):
team = self.org.get_team_by_slug(team_slug)
permission = team.get_repo_permission(repo)
if permission.admin:
role = "admin"
elif permission.maintain:
role = "maintain"
elif permission.push:
role = "push" # this is called 'write' in UI
elif permission.triage:
role = "triage"
elif permission.pull:
role = "read"
else:
print(
f"Couldn't determine permission for {team_slug} on ",
repo.full_name)
sys.exit(1)
return role

def set_repo_role(self, team, repo, role):
return team.update_team_repository(repo, role)

def switch_teams(self, from_team, to_team):
repos_present_in = self.get_repos_team_present_in(from_team)
for repo in repos_present_in:
role = self.get_repo_role(from_team, repo.full_name)
self.add_to_repo(to_team, repo, role)
self.remove_from_repo(from_team, repo)


parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--org', action='store', required=True)
parser.add_argument('--from-team-slug', action='store', required=True)
parser.add_argument('--to-team-slug', action='store', required=True)
parser.add_argument('--dry-run', action='store_true')
args = parser.parse_args()


team = GitHubTeamMover(args.org, args.dry_run)
team.switch_teams(args.from_team_slug, args.to_team_slug)
82 changes: 82 additions & 0 deletions github/group-replacer/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
amqp==5.0.6
appdirs==1.4.4
astroid==2.11.6
attrs==21.4.0
autopep8==1.5.6
billiard==3.6.4.0
boltons==21.0.0
bracex==2.3.post1
celery==5.1.2
certifi==2021.10.8
cffi==1.15.0
charset-normalizer==2.0.12
click==8.1.3
click-didyoumean==0.0.3
click-option-group==0.5.3
click-plugins==1.1.1
click-repl==0.2.0
colorama==0.4.5
coverage==5.5
dd-import==1.0.6
defusedxml==0.7.1
Deprecated==1.2.13
dill==0.3.5.1
distlib==0.3.1
face==20.1.1
filelock==3.0.12
Flask==2.0.1
Flask-Cors==3.0.10
glom==22.1.0
idna==3.3
importlib-metadata==4.11.3
iniconfig==1.1.1
isort==5.10.1
itsdangerous==2.0.1
Jinja2==3.0.1
jsonschema==4.7.2
kombu==5.1.0
lazy-object-proxy==1.7.1
Mako==1.2.0
Markdown==3.3.7
MarkupSafe==2.0.1
mccabe==0.7.0
packaging==21.0
pdoc3==0.10.0
peewee==3.15.1
platformdirs==2.5.2
pluggy==1.0.0
prompt-toolkit==3.0.19
py==1.10.0
pycodestyle==2.7.0
pycparser==2.21
PyGithub==1.55
PyJWT==2.3.0
pylint==2.14.4
PyNaCl==1.5.0
pyparsing==2.4.7
pyrsistent==0.18.1
pytest==6.2.5
pytest-cov==2.12.1
python-lsp-jsonrpc==1.0.0
pytz==2021.1
PyYAML==5.4.1
redis==3.5.3
requests==2.27.1
ruamel.yaml==0.17.21
ruamel.yaml.clib==0.2.6
semgrep==0.104.0
six==1.15.0
toml==0.10.2
tomli==2.0.1
tomlkit==0.11.1
tqdm==4.64.0
typing_extensions==4.3.0
ujson==5.4.0
urllib3==1.26.8
vine==5.0.0
virtualenv==20.4.4
wcmatch==8.4
wcwidth==0.2.5
Werkzeug==2.0.1
wrapt==1.13.3
zipp==3.8.0

0 comments on commit 49d1f41

Please sign in to comment.