Insight Technology

2020.09.04

GitLabでGroup内の全メンバー一覧を取得する

このエントリーをはてなブックマークに追加

こんにちは。インサイトテクノロジーの松尾です!

インサイトテクノロジーではCI/CDの構築を含めたソースコードの管理や、ちょっとしたタスク管理に至るまで、GitLabを活用して業務を行っています。

https://about.gitlab.com/

GitLabには、自分でホストするパターンと、GitLabをサービスとして利用するパターンがありますが、我々は後者のGitLabをサービスとして利用しています。また、無料プランでもプライベートレポジトリの利用等は可能ですが、我々はいくつかの便利な機能を使いたいため、課金プランを利用しており、そのため、課金が利用者数に応じて発生します。

発生した問題

GitLabのメニューで「Settings」-「Billing」を選択すると、課金メンバー数を確認することが可能です。また、Membersではそのグループに追加されているメンバーの一覧を確認できます(上位のグループから継承しているメンバも含む)。

billing-info.png (図:Billing画面の表示例)

しかしながら、下位のサブグループやプロジェクトで追加されたメンバーは含まれないため、ここのメンバー一覧の数と課金メンバー数は必ずしも一致はしません。

group-members.png (図:Membersを表示したところ)

一方で、課金メンバー一覧を取得する方法がGitLabには備わっていない(上記の図だとBillingの32のメンバー一覧を表示することができない)ため、課金グループの中のサブグループや各プロジェクトでメンバーを別途追加している場合、それらのメンバーを手動で確認する必要があります。なお、メンバーは、上位グループのメンバーを継承するため、グループとプロジェクトに含まれるメンバーは、実際は以下のように確認されます。

members-in-a-gitlab-group.png (図:グループ内のメンバーカウントの考え方)

我々が業務で使用しているgitlabグループの中にはグループもプロジェクトも無数に存在しているため、これらを一つ一つ確認するのは現実的ではありません。

解決方法

GitLabにはグループやプロジェクトを操作するためのAPIが備わっており、そのAPIを通して以下を取得することができます。

  • 指定グループ内のサブグループの一覧
  • 指定グループ内のプロジェクト一覧
  • 指定グループ内のメンバー一覧
  • 指定プロジェクト内のメンバー一覧

このAPIを組み合わせることで、トップのグループから各サブプロジェクト、各グループをたどって、そこの配下のメンバー一覧を取得することがで、最上位グループ配下のユニークメンバー数をカウントすることを試みましたので、本ブログにて紹介させていただきます。

GitLabのAPIの利用について

GitLabのAPIの使用については詳細なドキュメントが提供されています(英語)。各種サービスのAPIをアクセスして情報取得するようなことをこれまでやった経験がある方は、問題なく実行可能だと思います。

https://docs.gitlab.com/ee/api/

なお、APIアクセスするにはアクセストークンが必要となりますので、事前に、自分のアカウント情報からアクセストークンを取得します。 自分のアカウントのSettingsからAccess Tokensを選択し、「Personal Access Token」を生成します。「api」へのアクセスを選択しアクセストークンを生成します。

get-gitlab-access-token.png (図:アクセストークンの取得)

アクセストークンは、パラメータもしくはヘッダーで指定します。

https://docs.gitlab.com/ee/api/#personalproject-access-tokens

まずは自分の指定グループ内のメンバーの一覧を取得てみます。 メンバー一覧の取得はGroup and project members APIを使用します。

https://docs.gitlab.com/ee/api/members.html

APIの説明では取得部分の説明は以下となっています。

/groups/:id/members

「:id」の部分については対象のグループのidを指定する必要があります。グループのidは、gitlabの画面でグループを表示した際に、グループ名の下に表示されています。

where-is-group-id.png (図:グループID)

また基本使用方法のところに、"api"とバージョン"v4"をつけて呼ぶよう書かれていますので、元となるURLは以下となります。

https://gitlab.com/api/v4/

元となるURL、メンバーを取得するAPI、認証情報を組み合わせると、以下のURLとなります。(アクセストークンをパラメータで渡す場合)

https://gitlab.com/api/v4/groups/:id/members?private_token=<your_access_token>

APIの実行確認には、Postmanなどのツールを使う以外にも、curlコマンドで実行したり、ブラウザに貼り付けて実行確認することも可能です。

api-exec-in-a-browser.png (図:ブラウザでのAPI実行)

このAPI呼び出しを組み合わせて、指定グループ配下の全ユーザーを列挙することが可能です。

実装した処理とソースコード

このAPIを用い、指定グループ配下の全ユーザーを列挙する処理をPythonで実装してみましたので紹介します。

よくあるディレクトリ内を再帰的にたどるプログラムの考え方と同じく、トップのグループから、サブグループ、サブプロジェクトをたどることで、トップグループ配下の全グループ、全プロジェクトの一覧を取得し、それらを取得しながあ、各要素に含まれるユーザーアカウントを列挙します。

作ったソースは以下となります。ACCESS_TOKENTOP_GROUP_IDを自分の環境に合わせて書き換えて実行してください。

import requests
import json

# 環境に合わせて変える
ACCESS_TOKEN='xxxxxxxxxxxxxxxxxxxx'
TOP_GROUP_ID=000000
TOP_GROUP_URL='https://gitlab.com/xxxxxx'


END_POINT_URL='https://gitlab.com/api/v4/'
HEADERS={
    'Private-Token': ACCESS_TOKEN
}
ADDITIONAL_PARAM='?per_page=100' # paginationを100固定

members_table = {}

# APIアクセスを実行しレスポンスをjsonとして返す
def get_response_json(api):
    url = END_POINT_URL + api + ADDITIONAL_PARAM
    response = requests.get(headers=HEADERS, url=url)
    return response.json()

# メンバー一覧を取得し未登録メンバーをディクショナリに追加する
def list_members(api):
    response_json = get_response_json(api)
    for m in response_json:
        if m['id'] not in members_table:
            print('member_id:', m['id'], ', username:', m['username'])
            members_table[m['id']] = m['username'] + ' ' + m['name']

# 指定グループ内のメンバー一覧を列挙し、サブグループ一覧、プロジェクト一覧内のメンバー一覧を調べる
def process_subgroups(gid, gname):
    print(gname)
    list_members('groups/' + str(gid) + '/members')

    # サブグループの一覧を処理
    response_json = get_response_json('groups/' + str(gid) + '/subgroups')
    for g in response_json:
        process_subgroups(g['id'], g['web_url'])

    # サブプロジェクトの一覧を処理
    response_json = get_response_json('groups/' + str(gid) + '/projects')
    for p in response_json:
        print(p['web_url'])
        list_members('projects/' + str(p['id']) + '/members')

# 実行
process_subgroups(TOP_GROUP_ID, TOP_GROUP_URL)

# 実効結果出力
print('\nnumber of users:', len(members_table))
for m in members_table.keys():
    print('member_id:', m, ', user:', members_table[m])

(ソースコード)

問題発生:結果が合わない!?

上記のソースコードでは既に暫定対応済ですが実行の過程で、どうにも結果が合わないということがありました。ボッタくられているのか??とも思いもしたのですが、よくよく見ると、所属するメンバー数が一部しか列挙されていませんでした。問題はpaginationで、GitLabのAPIではpaginationのパラメーターのper_pageのデフォルトが20に設定されていました。このサンプルソースでは、ひとまず100に設定することで問題を回避しています。が、本来は、何ページあるかを確認したうえで、全ページを確認する必要があるかとは思います。

https://docs.gitlab.com/ee/api/#offset-based-pagination

おわりに

プライベートレポジトリをもともと無料でも使えたこともあり、インサイトテクノロジーではGitLabを開発の多くの工程で利用しています。APIやCIの機能も豊富。今後もどんどん活用していきたいと思います。今回は、GitLabの課金ユーザーのメンバー一覧情報を取得するために、APIを使った簡単なスクリプトを作成しました。業務でGitLabを使っていると、複数のGitLab projectに情報が散乱しがちにもなるため、今後、APIを使って自動で情報を集めることなども考えていけたらと思います。

開発メンバー募集中デス。

インサイトテクノロジーにご興味を持たれたエンジニアの方、ぜひご連絡をお待ちしております♪

https://recruit.jobcan.jp/insight-tec/

ページトップへ