Skip to main content

このバージョンの GitHub Enterprise はこの日付をもって終了となりました: 2023-01-18. 重大なセキュリティの問題に対してであっても、パッチリリースは作成されません。 パフォーマンスの向上、セキュリティの向上、新機能の向上を図るために、最新バージョンの GitHub Enterprise にアップグレードします。 アップグレードに関するヘルプについては、GitHub Enterprise サポートにお問い合わせく� さい

Checks API で CI テストを作成する

GitHub App と Checks API を使用して、テストを実行するための継続的インテグレーションサーバーを構築します。

はじめに

このガイドでは、GitHub AppChecks API について説明します。これらを使って、テストを実行する継続的インテグレーション (CI) サーバーを構築します。

CI とは、ソフトウェアの開発においてコードを� �繁に共有リポジトリにコミットする手法のことです。 コードをコミットする� �度が高いほどエラーの発生が早くなり、開発者がエラーの原� を見つけようとしてデバッグする必要性も減ります。 コードの更新が� �繁であれば、ソフトウェア開発チー� の他のメンバーによる変更をマージするのも、それ� け容易になります。 コードの記述により多くの時間をかけられるようになり、エラーのデバッグやマージコンフリクトの解決にかける時間が減るので、これは開発者にとって� 晴らしいやり方です。 🙌

CI サーバーは、コードの文法チェッカー (スタイルフォーマットをチェックする)、セキュリティチェック、コード網羅率、その他のチェックといった CI テストをリポジトリの新しいコードコミットに対して実行するコードをホストします。 CI サーバーは、ステージングサーバーや本番サーバーにコードを構築しデプロイすることも可能です。 GitHub App で作ることができる CI テストの種類の例については、GitHub Marketplace で手に入る継続的インテグレーション アプリを確認してく� さい。

メモ: このガイドでは、Ruby プログラミング言語を使用したアプリ開発のプロセスを示します。 た� し、Octokit にはさまざまな決まりごとがあります。 JavaScript を使用する� �合は、ProbotNode.js を使用して GitHub アプリを開発できます。

Checks API の概要

Checks API を使うと、リポジトリでコードがコミットされるたびに自動的に実行される CI テストを設定できます。 Checks API により、GitHub での各チェックについての詳細情� �が、pull request の [チェック] タブで� �告されます。Checks API を使うと、コードの特定の行に対して、追� 情� �を含むアノテーションを作成できます。 アノテーションは [チェック] タブに表示されます。pull request の一部であるファイルに対してアノテーションを作成すると、そのアノテーションは [変更されたファイル] タブにも表示されます。

チェック スイート は、チェック実行 のグループ (個々の CI テスト) です。 スイートと実行の両方に、GitHubのプル要求に表示される 状態 が含まれています。 ステータスを使用して、コードコミットがエラーを発生させるタイミングを決定できます。 これらのステータスを保護されたブランチで使うと、pull request の早すぎるマージを防ぐことができます。 詳しくは、「保護されたブランチについて」をご覧く� さい。

Checks API は、新しいコードがリポジトリにプッシュされるたびに、リポジトリにインストールされているすべての GitHub App に check_suite Webhook イベントを送信します。 Checks API イベントのすべてのアクションを受信するには、アプリに checks:write アクセス許可が必要です。 GitHub は既定のフローを使ってリポジトリでの新しいコードのコミットに対する check_suite イベントを自動的に作成しますが、必要に応じてチェック スイートに関するリポジトリ設定を更新できます。 デフォルトのフローは以下の通りです。

  1. リポジトリにコードがプッシュされるたびに、GitHub は、リポジトリにインストールされていて checks:write アクセス許可を持つすべての GitHub App に、requested アクションを含む check_suite イベントを送信します。 このイベントにより、コードがプッシュされたことと、GitHub が新しいチェックスイートを自動的に作成したことがアプリケーションに通知されます。
  2. アプリでは、このイベントを受信したら、そのスイートにチェック実行を追� できます。
  3. チェック実行には、特定のコード行に表示されるアノテーションを含めることができます。

このガイドでは、次の方法について説明します。

  • パート 1: Checks API を使用して CI サーバー用のフレー� ワークをセットアップする。
    • Checks API イベントを受信するサーバーとして GitHub App を構成します。
    • 新たにプッシュされたコミットをリポジトリが受信した時に、CI テスト用の新しいチェック実行を作成します。
    • ユーザが GitHub でチェック実行のアクションをリクエストした時に、チェック実行を再実行します。
  • パート 2: 文法チェッカー CI テストを追� して、作成した CI サーバーフレー� ワークを基に構築する。
    • statusconclusionoutput の詳細を使って、チェック実行を更新します。
    • pull request の [チェック] および [変更されたファイル] タブに表示されるコード行のアノテーションを作成します。
    • pull request の [チェック] タブに [これを修正する] ボタンを表示して、リンターによる推奨事� �を自動的に修正します。

このクイックスタートを完了したときに Checks API CI サーバーがどのように動作するかを理解するには、以下のデモをご覧く� さい。

Checks API CI サーバークイックスタートのデモ

前提条件

GitHub アプリWebhookChecks API のことをま� 理解していない� �合は、作業を始める前に理解しておいてく� さい。 REST API ドキュメントには、さらに API が記載されています。Checks API は GraphQL でも使用できますが、このクイックスタートでは REST に焦点を当てます。 詳しくは、GraphQL の Checks Suite および Check Run オブジェクトをご覧く� さい。

Checks API CI サーバー アプリを作るには、Ruby プログラミング言語Smee Webhook ペイロード配信サービス、GitHub REST API 用の Octokit.rb Ruby ライブラリSinatra Web フレー� ワークを使います。

このプロジェクトを完了するために、これらのツールや概念のエキスパートである必要はありません。 このガイドでは、必要なステップを� �番に説明していきます。 Checks API で CI テストを作成する前に、以下を行う必要があります。

  1. Checks API で CI テストを作成する」のリポジトリをクローンします。

    $ git clone https://github.com/github-developer/creating-ci-tests-with-the-checks-api.git

    ディレクトリの中には、このクイックスタートで使うテンプレート コードを含む template_server.rb ファイルと、完成したプロジェクト コードを含む server.rb ファイルがあります。

  2. 開発環境のセットアップに関するクイックスタートの手� �に従い、アプリ サーバーを構成して実行します。 注: GitHub App のテンプレート リポジトリをクローンするのではなく、このクイックスタートの前のステップでクローンしたリポジトリにある template_server.rb ファイルを使います。

    GitHub App クイックスタート ガイドを以前に完了している� �合は、必ず 新しい GitHub App を登録し、このクイックスタートで使う新しい Smee チャネルを開始してく� さい。

    テンプレート GitHub App の設定で問題が発生する� �合は、「トラブルシューティング」セクションをご覧く� さい。

第 1 部 Checks API インターフェースを作成する

このパートでは、check_suite Webhook イベントを受信してチェック実行を作成および更新するために必要なコードを追� します。 また、GitHub でチェックが再リクエストされた� �合にチェック実行を作成する方法についても学びます。 このセクションの最後では、GitHub プルリクエストで作成したチェック実行を表示できるようになります。

このセクションでは、作成したチェック実行はコードでチェックを実行しません。 その機能は、「パート 2: Octo RuboCop CI テストを作成する」で追� します。

ローカルサーバーにwebhook ペイロードを転送するよう Smee チャンネルが構成されているでしょうか。 サーバーは実行中で、登録済みかつテストリポジトリにインストールした GitHub App に接続している必要があります。 開発環境のセットアップに関する記事の手� �を完了していない� �合は、続ける前にそれを行う必要があります。

それでは作業を始めましょう。 パート 1 では、以下のステップを完了させます。

  1. アプリのアクセス許可を更新する
  2. イベントの処理を追� する
  3. チェック実行を作成する
  4. チェック実行を更新する

ステップ 1.1. アプリケーションの権限を更新する

最初にアプリを登録したときは、既定のアクセス許可を受け入れました。これは、アプリがほとんどのリソースにアクセスできないことを意味します。 この例においては、アプリケーションにはチェックを読み取りおよび書き込みする権限が必要となります。

アプリケーションの権限を更新するには、以下の手� �に従います。

  1. アプリの設定ページでアプリを選び、サイドバーの [アクセス許可と Webhook] をクリックします。
  2. [アクセス許可] セクションで [チェック] を探し、その横にある [アクセス] ドロップダウンで [読み取りと書き込み] を選びます。
  3. [イベントへのサブスクライブ] セクションで、 [チェック スイート][チェック実行] を選んで、これらのイベントにサブスクライブします。
  4. ページの下部にある [変更の保存] をクリックします。
  5. アプリケーションを自分のアカウントにインストールしたなら、メールをチェックして、新しい権限を受諾するリンクに従ってく� さい。 アプリケーションの権限あるいはwebhookを変更した� �合、そのアプリケーションをインストールしたユーザ(自分自身を含む)は、変更が有効になる前に新しい権限を承認しなければなりません。 インストール ページに移動し、アプリの横にある [構成] をクリックして、新しいアクセス許可を受け入れることもできます。 アプリケーションが異なる権限を要求していることを知らせるバナーがページの上部に表示されます。 "Details(詳細)"をクリックし、"Accept new permissions(新しい権限を承認)"をクリックしてく� さい。

すばらしい。 アプリケーションは必要なタスクを実行する権限を所有しています。 これでイベントを処理するコードを追� できるようになりました。

ステップ 1.2. イベントの処理を追� する

アプリが チェック スイートチェック実行 イベントにサブスクライブされたので、check_suite および check_run Webhook の受信が始まります。 GitHub は、Webhook ペイロードを POST 要求として送信します。 Smee Webhook ペイロードを http://localhost/event_handler:3000 に転送したため、サーバーは post '/event_handler' ルートで POST 要求のペイロードを受信します。

空の post '/event_handler' ルートは、「前提条件」セクションでダウンロードした template_server.rb ファイルに既に含まれています。 空のルートは次のようになっています。

  post '/event_handler' do

    # # # # # # # # # # # #
    # ADD YOUR CODE HERE  #
    # # # # # # # # # # # #

    200 # success status
  end

以下のコードを追� することで、このルートを使って check_suite イベントを処理します。

# Get the event type from the HTTP_X_GITHUB_EVENT header
case request.env['HTTP_X_GITHUB_EVENT']
when 'check_suite'
  # A new check_suite has been created. Create a new check run with status queued
  if @payload['action'] == 'requested' || @payload['action'] == 'rerequested'
    create_check_run
  end
end

GitHub が送信する全てのイベントには、HTTP_X_GITHUB_EVENT という要求ヘッダーが含まれており、これは POST 要求でのイベントの種類を示します。 ここで関心のあるイベントの種類は check_suite � けであり、これは新しいチェック スイートが作成されると出力されます。 各イベントには、イベントをトリガーしたアクションの種類を示す追� の action フィールドがあります。 check_suite の� �合、action フィールドは requestedrerequested、または completed になります。

requested アクションはリポジトリにコードがプッシュされるたびにチェック実行を要求し、rerequested アクションはリポジトリに既に存在するコードのチェックの再実行を要求します。 requestedrerequested のどちらのアクションでもチェック実行を作成する必要があるため、create_check_run というヘルパーを呼び出します。 では、このメソッドを書いてみましょう。

ステップ 1.3. チェック実行を作成する

他のルートでも使う� �合に備えて、この新しいメソッドを Sinatra ヘルパーとして追� します。 helpers do の下に、次の create_check_run メソッドを追� します。

# Create a new check run with the status queued
def create_check_run
  @installation_client.create_check_run(
    # [String, Integer, Hash, Octokit Repository object] A GitHub repository.
    @payload['repository']['full_name'],
    # [String] The name of your check run.
    'Octo RuboCop',
    # [String] The SHA of the commit to check 
    # The payload structure differs depending on whether a check run or a check suite event occurred.
    @payload['check_run'].nil? ? @payload['check_suite']['head_sha'] : @payload['check_run']['head_sha'],
    # [Hash] 'Accept' header option, to avoid a warning about the API not being ready for production use.
    accept: 'application/vnd.github+json'
  )
end

このコードは、create_check_run メソッドを使って "チェック実行を作成する" エンドポイントを呼び出します。

チェック実行を作成するために必要な入力パラメーターは、namehead_sha の 2 つのみです。 このクイックスタートでは、後で RuboCop を使って CI テストを実装します。そのため、ここでは "Octo RuboCop" という名前を使いますが、チェック実行には任意の名前を選べます。

ここでは基本的な機能を実行するため必要なパラメータのみを指定していますが、チェック実行について必要な情� �を収集するため、後でチェック実行を更新することになります。 既定では、statusqueued に設定されます。

GitHub によって特定のコミット SHA に対するチェック実行が作成されるため、head_sha は必� �パラメーターです。 コミット SHA は、webhook ペイロードで確認できます。 今は check_suite イベントのためのチェック実行� けを作成していますが、head_sha はイベント ペイロードの check_suite オブジェクトと check_run オブジェクトの両方に含まれることを覚えておいてく� さい。

上のコードでは、if/else ステートメントのように機能する三� �演算子を使って、ペイロードに check_run オブジェクトが含まれるかどうかを調べています。 そうである� �合は check_run オブジェクトから head_sha を読み取り、そうでない� �合は check_suite オブジェクトから読み取ります。

このコードをテストするには、サーバーをターミナルから再起動します。

$ ruby template_server.rb

注: 変更をテストする前に、Sinatra サーバーを再起動する必要があります。 Ctrl-C キーを押してサーバーを停止し、ruby template_server.rb を再度実行します。 アプリのコードを変更するたびにこの作業を行いたくない� �合は、再読み込みに関するページを確認してく� さい。

さて、それではアプリケーションをインストールしたリポジトリにあるプルリクエストを開いてく� さい。 アプリケーションは応答し、プルリクエストのチェック実行を作成するはずです。 [チェック] タブをクリックすると、次のような内容が表示されます。

キューに入ったチェック実行

[チェック] タブに他のアプリが表示される� �合は、チェックに対する 読み取りと書き込み アクセス権を持ち、チェック スイート および チェック実行 イベントにサブスクライブしている他のアプリが、リポジトリにインストールされていることを意味します。

すばらしい。 ここまでで、GitHub にチェック実行を作成するよう指示しました。 黄色のアイコンの横で、チェック実行のステータスが queued に設定されていることを確認できます。 次は、GitHub がチェック実行を作成し、ステータスを更新するのを待てばよいでしょう。

ステップ 1.4. チェック実行を更新する

create_check_run メソッドは実行すると、GitHub に新しいチェック実行の作成を要求します。 GitHub でチェック実行の作成が完了すると、created アクションを含む check_run Webhook イベントを受け取ります。 このイベントは、チェックの実行を開始する合図です。

created アクションを待つように、イベント ハンドラーを更新します。 イベント ハンドラーを更新するときに、rerequested アクションに対する条件を追� できます。 [再実行] ボタンをクリックして GitHub 上で単一のテストを再実行すると、GitHub からアプリに rerequested チェック実行イベントが送信されます。 チェック実行が rerequested の� �合は、すべてのプロセスを開始してから、新しいチェック実行を作成します。

post '/event_handler' ルートに check_run イベントの条件を含めるには、case request.env['HTTP_X_GITHUB_EVENT'] の下に次のコードを追� します。

when 'check_run'
  # Check that the event is being sent to this app
  if @payload['check_run']['app']['id'].to_s === APP_IDENTIFIER
    case @payload['action']
    when 'created'
      initiate_check_run
    when 'rerequested'
      create_check_run
    end
  end

GitHub は created チェック実行に関するすべてのイベントを、リポジトリにインストールされていて必要なチェック アクセス許可を持つすべてのアプリに送信します。 これはつまり、あなたのアプリケーションが他のアプリケーションにより作成されたチェック実行を受信するということです。 created チェック実行は、チェックの実行を要求されているアプリのみに GitHub が送信する requestedrerequested チェック スイートとは少し違います。 上記のコードは、チェック実行のアプリケーション ID を待ち受けます。 リポジトリの他のアプリケーションに対するチェック実行はすべて遮断されます。

次に、initiate_check_run メソッドを作成します。そこでは、チェック実行のステータスを更新して、CI テストの開始を準備します。

このセクションでは、CI テストはま� 開始しませんが、チェック実行のステータスを queued から pending に更新し、さらに pending から completed に更新する手� �を調べることで、チェック実行のフロー全体を確認します。 「パート 2: Octo RuboCop CI テストを作成する」で、CI テストを実際に実行するコードを追� します。

initiate_check_run メソッドを作成し、チェック実行のステータスを更新しましょう。 以下のコードを helpers セクションに追� します。

# Start the CI process
def initiate_check_run
  # Once the check run is created, you'll update the status of the check run
  # to 'in_progress' and run the CI process. When the CI finishes, you'll
  # update the check run status to 'completed' and add the CI results.

  @installation_client.update_check_run(
    @payload['repository']['full_name'],
    @payload['check_run']['id'],
    status: 'in_progress',
    accept: 'application/vnd.github+json'
  )

  # ***** RUN A CI TEST *****

  # Mark the check run as complete!
  @installation_client.update_check_run(
    @payload['repository']['full_name'],
    @payload['check_run']['id'],
    status: 'completed',
    conclusion: 'success',
    accept: 'application/vnd.github+json'
  )
end

上のコードでは、update_check_run Octokit メソッドを使って "チェック実行を更新する" API エンドポイントを呼び出し、既に作成したチェック実行を更新しています。

このコードがしていることを説明しましょう。 まず、チェック実行のステータスを in_progress に更新し、started_at の日時を現在の日時に暗黙的に設定します。 このクイックスタートの「パート 2」では、実際の CI テストを開始するコードを ***** RUN A CI TEST ***** の下に追� します。 今はこのセクションをプレースホルダーとして残しておきましょう。そうすると、続くコードが CI のプロセスを成功させ、すべてのテストに合� �したことをシミュレートすることになります。 最後に、コードでチェック実行のステータスを再び completed に更新します。

チェック実行を更新する」のドキュメントを見ると、completed ステータスを指定するときは、conclusioncompleted_at パラメーターが必� �であることがわかります。 conclusion はチェック実行の結果の要約であり、successfailureneutralcancelledtimed_out、または action_required を指定できます。 conclusion は success に、completed_at の日時は現在の日時に、ステータスは completed に設定します。

チェックが行っていることについてより詳しく指定することもできますが、それは次のセクションで行うことにします。 template_server.rb を実行し直して、このコードをもう一度テストしてみましょう。

$ ruby template_server.rb

開いている pull request に移動し、 [チェック] タブをクリックします。左上隅にある [すべて再実行] ボタンをクリックします。 チェック実行が pending から in_progress に変わり、success で終わることを確認できるはずです。

完了したチェック実行

第 2 部 Octo RuboCop CI テストを作成する

RuboCop は Ruby コードのリンターおよびフォーマッタです。 Ruby のコードが「Ruby スタイル ガイド」に準� していることをチェックします。 RuboCop の主な機能は、以下の 3 つです。

  • コードのスタイルを確認する文法チェック
  • コードのフォーマット
  • ruby -w を使って Ruby のネイティブ リンティング機能を置き換える

さて、Checks API を受信し、チェック実行を作成するために作ったインターフェースができあがったところで、今度は CI テストを実装するチェック実行を作成しましょう。

あなたのアプリケーションは CI サーバー上の RuboCop で実行され、結果を RuboCop が GitHub に� �告するチェック実行 (ここでは CI テスト) を作成します。

Checks API を使用すると、ステータス、画像、要約、アノテーション、リクエストされたアクションなどの、各チェック実行の詳細情� �を� �告できます。

アノテーションとは、リポジトリのコードの特定の行についての情� �です。 アノテーションを使用すると、追� 情� �を表示したいコードの部分を細かく指定して、それを視覚化できます。 この情� �は、たとえばコメント、エラー、警告など何でも構いません。 このクイックスタートでは、RuboCop のエラーを視覚化するためにアノテーションを使用します。

要求されたアクションを利用するため、アプリ開発者は pull request の [チェック] タブにボタンを作成できます。 これらのボタンをクリックすると、クリックから GitHub アプリに requested_action check_run イベントが送信されます。 アプリケーションが実行するアクションは、アプリケーション開発者が自由に設定できます。 このクイックスタートでは、RuboCop が見つけたエラーを修正するようユーザがリクエストするためのボタンを追� する方法について説明します。 RuboCop はコマンド ライン オプションを使ったエラーの自動修正をサポートしており、ここでは requested_action を構成してこのオプションを利用します。

それでは作業を始めましょう。 このセクションでは、以下のステップを完了させます。

  1. Ruby ファイルを追� する
  2. リポジトリの複製
  3. RuboCop を実行する
  4. RuboCop のエラーを収集する
  5. CI テスト結果でチェック実行を更新する
  6. RuboCop のエラーを自動的に修正する
  7. セキュリティのヒント

ステップ 2.1. Ruby ファイルを追� する

RuboCop がチェックするため、特定のファイルまたはディレクトリ全体を渡すことができます。 このクイックスタートでは、ディレクトリ全体で RuboCop を実行します。 RuboCop がチェックするのは Ruby のコードのみなので、エラーが含まれる Ruby ファイルをリポジトリ内に最低 1 つ置くとよいでしょう。 以下に示すサンプルのファイルには、いくつかのエラーが含まれています。 この例の Ruby ファイルを、アプリがインストールされているリポジトリに追� します (myfile.rb のように、ファイル名には必ず .rb 拡張子を付けます)。

# The Octocat class tells you about different breeds of Octocat
class Octocat
  def initialize(name, *breeds)
    # Instance variables
    @name = name
    @breeds = breeds
  end

  def display
    breed = @breeds.join("-")

    puts "I am of #{breed} breed, and my name is #{@name}."
  end
end

m = Octocat.new("Mona", "cat", "octopus")
m.display

ステップ 2.2. リポジトリの複製

RuboCop はコマンドラインユーティリティとして使用できます。 これはつまり、RuboCop がファイルを解析するためには、GitHub App が CI サーバー上のリポジトリのローカルコピーをクローンする必要があるということです。 Ruby アプリで Git の操作を実行するには、ruby-git gem を使えます。

building-a-checks-api-ci-server リポジトリの Gemfile には既に ruby-git gem が含まれており、前提条件ステップbundle install を実行したときにインストール済みです。 gem を使うには、次のコードを template_server.rb ファイルの先� �に追� します。

require 'git'

リポジトリをクローンするには、アプリケーションに「リポジトリコンテンツ」の読み取り権限が必要です。 このクイックスタートでは、後ほどコンテンツを GitHub にプッシュする必要がありますが、そのためには書き込み権限が必要です。 ここでアプリの "リポジトリの内容" のアクセス許可を [読み取りと書き込み] に設定しておけば、後で再び変更する必要がなくなります。 アプリケーションの権限を更新するには、以下の手� �に従います。

  1. アプリの設定ページでアプリを選び、サイドバーの [アクセス許可と Webhook] をクリックします。
  2. [アクセス許可] セクションで "リポジトリの内容" を探し、その横にある [アクセス] ドロップダウンで [読み取りと書き込み] を選びます。
  3. ページの下部にある [変更の保存] をクリックします。
  4. アプリケーションを自分のアカウントにインストールしたなら、メールをチェックして、新しい権限を受諾するリンクに従ってく� さい。 アプリケーションの権限あるいはwebhookを変更した� �合、そのアプリケーションをインストールしたユーザ(自分自身を含む)は、変更が有効になる前に新しい権限を承認しなければなりません。 インストール ページに移動し、アプリの横にある [構成] をクリックして、新しいアクセス許可を受け入れることもできます。 アプリケーションが異なる権限を要求していることを知らせるバナーがページの上部に表示されます。 "Details(詳細)"をクリックし、"Accept new permissions(新しい権限を承認)"をクリックしてく� さい。

GitHub App のアクセス許可を使ってリポジトリをクローンするには、次の例で示すアプリのインストール トークン (x-access-token:<token>) を使えます。

git clone https://x-access-token:@github.com//.git

上記のコードは、HTTP 経由でリポジトリをクローンします。 コードには、リポジトリの所有者 (ユーザまたは Organization) およびリポジトリ名を含む、リポジトリのフルネー� を入力する必要があります。 たとえば、octocat Hello-World リポジトリのフル ネー� は octocat/hello-world です。

アプリでは、リポジトリをクローンした後、最新のコード変更をプルし、特定の Git ref をチェックアウトする必要があります。これらのすべてを行うコードは、専用のメソッドにするのに最適です。 メソッドがこれらの操作を実行するには、リポジトリの名前とフルネー� 、チェックアウトする ref が必要です。 ref にはコミット SHA、ブランチ、タグ名を指定できます。 次の新しいメソッドを template_server.rb のヘルパー メソッド セクションに追� します。

# Clones the repository to the current working directory, updates the
# contents using Git pull, and checks out the ref.
#
# full_repo_name  - The owner and repo. Ex: octocat/hello-world
# repository      - The repository name
# ref             - The branch, commit SHA, or tag to check out
def clone_repository(full_repo_name, repository, ref)
  @git = Git.clone("https://x-access-token:#{@installation_token.to_s}@github.com/#{full_repo_name}.git", repository)
  pwd = Dir.getwd()
  Dir.chdir(repository)
  @git.pull
  @git.checkout(ref)
  Dir.chdir(pwd)
end

上のコードでは、ruby-git gem を使い、アプリのインストール トークンを使ってリポジトリをクローンしています。 このコードでは、template_server.rb と同じディレクトリにコードがクローンされます。 リポジトリで Git コマンドを実行するには、コードをリポジトリのディレクトリに変更する必要があります。 ディレクトリを変更する前に、コードで現在の作業ディレクトリを変数 (pwd) に保存して戻る� �所を記憶した後、clone_repository メソッドを終了します。

このコードは、リポジトリのディレクトリから最新の変更をフェッチしてマージし (@git.pull)、ref をチェックアウトしてから (@git.checkout(ref))、元の作業ディレクトリに戻ります (pwd)。

これで、リポジトリをクローンして ref をチェックアウトするメソッドができました。次に、必要な入力パラメーターを取得して新しい clone_repository メソッドを呼び出すコードを追� する必要があります。 initiate_check_run ヘルパー メソッドの ***** RUN A CI TEST ***** というコメントの下に、次のコードを追� します。

# ***** RUN A CI TEST *****
full_repo_name = @payload['repository']['full_name']
repository     = @payload['repository']['name']
head_sha       = @payload['check_run']['head_sha']

clone_repository(full_repo_name, repository, head_sha)

上のコードは、リポジトリのフル ネー� とコミットのヘッド SHA を、check_run Webhook ペイロードから取得します。

ステップ 2.3. RuboCop を実行する

すばらしい。 リポジトリをクローンし、CI サーバーを使用してチェック実行を作成しようという段階にまで到達しました。 ここでは、RuboCop リンターChecks API ノーテーションの� �心部分に踏み込みます。

次のコードは、RuboCop を実行し、スタイル コード エラーを JSON フォーマットで保存します。 前のステップで追� した clone_repository の呼び出しの下、チェック実行を更新するコードの上に、このコードを追� して完了です。

# Run RuboCop on all files in the repository
@report = `rubocop '#{repository}' --format json`
logger.debug @report
`rm -rf #{repository}`
@output = JSON.parse @report

上記のコードは、リポジトリのディレクトリにある全てのファイルで RuboCop を実行します。 オプション --format json は、リンティングの結果のコピーをコンピューターで解析可能な形式で保存する便利な方法です。 JSON 形式の詳細と例については、RuboCop のドキュメントをご覧く� さい。

このコードは RuboCop の結果を @report 変数に� �納するため、リポジトリのチェックアウトを安全に削除できます。 また、このコードは JSON も解析するため、@output 変数を使って GitHub App のキーと変数に簡単にアクセスできます。

注: リポジトリの削除に使うコマンド (rm -rf) を元に戻すことはできません。 アプリで意図したものとは異なるディレクトリを削除するために使われる可能性がある悪意のあるコマンドの挿入を Webhook で調べる方法については、「ステップ 2.7. セキュリティのヒント」をご覧く� さい。 たとえば、悪意のあるアクターが ./ というリポジトリ名を使って Webhook を送信した� �合、アプリはルート ディレクトリを削除します。 😱何らかの理由で メソッド (に含まれている) を使用して Webhook の送信者を検証verify_webhook_signatureしていないtemplate_server.rb� �合は、リポジトリ名が有効であることを確認してく� さい。

このコードが動作することをテストし、サーバーのデバッグ出力で RuboCop により� �告されたエラーを確認できます。 template_server.rb サーバーを再起動し、アプリをテストしているリポジトリで新しい pull request を作成します。

$ ruby template_server.rb

デバッグ出力に文法エラーが表示されているはずです。た� し、出力は整形されていません。 JSON フォーマッタのような Web ツールを使うと、JSON 出力を次のように書式設定されたリンティング エラー出力に整形できます。

{
  "metadata": {
    "rubocop_version": "0.60.0",
    "ruby_engine": "ruby",
    "ruby_version": "2.3.7",
    "ruby_patchlevel": "456",
    "ruby_platform": "universal.x86_64-darwin18"
  },
  "files": [
    {
      "path": "Octocat-breeds/octocat.rb",
      "offenses": [
        {
          "severity": "convention",
          "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.",
          "cop_name": "Style/StringLiterals",
          "corrected": false,
          "location": {
            "start_line": 17,
            "start_column": 17,
            "last_line": 17,
            "last_column": 22,
            "length": 6,
            "line": 17,
            "column": 17
          }
        },
        {
          "severity": "convention",
          "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.",
          "cop_name": "Style/StringLiterals",
          "corrected": false,
          "location": {
            "start_line": 17,
            "start_column": 25,
            "last_line": 17,
            "last_column": 29,
            "length": 5,
            "line": 17,
            "column": 25
          }
        }
      ]
    }
  ],
  "summary": {
    "offense_count": 2,
    "target_file_count": 1,
    "inspected_file_count": 1
  }
}

ステップ 2.4. RuboCop のエラーを収集する

@output 変数には、RuboCop レポートの解析済みの JSON の結果が含まれます。 上で示すように、結果には summary セクションが含まれており、コードでエラーがあるかどうかを迅速に判断するために使えます。 次のコードは、エラーが� �告されないときは、チェック実行の結果を success に設定します。 RuboCop は files 配列で各ファイルのエラーを� �告するので、エラーがある� �合、file オブジェクトからデータを抽出する必要があります。

Checks API により、コードの特定の行に対してアノテーションを作成することができます。 チェック実行を作成または更新する際に、アノテーションを追� できます。 このクイックスタートでは、アノテーションを使ってチェック実行を更新しています。

Checks API では、アノテーションの数は API の 1 リクエストあたり最大 50 に制限されています。 51 個以上のアノテーションを作るには、"チェック実行を更新する" エンドポイントに対して要求を複数回行う必要があります。 たとえば、105 個のアノテーションを作るには、"チェック実行を更新する" エンドポイントを 3 回呼び出す必要があります。 始めの 2 回のリクエストでそれぞれ 50 個のアノテーションが作成され、3 回目のリクエストで残り 5 つのアノテーションが作成されます。 チェック実行を更新するたびに、アノテーションは既存のチェック実行にあるアノテーションのリストに追� されます。

チェック実行は、アノテーションをオブジェクトの配列として受け取ります。 各アノテーション オブジェクトには、pathstart_lineend_lineannotation_levelmessage が含まれている必要があります。 RuboCop により start_columnend_column も提供されるので、それらのオプションのパラメーターをアノテーションに含めることができます。 アノテーションの start_columnend_column は、同じ行でのみサポートされます。 詳しくは、annotations オブジェクトの参照ドキュメントをご覧く� さい。

各アノテーションを作成するために必要な RuboCop から、必� �の情� �を抽出します。 前のセクションで追� したコードの後に次のコードを追� します。

annotations = []
# You can create a maximum of 50 annotations per request to the Checks
# API. To add more than 50 annotations, use the "Update a check run" API
# endpoint. This example code limits the number of annotations to 50.
# See /rest/reference/checks#update-a-check-run
# for details.
max_annotations = 50

# RuboCop reports the number of errors found in "offense_count"
if @output['summary']['offense_count'] == 0
  conclusion = 'success'
else
  conclusion = 'neutral'
  @output['files'].each do |file|

    # Only parse offenses for files in this app's repository
    file_path = file['path'].gsub(/#{repository}\//,'')
    annotation_level = 'notice'

    # Parse each offense to get details and location
    file['offenses'].each do |offense|
      # Limit the number of annotations to 50
      next if max_annotations == 0
      max_annotations -= 1

      start_line   = offense['location']['start_line']
      end_line     = offense['location']['last_line']
      start_column = offense['location']['start_column']
      end_column   = offense['location']['last_column']
      message      = offense['message']

      # Create a new annotation for each error
      annotation = {
        path: file_path,
        start_line: start_line,
        end_line: end_line,
        start_column: start_column,
        end_column: end_column,
        annotation_level: annotation_level,
        message: message
      }
      # Annotations only support start and end columns on the same line
      if start_line == end_line
        annotation.merge({start_column: start_column, end_column: end_column})
      end

      annotations.push(annotation)
    end
  end
end

このコードでは、アノテーションの合計数を 50 に制限しています。 このコードを、50 のアノテーションごとにチェック実行を更新するよう変更することも可能です。 上のコードには、違反を反復処理するループで使われる変数 max_annotations が含まれていて、制限が 50 に設定されています。

offense_count が 0 の� �合、CI テストは success になります。 エラーがある� �合、このコードは結果を neutral に設定します。これは、コード リンターによってエラーが厳� �に強制されるのを防ぐためです。 た� し、リンティング エラーがある� �合にチェックスイートが失敗になるようにしたい� �合は、結果を failure に変更できます。

エラーが� �告されると、上のコードは RuboCop レポートの files 配列を反復処理します。 各ファイルについて、ファイル パスが抽出され、アノテーション レベルが notice に設定されます。 さらに細かく、RuboCop Cop の各種類に特定の警告レベルを設定することもできますが、このクイックスタートでは簡単さを優先し、すべてのエラーを notice のレベルに設定します。

このコードでは、また、offenses 配列の各エラーを反復処理し、違反の� �所とエラー メッセージを収集します。 必要な情� �を抽出した後、コードでエラーごとにアノテーションを作成して、annotations 配列に� �納します。 アノテーションは開始列と終了列が同じ行にある� �合にのみサポートされるため、開始行と終了行の値が同じ� �合に� け、start_columnend_columnannotation オブジェクトに追� されます。

このコードはま� チェック実行のアノテーションを作成しません。 それを作成するコードは、次のセクションで追� します。

ステップ 2.5. CI テスト結果でチェック実行を更新する

GitHub からの各チェック実行には、titlesummarytextannotationsimages を含む output オブジェクトが含まれます。 output の必� �パラメーターは summarytitle � けですが、それら� けでは十分に詳細な情� �が提供されないので、このクイックスタートでは textannotations も追� します。 ここに挙げるコードでは画像は追� しませんが、追� したければぜひどうぞ。

summary については、この例では RuboCop からの概要情� �を使い、出力の書式を設定するために改行 (\n) をいくつか追� します。 text パラメーターに追� するものはカスタマイズできますが、この例では RuboCop のバージョンを text パラメーターに設定します。 summarytext を設定するには、前のセクションで追� したコードに次のコードを追� します。

# Updated check run summary and text parameters
summary = "Octo RuboCop summary\n-Offense count: #{@output['summary']['offense_count']}\n-File count: #{@output['summary']['target_file_count']}\n-Target file count: #{@output['summary']['inspected_file_count']}"
text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}"

さて、これでチェック実行を更新するために必要な情� �がすべて揃いました。 このクイックスタートの前半では、チェック実行のステータスを success に設定するためにこのコードを追� しました。

# Mark the check run as complete!
@installation_client.update_check_run(
  @payload['repository']['full_name'],
  @payload['check_run']['id'],
  status: 'completed',
  conclusion: 'success',
  accept: 'application/vnd.github+json'
)

そのコードを、RuboCop の結果に基づいて (success または neutral に) 設定した conclusion 変数を使うように、更新する必要があります。 コードは以下のようにして更新できます。

# Mark the check run as complete! And if there are warnings, share them.
@installation_client.update_check_run(
  @payload['repository']['full_name'],
  @payload['check_run']['id'],
  status: 'completed',
  conclusion: conclusion,
  output: {
    title: 'Octo RuboCop',
    summary: summary,
    text: text,
    annotations: annotations
  },
  actions: [{
    label: 'Fix this',
    description: 'Automatically fix all linter notices.',
    identifier: 'fix_rubocop_notices'
  }],
  accept: 'application/vnd.github+json'
)

さて、これで CI テストのステータスに基づいて結論を設定し、RuboCop の結果からの出力を追� しました。あなたは CI テストを作成したのです。 お疲れさまでした。 🙌

また、上のコードでは、actions オブジェクトによって CI サーバーに要求されたアクションという機能も追� されます。 "要求されたアクション" により、追� アクションの実行をチェック実行に要求できるボタンが GitHub の [チェック] タブに追� されます。 追� のアクションは、アプリケーションが自由に設定できます。 たとえば、RuboCop には Ruby のコードで見つかったエラーを自動的に修正する機能があるので、CI サーバーはリクエストされたアクションボタンを使用して、自動的なエラー修正をユーザが許可することができます。 このボタンをクリックすると、アプリは requested_action アクションを含む check_run イベントを受け取ります。 アプリでは、各要求されたアクションに含まれる identifier を使って、クリックされたボタンを特定します。

上記のコードには、ま�  RuboCop が自動的にエラーを修正する処理がありません。 この処理については、次のセクションで追� します。 た� し、まず、template_server.rb サーバーをもう一度起動して新しい pull request を作成することで、作成した CI テストを見てみましょう。

$ ruby template_server.rb

アノテーションが [チェック] タブに表示されます。

[Checks] タブのチェック実行アノテーション

リクエストされたアクションを追� することにより作成された [Fix this] ボタンに注目してく� さい。

チェック実行のリクエストされたアクションのボタン

既に PR に含まれるファイルにアノテーションが関連している� �合、 [変更されたファイル] タブにもそのアノテーションが表示されます。

ファイルが変更されたタブのチェック実行アノテーション

ステップ 2.6. RuboCop のエラーを自動的に修正する

ここまで来たのはすごいですよ! 👏 CI テストの作成は済んでいます。 このセクションでは、もう 1 つの機能を追� します。RuboCop を使用して、見つけたエラーを自動的に修正するために使用するための機能です。 [これを修正する] ボタンは、前のセクションで既に追� しました。 ここでは、[これを修正する] ボタンがクリックされるとトリガーされる requested_action チェック実行イベントを処理するコードを追� します。

RuboCop ツールには、検出されたエラーを自動的に修正するための --auto-correct コマンド ライン オプションが用意されています--auto-correct 機能を使うと、サーバー上のローカル ファイルに更新が適用されます。 RuboCop がこの作業をやってのけた後は、その変更を GitHub にプッシュする必要があります。

リポジトリにブッシュするには、アプリケーションに [Repository contents] への書き込み権限が必要です。 このアクセス許可は、「ステップ 2.2. リポジトリの複製」で既に 読み取りと書き込み に設定してあるので、準備はすべて整っています。

ファイルをコミットするには、Git はコミットに関連付けるユーザー名メール アドレスを認識する必要があります。 名前 (GITHUB_APP_USER_NAME) とメール アドレス (GITHUB_APP_USER_EMAIL) の設定を� �納するための 2 つの環境変数を、.env ファイルに追� します。 アプリケーションにはあなたの名前を付けることもできます。この例では、メールアドレスは何でも構いません。 次に例を示します。

GITHUB_APP_USER_NAME=Octoapp
GITHUB_APP_USER_EMAIL=octoapp@octo-org.com

作成者およびコミットしたユーザーの名前とメール アドレスで .env ファイルを更新したら、環境変数を読み取って Git 構成を設定するコードを追� する準備が整います。 このコードは、もうすぐ追� することになります。

[これを修正する] ボタンをクリックすると、アプリは requested_action アクションの種類を含むチェック実行 Webhook を受信します。

ステップ 1.4. チェック実行を更新する」で、check_run イベント内のアクションを検索するように event_handler を更新しました。 createdrerequested のアクションの種類を処理する case ステートメントは既に存在します。

when 'check_run'
  # Check that the event is being sent to this app
  if @payload['check_run']['app']['id'].to_s === APP_IDENTIFIER
    case @payload['action']
    when 'created'
      initiate_check_run
    when 'rerequested'
      create_check_run
  end
end

rerequested_action イベントを処理するための別の when ステートメントを、rerequested ケースの後に追� します。

when 'requested_action'
  take_requested_action

このコードは、アプリに対するすべての requested_action イベントを処理する新しいメソッドを呼び出します。 以下のメソッドをコードのヘルパーメソッドセクションに追� します。

# Handles the check run `requested_action` event
# See /webhooks/event-payloads/#check_run
def take_requested_action
  full_repo_name = @payload['repository']['full_name']
  repository     = @payload['repository']['name']
  head_branch    = @payload['check_run']['check_suite']['head_branch']

  if (@payload['requested_action']['identifier'] == 'fix_rubocop_notices')
    clone_repository(full_repo_name, repository, head_branch)

    # Sets your commit username and email address
    @git.config('user.name', ENV['GITHUB_APP_USER_NAME'])
    @git.config('user.email', ENV['GITHUB_APP_USER_EMAIL'])

    # Automatically correct RuboCop style errors
    @report = `rubocop '#{repository}/*' --format json --auto-correct`

    pwd = Dir.getwd()
    Dir.chdir(repository)
    begin
      @git.commit_all('Automatically fix Octo RuboCop notices.')
      @git.push("https://x-access-token:#{@installation_token.to_s}@github.com/#{full_repo_name}.git", head_branch)
    rescue
      # Nothing to commit!
      puts 'Nothing to commit'
    end
    Dir.chdir(pwd)
    `rm -rf '#{repository}'`
  end
end

上のコードは、「ステップ 2.2. リポジトリの複製」で追� したコードと同じようにリポジトリをクローンします。 if ステートメントは、要求されたアクションの識別子が RuboCop のボタン識別子 (fix_rubocop_notices) と一致することを確認します。 それらが一致する� �合、コードはリポジトリをクローンし、Git のユーザー名とメール アドレスを設定してから、オプション --auto-correct を指定して RuboCop を実行します。 --auto-correct オプションは、ローカル環境の CI サーバー ファイルに変更を自動的に適用します。

ファイルはローカルで変更されますが、それを GitHub にプッシュする必要はあります。 もう一度便利な ruby-git gem を使って、すべてのファイルをコミットします。 Git には、変更または削除されたすべてのファイルをステージングし、それらをコミットする git commit -a というコマンドがあります。 ruby-git を使って同じことを行うため、上のコードでは commit_all メソッドを使っています。 その後、Git の clone コマンドと同じ認証方法を使い、インストール トークンを使って GitHub にコミットしたファイルをプッシュします。 最後に、リポジトリディレクトリを削除して、ワーキングディレクトリが次のイベントに備えるようにします。

これで完了です。 Checks API CI サーバーのコードがついに完成しました。 💪 template_server.rb サーバーをもう一度再起動し、新しい pull request を作成します。

$ ruby template_server.rb

注: 変更をテストする前に、Sinatra サーバーを再起動する必要があります。 Ctrl-C キーを押してサーバーを停止し、ruby template_server.rb を再度実行します。 アプリのコードを変更するたびにこの作業を行いたくない� �合は、再読み込みに関するページを確認してく� さい。

今回は、[これを修正する] ボタンをクリックすると、RuboCop が [チェック] タブで検出したエラーが自動的に修正されます。

[コミット] タブには、Git の構成で設定したユーザー名で新しいコミットが表示されます。 更新を確認するには、ブラウザを更新する必要がある� �合があります。

Octo RuboCop の通知を自動的に修正する新しいコミット

新しいコミットがリポジトリにプッシュされたので、 [チェック] タブに Octo RuboCop の新しいチェック スイートが表示されています。しかし、今回は RuboCop によってすべて修正されたためエラーはありません。 🎉

チェックスイート、チェック実行のエラーなし

作成したアプリの完成したコードは、「Checks API で CI テストを作成する」のリポジトリの server.rb ファイルにあります。

ステップ 2.7. セキュリティのヒント

GitHub App コードのテンプレートには、受信した webhook ペイロードを検証して、信� �できるソースからのものであることを確認するためのメソッドが最初から用意されています。 webhook ペイロードを検証しない� �合、リポジトリ名が webhook ペイロードに含まれる際には、その webhook が悪意をもって使用されかねない任意のコマンドを確実に含まないようにする必要があります。 以下のコードは、リポジトリ名に含まれる文字がラテン文字、ハイフン、アンダースコアのみであることを検証します。 完全な例を提供するため、このクイックスタートのコンパニオン リポジトリで入手できる完全な server.rb のコードには、受信した Webhook ペイロードを検証するメソッドと、リポジトリ名を検証するためのこのチェックの両方が含まれています。

# This quickstart example uses the repository name in the webhook with
# command-line utilities. For security reasons, you should validate the
# repository name to ensure that a bad actor isn't attempting to execute
# arbitrary commands or inject false repository names. If a repository name
# is provided in the webhook, validate that it consists only of latin
# alphabetic characters, `-`, and `_`.
unless @payload['repository'].nil?
  halt 400 if (@payload['repository']['name'] =~ /[0-9A-Za-z\-\_]+/).nil?
end

トラブルシューティング

以下は、いくつかの一般的な問題と推奨される解決策です。 他の問題が発生した� �合は、GitHub コミュニティでの API と統合に関するディスカッション にヘルプやアドバイスを求めることができます。

  • Q: アプリで GitHub にコードがプッシュされません。 RuboCop が自動的に行う修正が表示されません。

    A: "リポジトリの内容" に対する 読み取りと書き込み アクセス許可があること、およびインストール トークンを使ってリポジトリをクローンしていることを確認します。 詳しくは、「ステップ 2.2. リポジトリの複製」をご覧く� さい。

  • Q: template_server.rb のデバッグ出力に、リポジトリのクローンに関するエラーが表示されます。

    A: 以下のエラーが表示される� �合、initiate_check_runtake_requested_action メソッドの一方または両方で、リポジトリのチェックアウトを削除していません。

    2018-11-26 16:55:13 - Git::GitExecuteError - git  clone '--' 'https://x-access-token:ghs_9b2080277016f797074c4dEbD350745f4257@github.com/codertocat/octocat-breeds.git' 'Octocat-breeds'  2>&1:fatal: destination path 'Octocat-breeds' already exists and is not an empty directory.:

    自分のコードを server.rb ファイルと比較し、initiate_check_run および take_requested_action メソッドのコードが同じであることを確認してく� さい。

  • Q: 新しいチェック実行が、GitHub の [チェック] タブに表示されません。

    A: Smee を再起動し、template_server.rb サーバーを実行し直してく� さい。

  • Q: [すべて再実行] ボタンが GitHub の [チェック] タブに表示されません。

    A: Smee を再起動し、template_server.rb サーバーを実行し直してく� さい。

まとめ

このガイドの手� �を一通り終えたら、Checks API を使用して CI サーバーを作成することの基本が習得できています。 振り返ると、以下を行いました。

  • Checks API イベントを受信し、チェック実行を作成するようサーバーを設定しました。
  • リポジトリ内のコードをチェックし、エラーのアノテーションを作成するため RuboCop を使用しました。
  • 文法エラーを自動的に修正する、リクエストされたアクションを実装しました。

次の手� �

以下は、次に行えることのいくつかのアイデアです。

  • 現在、[Fix this] ボタンは常に表示されています。 ここまで書いたコードを更新し、RuboCop がエラーを見つけた時にのみ [Fix this] ボタンが表示されるようにしましょう。
  • RuboCop で head ブランチにファイルを直接コミットしたくない� �合は、head ブランチに基づいて新しいブランチで pull request を作成するようにコードを更新できます。