7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LIFULLAdvent Calendar 2020

Day 21

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

Last updated at Posted at 2020-12-20

はじめに

みなさんは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

7
0
0

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
  3. You can use dark theme
What you can do with signing up
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?