LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

Organization

GitHubのManage Accessのチーム紐付け as Code

はじめに

みなさんはGitHub Organizationで管理するリポジトリのManage accessのチームの紐付けは普段どのように設定していますでしょうか?
スクリーンショット 2020-12-19 22.22.21.png
いつもポチポチ設定するの面倒ではないでしょうか?

some_repository.yml
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でサヨナラします。

main.py
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

各権限がキー、チームが配列となるように書いていきます。

リポジトリ名.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
duplicate_team_check.py
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

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が必要

こんな感じで紐付けができていることが確認できます。
スクリーンショット 2020-12-20 14.49.43.png

その他

CODEOWNERSとの連携

CODEOWNERSとの連携を行って、そのリポジトリのManage Accessの設定はそのリポジトリのadminチームのレビューを通さないとマージできない仕組みにすればいい感じかも。

.github/CODEOWNERS
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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0