はじめに
みなさんはGitHub Organizationで管理するリポジトリのManage accessのチームの紐付けは普段どのように設定していますでしょうか?
いつもポチポチ設定するの面倒ではないでしょうか?
read:
- team1
- team2
write:
- team3
admin:
- team4
こんな感じにリポジトリごとにYamlを用意してコード管理できたら嬉しくないですか?
GitHub APIを扱えるPyGithubを使って考えてみました。
https://github.com/PyGithub/PyGithub
ディレクトリ構成
構成としてはこのような形です。
├── .github
│ ├── scripts
│ │ └── duplicate_team_check.py
│ └── workflows
│ └── duplicate_team_check.yml
├── README.md
├── organization_name # GitHub Organization名
│ ├── repo1.yml # リポジトリ名.yml
│ ├── repo2.yml
│ └── repo3.yml
└── main.py
ディレクトリ名、ファイル名を使い、Yamlに書かれた設定通りにチームとManage Accessを紐付けていきます。
また、Yaml内のチームの重複を防ぐためのCIも設定しています。
各種コード
main.py
YamlをparseしてPyGithubで用意されているupdate_team_repositoryを利用して紐付けを行います。
Yaml側になくてGitHub側にあるチームはremove_from_reposでサヨナラします。
import glob
import os
import sys
import threading
import yaml
from github import Github
file_list = glob.glob('./*/*')
g = Github(os.environ['TOKEN'])
class th(threading.Thread):
def __init__(self, file):
threading.Thread.__init__(self)
self._file = file
def run(self):
l = self._file.split('/')
owner_name = l[1]
repo_name = os.path.splitext(os.path.basename(l[2]))[0]
obj = self.yaml_load(f)
org = g.get_organization(owner_name)
repo = g.get_repo(owner_name + '/' + repo_name)
self._delete_teams(repo, obj)
for k, v in obj.items():
for i in v:
team = org.get_team_by_slug(i)
team.update_team_repository(repo, k)
print(f'Finished setting of {repo_name}')
def yaml_load(self, path):
try:
with open(path) as file:
obj = yaml.safe_load(file)
except Exception as e:
print(e, file=sys.stderr)
sys.exit(1)
return obj
def delete_teams(self, repo, obj):
teams = repo.get_teams()
l = []
for _, v in obj.items():
l.append(v)
l = sum(l, [])
for team in teams:
if team.name not in l:
team.remove_from_repos(repo)
for f in file_list:
t = th(f)
t.start()
リポジトリ名.yml
各権限がキー、チームが配列となるように書いていきます。
pull:
- team2
push:
- team3
admin:
- team4
maintain:
- team5
triage:
- team1
各権限に関しては以下に書かれています。
https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization
.github/scripts/duplicate_team_check.py
Yaml内ではチームがユニークであってほしいので、それをマージ前に防ぎましょう。
pull:
- team1
push:
- team1
admin:
- team1
import glob
import sys
import yaml
file_list = glob.glob('./*/*')
def yaml_load(path):
try:
with open(path) as file:
obj = yaml.safe_load(file)
except Exception as e:
print(e, file=sys.stderr)
sys.exit(1)
return obj
for f in file_list:
l = []
obj = yaml_load(f)
for _, v in obj.items():
l.append(v)
l = sum(l, [])
dup = [x for x in set(l) if l.count(x) > 1]
if dup:
print(f'{dup} is duplicate in {f}')
sys.exit(1)
.github/workflows/duplicate_team_check.yml
name: Duplicate Team Check
on:
pull_request:
types:
- opened
- synchronize
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 2
- name:
run: python3 .github/scripts/duplicate_team_check.py
実行
準備ができたら実行するだけです。
$ TOKEN=xxxxxxxxxx python3 main.py # トークンの権限はFull control of private repositoriesが必要
その他
CODEOWNERSとの連携
CODEOWNERSとの連携を行って、そのリポジトリのManage Accessの設定はそのリポジトリのadminチームのレビューを通さないとマージできない仕組みにすればいい感じかも。
organization-name/repo1.yml @organization-name/<repo1のadminを持つチーム>
CODEOWNERSについてはこちらの記事が参考になります。
注意👺
権限設定の実行はCIで行わないこと
マージされたら権限設定も自動で行えればかっこいいと思うのですが、ちょっと危ないです。
GitHub Actionsではトリガーとなる部分が書き換えられたらfeatureブランチでも実行できちゃうので、悪意のある人が任意の権限を設定しちゃうかもしれません。
on:
pull_request:
types:
- closed
branches:
- master
env:
TOKEN: ${{ secrets.TOKEN }}
↓
on:
pull_request:
types:
- opened
- synchronize
env:
TOKEN: ${{ secrets.TOKEN }}
信頼を持てるメンバーであれば良いと思いますが、
基本的にはローカルでそのOrganizationの責任を持つ人が実行するのが良いでしょう。
チームで設定すること
誤って外部コントリビューターをinviteしてしまう危険性があるため、チーム名で設定するのがおすすめです。
(ここはもしかしたらOrganizationの設定で防ぐことも可能かもしれません。)
まとめ
良いことが多いです。
- ポチポチしなくて良い
- 権限設定のレビューができる⭐
- 時短
ちゃんと調べてないですが、すでにオープンソースであったりしそう…🤔
コードはこちらにあります。
いい感じに改修してくれる方、プルリク待ってます!
https://github.com/hikimochi/gmac