Perl Hackers Hub

第67回GitHub APIによるチーム開発の効率化 ―基本操作から、GitHub Webhooksの活用まで(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはtecklこと菅井茂樹さんで、テーマは「GitHub APIによるチーム開発の効率化」です。

本稿のコードは、執筆時点(2021年3月)の最新であるPerl 5.32.1で動作確認を行っています。サンプルコードはWEB+DB PRESS Vol.122のサポートサイトから入手できます。

チーム開発をもっと便利にするGitHub API

ソフトウェア開発にとって欠かせない存在となったGitHubですが、GitHub APIを使用することでさらに便利に活用できます。アイデアしだいで、普段手動で行っている操作を自動化したり、管理コストを削減したり、外部のツールと連携させたりと、チーム開発の効率化を行えます。

本稿では、このGitHub APIに焦点を当てます。みなさんが会社のGitHubの管理・運用を行うことになったケースを想定し、PerlのGitHub APIライブラリを用いてチーム開発をよりスムーズに進めるためのアイデアやテクニックを紹介します。

GitHub APIとは

GitHub APIとは、GitHubが提供するGitHub独自のAPIです。GitHub APIを使用すると、リポジトリの作成・編集、issueの取得・編集、Pull Requestの作成・編集、Gistの投稿、任意のコードの検索、Organization(組織)の管理など、ブラウザから可能な操作のほとんどを行えます。

GitHub APIの種類

2021年3月現在、GitHub APIにはv3 REST APIv4 GraphQL APIがあります。v4 GraphQL APIはAPIのエンドポイントが1つとなり、複数のリクエストを1回にまとめることができたり、必要な要素のみを取得できたりするなどの長所があります。一方でエラーハンドリングが難しく、初学者の学習コストがかかることも想定されます。v3 REST APIとv4 GraphQL APIではどちらも同じことを実現できますが、古くからあるREST APIのほうが気軽に組込みやすい方が多いと思いますので、本稿ではREST APIを用います。

GitHub Enterpriseの場合

GitHub Enterpriseという法人向けのGitHubを利用している方もいるでしょう。GitHub Enterpriseも、GitHub.comと同等のGitHub APIを備えています。GitHub Enterprise用のAPIのエンドポイントを合わせて指定することで利用できます。本稿の解説内容は、GitHub Enterpriseでも利用可能です。

GitHub API開発の準備

GitHub APIを使う準備を行いましょう。

OAuthトークンの取得

GitHub APIでは、オープンなリポジトリの操作には認証が不要です。プライベートなリポジトリの操作にはOAuthトークンによる認証が必要です。また、未認証の場合のリクエスト数は1時間あたり60件までですが、認証されたリクエストの場合は1時間あたり5,000件までと大幅に緩和されます。

OAuthトークンの取得には、GitHubのサイト上から「Settings」「Developer settings」「Personal access tokens」と遷移し、任意の名前でトークンを作成してください。この際、トークンに付与するスコープ(権限)を適切に選択してください。慣れないうちは、書き込み権限は付与せずに作成するほうがよいと思います。

GitHub APIクライアントのインストール

CPANにはPerlでGitHub APIを利用するためのクライアントがいくつも公開されています。代表的なのはPithubNet::GitHubです。どちらもほとんど同じことができますが、今回はオブジェクト指向で書きやすいPithubを使用します。Pithubはv3 REST APIのみの対応となるため、v4 GraphQL APIを使いたい場合はNet::GitHub::V4を利用するとよいでしょう。

Pithubは、cpanmコマンドでインストールできます。

$ cpanm Pithub
$ perl -MPithub -E 'say $Pithub::VERSION'
0.01036

これで、GitHub APIを利用するための準備が整いました。

GitHub APIの実用例

それでは、GitHub APIを利用してみましょう。前述したようにみなさんが会社のGitHubのOrganizationを運用している管理者だと想定して、いくつかの例を紹介します。

組織の公開リポジトリ一覧を取得する

たとえば運用しているOrganizationが、OSSOpen Source Software向けの公開リポジトリと、自社運用サービス向けのプライベートリポジトリの両方を扱っていたとします。これらのリポジトリの中から、公開リポジトリのみの一覧を取得するには次のようにします。

use strict;
use warnings;
use feature 'say';

use Pithub;

# 前節で取得したOAuthトークン
my $token = 'my_oauth_token';
my $pit = Pithub->new(
    token => $token,
);
my $result = $pit->repos->list( …(1)
    org => 'org_name_xxxxx',
    params => {
        type => 'public',
    },
);
while ( my $row = $result->next ) {
    say $row->{name};
}

これ以降に登場するコードでは、冒頭のuse Pithub;までは省略します

(1)$pit->reposとしている箇所は、GitHub APIドキュメントのRepositoriesに対応しています。このように、Pithubのメソッドと連動するモジュール名はGitHub APIの各リソースと対応しており、使用したいリソースから直感的にAPIリクエストを扱えます。この例ではリポジトリ名のみを出力していますが、ほかにもドキュメント内のレスポンスのサンプルにあるようなリポジトリの最終更新日(updated_at)やデフォルトブランチ名(default_branch⁠⁠、未解決のissueの件数(open_issues_count)などのリポジトリの固有情報を取得できます。

この程度であればブラウザ上での確認で十分ではないかと思われるかもしれませんが、後述するSlackなどの外部ツールと連携させることによって、公開リポジトリが作成されたタイミングでSlackなどの外部ツールに通知する、といったケースに活用できます。

なお、GitHub Enterpriseを利用している方は、次のように自身のGitHub Enterprise用のAPIのエンドポイントを追加で指定することで同じことが行えます。

my $pit = Pithub->new(
    token => $token,
    api_uri => 'https://example.com/api/v3/',
);

組織のメンバー一覧を取得する

たとえばみなさんのOrganizationにおいて、外部からの不正アクセスや不正ログインを防ぐため、GitHubの2要素認証を必須とすることになった状況を考えてみます。Organizationに所属しているメンバー一覧の中から、まだ2要素認証を設定していないメンバーの一覧を取得するには、次のようにします。

my $pit = Pithub->new(
    token => $token,
);
my $res = $pit->orgs->members->list( …(1)
    org => 'org_name_xxxxx',
    params => {
        filter => '2fa_disabled',
    },
);
my $members = $res->content;
for my $member (@{$members}) {
    say $member->{login};
}

(1)$pit->orgsとしている箇所は、GitHub APIドキュメントのOrganizationsに対応しています。

issueを検索する

ここからは、リポジトリ単位の操作を見ていきましょう。

たとえばissueの件数が増えてくると、対応漏れが出てきたり、対応する優先度が途中で変わったりして、定期的なissueの棚卸しが必要になります。GitHub APIを利用すると、チームで必要な任意の条件でissueのソート条件を設定したり、特定のマイルストーンや特定のメンバーのアサインのみをフィルタできたりします。

次のコードは、未解決かつbugのラベルが付いたissueを取得して、最終更新日時が昇順(古い順)で取得する例です。

use JSON;

my $pit = Pithub->new(
    user => 'XXXXX',
    repo => 'YYYYY',
    token => 'my_oauth_token',
);

my $res = $pit->repos->issues->list(
    params => {
        state  => 'open',
        labels => 'bug',        # 抽出対象のラベル
        since  => '2021-01-01', # 更新日(設定日時以降)
        direction => 'asc',     # 昇順
        sort      => 'updated', # 最終更新日時順
    }
)->response;

my $json = decode_json($res->content);
for my $rec (@{$json}) {
    say(join(',', $rec->{html_url}, $rec->{title}));
}

これをcronなどに設定して毎日特定の時間に未解決のissueの通知を流すようにすることで、対応漏れに気付きやすい環境を整備できます。

Pull Requestのレビュアーをランダムに設定する

Pull Requestの操作もAPIで行えます。たとえばPull Requestのレビュアーは、特定のメンバーに偏ってしまわないように、チーム内で持ち回り制にしたいことがあると思います。そういった場合に、レビュアーのTeamに所属しているメンバーの中から、ランダムに1人を抽出してレビュアーに設定するには、次のようにします。

use JSON;

my $pit = Pithub->new(
    token => 'my_oauth_token',
);

# レビュアーの所属するTeam ID。実際は数字
my $reviewer_team_id = NN;
# レビュアーを指定するPull Request ID。実際は数字
my $pull_request_id = MM;

# レビュアー担当のチームメンバー一覧を取得
my $res = $pit->orgs->teams->list_members(
    team_id => $reviewer_team_id,
)->response;

my $json = decode_json($res->content);
my @members = ();
for my $member (@{$json}) {
    push @members, $member->{login};
}
# レビュアー用のチームメンバーからランダムに1人を抽出
my $randomized_member = $members[int(rand(@members))];

# ランダムに抽出したメンバーをレビュアーに設定
$pit->repos->pull_requests->reviewers->update(
    repo     => 'XXXXXX',
    user     => 'YYYYYY',
    pull_request_id => $pull_request_id,
    data     => { reviewers => [$randomized_member] },
);

レビューを担当するTeam IDは、あらかじめ決まっているものとします。また、これまでの例とは異なりレビュアーに設定という更新処理を伴いますので、使用するOAuthトークンにはリポジトリの書き込み権限が必要です。

なお、レビュアーを複数人設定することもできます。

レビューが放置されているPull Request一覧を取得する

たとえば、コードレビューを依頼されたあと、忙しくてレビューになかなか着手できない場合があります。別作業に集中していて、レビューを依頼されていることを忘れてしまうこともあるでしょう。レビュイーとしても、レビュアーが忙しそうな場合は再度の依頼は気がひけるかもしれません。このようなレビュー遅延のためのリマインダーとして、Pull Requestが作成されてから1日経過したものだけを抽出するコードは次のようになります。

use Encode;
use Time::Piece;
use Time::Seconds;
use JSON;

my $now = localtime;
my $yesterday = $now - ONE_DAY;

my $pit = Pithub->new(
    token => 'my_oauth_token',
);
my $response = $pit->repos->pull_requests->list(
    user  => 'XXXXX',
    repo  => 'YYYYY',
    params => {
        state => 'open',
    }
)->response;

my $json = decode_json($res->content);
for my $rec (@{$json}) {
    my $updated_at = Time::Piece->strptime(
        $rec->{updated_at}, '%Y-%m-%dT%H:%M:%SZ'
    );
    # Pull Requestの更新後1日以上経過している場合
    if ($updated_at < $yesterday) {
        printf("[%s] が長時間確認されていません。 %s\n",
            encode_utf8($rec->{title}),
            $rec->{html_url});
    }
}

これも定期的に通知を流すようにすることで、対応漏れを防ぎやすくなるでしょう。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧