人に紐付かない権限で連携機能を設定できるGithub Apps

2018年12月18日 火曜日


【この記事を書いた人】
濵﨑 一樹

システムクラウド本部所属。IaaSを作っています。旅行をしたり、雪山に登って写真を撮るのが趣味です。

「人に紐付かない権限で連携機能を設定できるGithub Apps」のイメージ

IIJ 2018 TECHアドベントカレンダー 12/18(火)の記事です】

IIJではコードホスティング環境としてGithub Enterpriseを導入しています。Github EnterpriseについてはIIJのサービス開発を支えるGithub Enterpriseとdrone.io をご覧ください。

GithubではGithubと連携するツールを作るための仕組みとして、Personal access tokens を使う方法と、OAuthを利用する方法がありました。しかしいずれの仕組みもユーザーに紐付いたアクセスキーが発行されてしまうため、設定したユーザーが退職したり、異動したりしてアカウントが停止されたりするとアクセスキーも無効になり、API連携がエラーになってしまうことがあります。

そこで新しくGithub Appsという仕組みが導入され、リポジトリに紐づけて各種アプリケーションにアクセスを許可できるようになりました。今後はGithub Appsを用いて連携するのが推奨されています。GitHub Enterpriseでは2.13から利用が可能です。

Github Apps を作る

チュートリアルが用意されていますのでそれに従っていけばWebhookを受け取るところまで簡単に作成できます。チュートリアルはRubyで書かれていますが公式のGithub APIライブラリであるOctokit は.Net、JavaScriptでも提供されていますのでこれらの言語でも簡単に作成することができます。

Github Appとしての認証はJSON Web Token(JWT)を利用します。

require 'octokit'
require 'jwt'
require 'time'

  before do
    payload = {
      # The time that this JWT was issued, _i.e._ now.
      iat: Time.now.to_i,

      # How long is the JWT good for (in seconds)?
      # Let's say it can be used for 10 minutes before it needs to be refreshed.
      # TODO we don't actually cache this token, we regenerate a new one every time!
      exp: Time.now.to_i + (10 * 60),

      # Your GitHub App's identifier number, so GitHub knows who issued the JWT, and know what permissions
      # this token has.
      iss: APP_IDENTIFIER
    }

    # Cryptographically sign the JWT
    jwt = JWT.encode(payload, PRIVATE_KEY, 'RS256')

    # Create the Octokit client, using the JWT as the auth token.
    # Notice that this client will _not_ have sufficient permissions to do many interesting things!
    # We might, for particular endpoints, need to generate an installation token (using the JWT), and instantiate
    # a new client object. But we'll cross that bridge when/if we get there!
    @client ||= Octokit::Client.new(
      bearer_token: jwt,
      api_endpoint: "https://#{ENV['GITHUB_HOST']}/api/v3/"
    )
  end

また永続的に利用可能なアクセストークンは発行されず、都度アクセストークンを発行しそれを利用してAPIにアクセスし、有効期限は1時間です。毎回アクセストークンを発行するには POST /app/installations/:installation_id/access_tokens を利用します。アクセストークンを発行すればあとは通常のOAuthと同様にAPIアクセスが可能になります。

installation_id = payload['installation']['id']
@client.create_app_installation_access_token(installation_id)[:token]

GraphQL

GithubではGraphQLをサポートしています。GraphQLはFacebookが開発しているAPI用のクエリ言語です。GraphQLを提供するのは難しいですが、利用するのはそこそこ簡単です。現在では GraphQL API v4 が提供されています。

実はREST APIでは取れない情報でもGraphQLでは取得できる情報があります。SuggestedReviewer もそのうちのひとつです。

今回は SuggestedReviewer を利用してレビュアーの自動割当をするアプリを作成します。RubyのOctokitはREST APIにしか対応していないため、GraphQLを利用する場合は自分でクライアントを用意する必要があります。Github専用ではありませんが、Github自身がGraphQL用のクライアントライブラリとして graphql-client を提供しています。

graphql-client はスキーマを取得するときのHTTP Headerを設定できず、Github GraphQLのスキーマを落としてこれないので手で落としたものをファイルから読み込ませるようにすると良いです。

自動でレビューアーをアサインする

プルリクエストを用いたコードレビューは文化として浸透してきたと思います。そこで度々問題になるのは誰にレビューをお願いするかということです。コードレビューの一番の目的はバグを見つけたり、保守性の高いコードにしたりしてコードの品質を向上させることですが、チーム内での知識の共有というのも重要な目的になります。つまりたくさんの人が見ることにより、書き方を統一する、不具合が起きたときに対応できる増やす、良い書き方を広めるなどの効果がきたいできます。ですので誰にレビューを依頼するか困るような状況であればおみくじで決めてしまっても長期的には良い結果を生むことになります。

ただ適当にレビューを依頼するというのを人間がすると偏るかもしれませんし、角が立つかもしれませんね。そういうときはbotにやらせてしまうのが一番です。

SuggestedReviewer は変更箇所などから誰がレビューアーとしてよいかGithubが提案してくれる機能ですが、GraphQLのクエリでは以下のように書くと取得することができます。

query { 
  repository(owner: '', name: $repo_name) { 
    pullRequest(number: $pr_number) {
      suggestedReviewers {
        isAuthor
        isCommenter
        reviewer {
          id
        }
      }
    }
  }
}

とりあえず実行して結果を見てみたい場合は https://developer.github.com/v4/explorer/ からお手軽に実行結果を試すことができます。また型情報が強力なため補完もバリデーションも効きます。ただレビューアーを割り当てる権限がないリポジトリに対して実行しても結果は取得できないようです。

ついでに RepositoryオブジェクトにassignableUsersがありますので、誰も提案してくれなかった場合はそこからランダムに割り当てることにしましょう。GraphQLなので上記のクエリを改造すれば一度で取得することができます。

query ($owner: String!, $repo_name: String!, $pr_number: Int!) {
  repository(owner: $owner, name: $repo_name) {
    pullRequest(number: $pr_number) {
      id
      suggestedReviewers {
        isAuthor
        isCommenter
        reviewer {
          id
        }
      }
    }
    assignableUsers(first: 100) {
      edges {
        node {
          id
        }
      }
    }
  }
}

あとはこの中から誰を選ぶのかを決めて

# Search reviewers
assign_id = if response.data.repository.pull_request.suggested_reviewers.empty?
              # from repository users
              assignable_users = response.data.repository.assignable_users.edges.map do |edge|
                edge.node.id
              end
              assignable_users.sample
            else
              # from suggested reviewers
              response.data.repository.pull_request.suggested_reviewers.sample.id
            end

addPullRequestReview mutation を利用してレビューアを割り当てます。

mutation ($pr_id: ID!, $user_id: ID!) {
  requestReviews(input: {pullRequestId: $pr_id, userIds: [$user_id]}) {
    pullRequest {
      title
    }
  }
}

あとはpull_requestイベントのopenedアクションで上記を実行するようにすれば

このようにプルリクエストを作成したタイミングで自動でレビューアーを割り当てることができます。(ロゴを設定しないと自分のアイコンが使われてしまうようです)

ここまで来たらアプリを公開しましょう。以下のようなページが作成され、誰でもここからアプリをインストールして使うことができるようになります。

まとめ

このようにGithub Appsで作成したアプリケーションは人に依存しない権限を与えることができるのでチーム開発に最適です。また利用者はボタン一発でインストールすることができますのでチーム外への展開も楽になります。

Github GraphQLは機能追加がたくさん行われておりPreview状態にあるAPIも多数あります。REST APIで取れない情報も取れたりする場合もありますので探してみると使えるものが見つかるかもしれません。

今回作成したコードは ashphy/reviewer-auto-assign にありますのでサンプルとしてお使いください。

濵﨑 一樹

2018年12月18日 火曜日

システムクラウド本部所属。IaaSを作っています。旅行をしたり、雪山に登って写真を撮るのが趣味です。

Related
関連記事