概要
退職等でブロックしていくときに発生する、次のいずれかを満たすプロジェクトを探す。
名前空間はユーザーとグループのいずれでもよい。
- 所属メンバーがすべてブロック済である。
- ブロックされていないメンバーもいるが、Maintainer 権限以上を持つものがいない。
その他:
- 出力は YAML 的ななにか。
- これからブロックするユーザーについてあらかじめ確認する場合は、
--block
オプションで模擬できる。 - ユーザーの名前空間だけテストする場合は、
--user
オプションで模擬できる。
利用例
./find_missing # 現状をチェックする
./find_missing --block foo --block bar # foo と bar をブロックした状況をチェックする
./find_missing --block foo --user foo # foo をブロックした状況について、 foo/* をチェックする
ソースコード
find_missing
#!/usr/bin/env python3
import argparse
import os
import requests
class GitLabAPI:
def __init__(self, endpoint, token):
self.endpoint = endpoint
self.session = requests.Session()
self.session.headers.update({'PRIVATE-TOKEN': token})
def fetch(self, path, verbose=False, **kwargs):
url = self.endpoint + path if path.startswith('/') else path
res = None
while True:
params = dict(kwargs)
if res is not None:
params['page'] = res.headers['X-Next-Page']
res = self.session.get(url, params=params)
if verbose:
current_page = res.headers.get('X-Page', '?')
total_pages = res.headers.get('X-Total-Pages', '?')
print(f'# [{current_page}/{total_pages}] {res.url}')
res.raise_for_status()
for item in res.json():
yield item
if res.headers.get('X-Next-Page', '') == '':
break
def get_users(self, **kwargs):
return self.fetch('/users', **kwargs)
def get_projects(self, **kwargs):
return self.fetch('/projects', **kwargs)
def get_user_projects(self, id_, **kwargs):
return self.fetch(f'/users/{id_}/projects', **kwargs)
def get_project_members(self, id_, **kwargs):
return self.fetch(f'/projects/{id_}/members/all', **kwargs)
def report_project(project, members, notifications):
roles = {
0: 'No access',
5: 'Minimal access',
10: 'Guest',
20: 'Reporter',
30: 'Developer',
40: 'Maintainer',
50: 'Owner',
}
print('- project:', project['path_with_namespace'])
print(' visibility:', project['visibility'])
print(' description:', project['description'].replace('\r\n', ' '))
print(' members:')
for member in members:
name = member['username']
role = roles.get(member['access_level'], member['access_level'])
state = member['state'] if member['state'] == 'active' else member['state'].upper()
print(f' - {name} ({role}, {state})')
if len(notifications) > 0:
print(' notifications:')
for message in notifications:
print(f' - {message}')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--endpoint',
default=os.getenv('GITLAB_API_ENDPOINT', None),
help='GitLab API endpoint [https://example.com/api/v4]')
parser.add_argument('--token',
default=os.getenv('GITLAB_API_PRIVATE_TOKEN', None),
help='GitLab private token')
parser.add_argument('--user', help='Check for the user project')
parser.add_argument('--block', action='append', help='Assume the user is blocked')
args = parser.parse_args()
gitlab = GitLabAPI(args.endpoint, args.token)
# check username
if args.block:
for name in args.block:
for obj in gitlab.get_users(username=name):
break
else:
raise RuntimeError(f'User {name} not found')
if args.user:
iterable = gitlab.get_user_projects(args.user, verbose=True)
else:
iterable = gitlab.get_projects(verbose=True)
for project in iterable:
no_active_member = True
owner_is_blocked = True
users = []
for user in gitlab.get_project_members(project['id']):
if args.block and user['username'] in args.block:
user['state'] = 'block_simulated'
if user['state'] == 'active':
no_active_member = False
if user['access_level'] >= 40:
owner_is_blocked = False
users.append(user)
notifications = []
if no_active_member:
notifications.append('no active member')
elif owner_is_blocked:
notifications.append('owner is blocked')
if len(notifications) > 0 or args.user:
report_project(project, users, notifications)
if __name__ == '__main__':
main()