はじめに
このガイドでは、GitHub App と Checks API について説明します。これらを使って、テストを実行する継続的インテグレーション (CI) サーバーを構築します。
CI とは、ソフトウェアの開発においてコードを頻繁に共有リポジトリにコミットする手法のことです。 コードをコミットする頻度が高いほどエラーの発生が早くなり、開発者がエラーの原因を見つけようとしてデバッグする必要性も減ります。 コードの更新が頻繁であれば、ソフトウェア開発チームの他のメンバーによる変更をマージするのも、それだけ容易になります。 コードの記述により多くの時間をかけられるようになり、エラーのデバッグやマージコンフリクトの解決にかける時間が減るので、これは開発者にとって素晴らしいやり方です。 🙌
CI サーバーは、コードの文法チェッカー (スタイルフォーマットをチェックする)、セキュリティチェック、コード網羅率、その他のチェックといった CI テストをリポジトリの新しいコードコミットに対して実行するコードをホストします。 CI サーバーは、ステージングサーバーや本番サーバーにコードを構築しデプロイすることも可能です。 GitHub App で作ることができる CI テストの種類の例については、GitHub Marketplace で手に入る継続的インテグレーション アプリを確認してください。
メモ: このガイドでは、Ruby プログラミング言語を使用したアプリ開発のプロセスを示します。 ただし、Octokit にはさまざまな決まりごとがあります。 JavaScript を使用する場合は、Probot と Node.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
イベントを自動的に作成しますが、必要に応じてチェック スイートに関するリポジトリ設定を更新できます。 デフォルトのフローは以下の通りです。
- リポジトリにコードがプッシュされるたびに、GitHub は、リポジトリにインストールされていて
checks:write
アクセス許可を持つすべての GitHub App に、requested
アクションを含むcheck_suite
イベントを送信します。 このイベントにより、コードがプッシュされたことと、GitHub が新しいチェックスイートを自動的に作成したことがアプリケーションに通知されます。 - アプリでは、このイベントを受信したら、そのスイートにチェック実行を追加できます。
- チェック実行には、特定のコード行に表示されるアノテーションを含めることができます。
このガイドでは、次の方法について説明します。
- パート 1: Checks API を使用して CI サーバー用のフレームワークをセットアップする。
- Checks API イベントを受信するサーバーとして GitHub App を構成します。
- 新たにプッシュされたコミットをリポジトリが受信した時に、CI テスト用の新しいチェック実行を作成します。
- ユーザが GitHub でチェック実行のアクションをリクエストした時に、チェック実行を再実行します。
- パート 2: 文法チェッカー CI テストを追加して、作成した CI サーバーフレームワークを基に構築する。
status
、conclusion
、output
の詳細を使って、チェック実行を更新します。- pull request の [チェック] および [変更されたファイル] タブに表示されるコード行のアノテーションを作成します。
- pull request の [チェック] タブに [これを修正する] ボタンを表示して、リンターによる推奨事項を自動的に修正します。
前提条件
GitHub アプリ、Webhook、Checks 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 テストを作成する前に、以下を行う必要があります。
-
「Checks API で CI テストを作成する」のリポジトリをクローンします。
$ git clone https://github.com/github-developer/creating-ci-tests-with-the-checks-api.git
ディレクトリの中には、このクイックスタートで使うテンプレート コードを含む
template_server.rb
ファイルと、完成したプロジェクト コードを含むserver.rb
ファイルがあります。 -
「GitHub Appを作成するための開発環境のセットアップ」クイックスタートに記載の手順に従い、アプリ サーバーを構成し、実行します。 注: 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 に接続している必要があります。 「GitHub Appを作成するための開発環境のセットアップ」の手順を完了していない場合は、続ける前にそれを行う必要があります。
それでは作業を始めましょう。 パート 1 では、以下のステップを完了させます。
ステップ 1.1. アプリケーションの権限を更新する
最初にアプリを登録したときは、既定のアクセス許可を受け入れました。これは、アプリがほとんどのリソースにアクセスできないことを意味します。 この例においては、アプリケーションにはチェックを読み取りおよび書き込みする権限が必要となります。
アプリケーションの権限を更新するには、以下の手順に従います。
- アプリの設定ページでアプリを選び、サイドバーの [アクセス許可と Webhook] をクリックします。
- [アクセス許可] セクションで [チェック] を探し、その横にある [アクセス] ドロップダウンで [読み取りと書き込み] を選びます。
- [イベントへのサブスクライブ] セクションで、 [チェック スイート] と [チェック実行] を選んで、これらのイベントにサブスクライブします。
- ページの下部にある [変更の保存] をクリックします。
- アプリケーションを自分のアカウントにインストールしたなら、メールをチェックして、新しい権限を受諾するリンクに従ってください。 アプリケーションの権限あるいはwebhookを変更した場合、そのアプリケーションをインストールしたユーザ(自分自身を含む)は、変更が有効になる前に新しい権限を承認しなければなりません。 インストール ページに移動し、アプリの横にある [構成] をクリックして、新しいアクセス許可を受け入れることもできます。 アプリケーションが異なる権限を要求していることを知らせるバナーがページの上部に表示されます。 "Details(詳細)"をクリックし、"Accept new permissions(新しい権限を承認)"をクリックしてください。
すばらしい。 アプリケーションは必要なタスクを実行する権限を所有しています。 これでイベントを処理するコードを追加できるようになりました。
ステップ 1.2. イベントの処理を追加する
アプリがチェック スイートとチェック実行イベントにサブスクライブされたので、check_suite
および check_run
Webhook の受信が始まります。 GitHub は、Webhook ペイロードを POST
要求として送信します。 Smee Webhook ペイロードを http://localhost:3000/event_handler
に転送したため、サーバーは 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
フィールドは requested
、rerequested
、または completed
になります。
requested
アクションはリポジトリにコードがプッシュされるたびにチェック実行を要求し、rerequested
アクションはリポジトリに既に存在するコードのチェックの再実行を要求します。 requested
と rerequested
のどちらのアクションでもチェック実行を作成する必要があるため、create_check_run
というヘルパーを呼び出します。 では、このメソッドを書いてみましょう。
ステップ 1.3. チェック実行を作成する
他のルートでも使う場合に備えて、この新しいメソッドを Sinatra ヘルパーとして追加します。 helpers do
の下に、次の create_check_run
メソッドを追加します。
# Create a new check run with 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 メソッドを使って "チェック" エンドポイントを呼び出します。
チェック実行を作成するために必要な入力パラメーターは、name
と head_sha
の 2 つのみです。 このクイックスタートでは、後で RuboCop を使って CI テストを実装します。そのため、ここでは "Octo RuboCop" という名前を使いますが、チェック実行には任意の名前を選べます。
ここでは基本的な機能を実行するため必要なパラメータのみを指定していますが、チェック実行について必要な情報を収集するため、後でチェック実行を更新することになります。 既定では、status
は queued
に設定されます。
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
を再度実行します。 アプリのコードを変更するたびにこの作業を行いたくない場合は、再読み込みに関するページを確認してください。
さて、それではアプリケーションをインストールしたリポジトリにあるプルリクエストを開いてください。 アプリケーションは応答し、プルリクエストのチェック実行を作成するはずです。 [チェック] タブをクリックすると、"Octo RuboCop" という名前か、先にチェック実行に選んだ名前のチェック実行が表示されるはずです。
[チェック] タブに他のアプリが表示される場合は、チェックに対する読み取りと書き込みアクセス権を持ち、チェック スイートおよびチェック実行イベントにサブスクライブしている他のアプリが、リポジトリにインストールされていることを意味します。
すばらしい。 ここまでで、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 が送信する requested
や rerequested
チェック スイートとは少し違います。 上記のコードは、チェック実行のアプリケーション 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
のステータスを指定するときは、conclusion
と completed_at
パラメーターが必須であることがわかります。 conclusion
はチェック実行の結果の要約であり、success
、failure
、neutral
、cancelled
、timed_out
、skipped
、または 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
を構成してこのオプションを利用します。
それでは作業を始めましょう。 このセクションでは、以下のステップを完了させます。
- Ruby ファイルを追加する
- リポジトリの複製
- RuboCop を実行する
- RuboCop のエラーを収集する
- CI テスト結果でチェック実行を更新する
- RuboCop のエラーを自動的に修正する
- セキュリティのヒント
ステップ 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 にプッシュする必要がありますが、そのためには書き込み権限が必要です。 ここでアプリの "リポジトリの内容" のアクセス許可を [読み取りと書き込み] に設定しておけば、後で再び変更する必要がなくなります。 アプリケーションの権限を更新するには、以下の手順に従います。
- アプリの設定ページでアプリを選び、サイドバーの [アクセス許可と Webhook] をクリックします。
- [アクセス許可] セクションで "リポジトリの内容" を探し、その横にある [アクセス] ドロップダウンで [読み取りと書き込み] を選びます。
- ページの下部にある [変更の保存] をクリックします。
- アプリケーションを自分のアカウントにインストールしたなら、メールをチェックして、新しい権限を受諾するリンクに従ってください。 アプリケーションの権限あるいは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 つのアノテーションが作成されます。 チェック実行を更新するたびに、アノテーションは既存のチェック実行にあるアノテーションのリストに追加されます。
チェック実行は、アノテーションをオブジェクトの配列として受け取ります。 各アノテーション オブジェクトには、path
、start_line
、end_line
、annotation_level
、message
が含まれている必要があります。 RuboCop により start_column
と end_column
も提供されるので、それらのオプションのパラメーターをアノテーションに含めることができます。 アノテーションの start_column
と end_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_column
と end_column
が annotation
オブジェクトに追加されます。
このコードはまだチェック実行のアノテーションを作成しません。 それを作成するコードは、次のセクションで追加します。
ステップ 2.5. CI テスト結果でチェック実行を更新する
GitHub からの各チェック実行には、title
、summary
、text
、annotations
、images
を含む output
オブジェクトが含まれます。 output
の必須パラメーターは summary
と title
だけですが、それらだけでは十分に詳細な情報が提供されないので、このクイックスタートでは text
と annotations
も追加します。 ここに挙げるコードでは画像は追加しませんが、追加したければぜひどうぞ。
summary
については、この例では RuboCop からの概要情報を使い、出力の書式を設定するために改行 (\n
) をいくつか追加します。 text
パラメーターに追加するものはカスタマイズできますが、この例では RuboCop のバージョンを text
パラメーターに設定します。 summary
と text
を設定するには、前のセクションで追加したコードに次のコードを追加します。
# 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
[チェック] タブに注釈が表示されます。要求されたアクションを追加して作成した [これを修正する] ボタンにもご注目ください。
既に 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
を更新しました。 created
と rerequested
のアクションの種類を処理する 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 の新しいチェック スイートが表示されています。しかし、今回は 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_run
とtake_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 を作成するようにコードを更新できます。