はじめに
このガイドでは、GitHub App と Checks API について紹介します。Checks API は、テストを実行する継続的インテグレーション (CI) サーバーを構築するために使用します。
CI とは、ソフトウェアの開発においてコードを� �繁に共有リポジトリにコミットする手法のことです。 コードをコミットする� �度が高いほどエラーの発生が早くなり、開発者がエラーの原� を見つけようとしてデバッグする必要性も減ります。 コードの更新が� �繁であれば、ソフトウェア開発チー� の他のメンバーによる変更をマージするのも、それ� け容易になります。 コードの記述により多くの時間をかけられるようになり、エラーのデバッグやマージコンフリクトの解決にかける時間が減るので、これは開発者にとって� 晴らしいやり方です。 🙌
CI サーバーは、コードの文法チェッカー (スタイルフォーマットをチェックする)、セキュリティチェック、コード網羅率、その他のチェックといった CI テストをリポジトリの新しいコードコミットに対して実行するコードをホストします。 CI サーバーは、ステージングサーバーや本番サーバーにコードを構築しデプロイすることも可能です。 GitHub App で作成できる CI テストのタイプの例については、GitHub Marketplace で入手できる継続的インテグレーションアプリケーションを確認してく� さい。
ノート: このガイドは、Rubyプログラミング言語を使ったアプリケーションの開発プロセスを示します。 しかし、Octokitのバラエティはたくさんあります。 JavaScriptが好きなのであれば、GitHub Appsを開発するためにProbot及びNode.jsを使うことができます。
Checks API の概要
Checks API を使用すると、リポジトリでコミットされている各コードに対して自動的に実行される CI テストを設定できます。 Checks API は、プルリクエストの [Checks] タブにおいて、各チェックについての詳細情� �をレポートします。 Checks API を使用すると、コードの特定の行に対して追� 的な情� �を含むアノテーションを作成できます。 アノテーションは [Checks] タブに表示されます。 プルリクエストの一部であるファイルに対してアノテーションを作成すると、そのアノテーションは [Files changed] タブにも表示されます。
チェックスイートとは、 チェック実行 (個々の CI テスト) をグループ化したものです。 チェックスイートにもチェック実行にもステータスが含まれており、GitHub のプルリクエストで表示できます。 ステータスを使用して、コードコミットがエラーを発生させるタイミングを決定できます。 これらのステータスを保護されたブランチで使用すると、プルリクエストを早まってマージすることを防げます。 詳細は「保護されたブランチについて」を参照してく� さい。
Checks API は、新しいコードがリポジトリにプッシュされるたびに、リポジトリにインストールされている全ての GitHub App に check_suite
webhook イベントを送信します。 Checks API イベントの全てのアクションを受信するには、アプリケーションに checks:write
権限が必要です。 GitHub はデフォルトのフローを使ってリポジトリの新しいコードのコミットに check_suite
イベントを自動的に作成しますが、チェックスイートのためのリポジトリプリファレンスの更新を行っても構いません。 デフォルトのフローは以下の通りです。
- 誰かがリポジトリにコードをプッシュすると、GitHubは、
checks:write
権限を持つ、リポジトリにインストールされている全ての GitHub Apps にrequested
のアクションと共にcheck_suite
イベントを送信します。 このイベントにより、コードがプッシュされたことと、GitHub が新しいチェックスイートを自動的に作成したことがアプリケーションに通知されます。 - アプリケーションがこのイベントを受信すると、アプリケーションはスイートにチェック実行を追� できます。
- チェック実行には、コードの特定の行で表示されるアノテーションを含めることができます。
このガイドでは、次のこと行う方法について学びます。
- パート 1: Checks API を使用して CI サーバー用のフレー� ワークをセットアップする。
- Checks API イベントを受信するサーバーとして GitHub App を構成します。
- 新たにプッシュされたコミットをリポジトリが受信した時に、CI テスト用の新しいチェック実行を作成します。
- ユーザが GitHub でチェック実行のアクションをリクエストした時に、チェック実行を再実行します。
- パート 2: 文法チェッカー CI テストを追� して、作成した CI サーバーフレー� ワークを基に構築する。
status
、conclusion
、output
の情� �を入力して、チェック実行を更新します。- プルリクエストの [Checks] および [Files Changed] タブで GitHub が表示する、コードの行のアノテーションを作成します。
- プルリクエストの [Checks] タブに [Fix this] ボタンを表示して、文法チェッカーによる推奨事� �を自動的に適用します。
このクイックスタートを完了したときに Checks API CI サーバーがどのように動作するかを理解するには、以下のデモをご覧く� さい。
必要な環境
GitHub Apps、webhook、Checks API を使い慣れていない� �合は、以下の作業に取りかかる前にある程度慣れておくとよいでしょう。 REST API ドキュメントには、さらに多くの API が掲載されています。 Checks API は GraphQL でも使用できますが、このクイックスタートでは REST に焦点を当てます。 詳細については、GraphQL Checks Suite および Check Run オブジェクトを参照してく� さい。
Ruby プログラミング言語、Smee webhook ペイロード配信サービス、GitHub REST API 用の Octokit.rb Ruby ライブラリ、および Sinatra ウェブフレー� ワーク を使用して、Checks API CI サーバーアプリケーションを作成します。
このプロジェクトを完了するために、これらのツールや概念のエキスパートである必要はありません。 このガイドでは、必要なステップを� �番に説明していきます。 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 のテンプレートリポジトリをクローンするのではなく、このクイックスタートの直前のステップでクローンしたリポジトリにある
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.1. アプリケーションの権限を更新する
最初にアプリケーションを登録した際は、デフォルトの権限を受け入れています。これは、アプリケーションがほとんどのリソースにアクセスできないことを意味します。 この例においては、アプリケーションにはチェックを読み取りおよび書き込みする権限が必要となります。
アプリケーションの権限を更新するには、以下の手� �に従います。
- アプリケーションの設定ページからアプリケーションを選択し、サイドバーの [Permissions & Webhooks] をクリックします。
- [Permissions] セクションで [Checks] を見つけて、隣にある [Access] ドロップダウンで [Read & write] を選択します。
- [Subscribe to events] セクションで [Check suite] と [Check run] を選択してこれらのイベントをサブスクライブします。
- ページの下部でSave changes(変更を保存)をクリックしてく� さい。
- アプリケーションを自分のアカウントにインストールしたなら、メールをチェックして、新しい権限を受諾するリンクに従ってく� さい。 アプリケーションの権限あるいはwebhookを変更した� �合、そのアプリケーションをインストールしたユーザ(自分自身を含む)は、変更が有効になる前に新しい権限を承認しなければなりません。 インストールページにアクセスして、アプリケーションの隣の"Configure(設定)"をクリックしても、新しい権限を承認できます。 アプリケーションが異なる権限を要求していることを知らせるバナーがページの上部に表示されます。 "Details(詳細)"をクリックし、"Accept new permissions(新しい権限を承認)"をクリックしてく� さい。
これでうまくいきました。 アプリケーションは必要なタスクを実行する権限を所有しています。 これでイベントを処理するコードを追� できるようになりました。
ステップ 1.2. イベントの処理を追� する
ここまでで、アプリケーションが Check suite および Check run イベントにサブスクライブされ、check_suite
および check_run
webhook を受信し始めます。 GitHub は webhook ペイロードを POST
リクエストとして送信します。 Smee webhook ペイロードを http://localhost/event_handler:3000
に転送したため、サーバーは POST
リクエストのペイロードを post '/event_handler'
ルートで受信します。
空の 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 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.v3+json'
)
end
このコードはcreate_check_run methodメソッドを使用して、Create a check runエンドポイントを呼び出します。
チェック実行を作成するために必要なのは、name
と head_sha
の 2 つの入力パラメータのみです。 We will use RuboCop to implement the CI test later in this quickstart, which is why the name "Octo RuboCop" is used here, but you can choose any name you'd like for the check run.
ここでは基本的な機能を実行するため必要なパラメータのみを指定していますが、チェック実行について必要な情� �を収集するため、後でチェック実行を更新することになります。 デフォルトでは、GitHub は 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
をもう一度実行してく� さい。 アプリエーションのコードを変更するたびにこれを繰り返したくないなら、リローディングを調べてみてく� さい。
さて、それではアプリケーションをインストールしたリポジトリにあるプルリクエストを開いてく� さい。 アプリケーションは応答し、プルリクエストのチェック実行を作成するはずです。 [Checks] タブをクリックすると、画面が以下のようになっているはずです。
[Checks] タブに他のアプリケーションが表示されている� �合は、チェックに対して読み取りおよび書き込みアクセス権を持ち、Check suite および Check run イベントにサブスクライブしている他のアプリケーションをリポジトリにインストールしているものと思われます。
これでうまくいきました。 ここまでで、GitHub にチェック実行を作成するよう指示しました。 チェック実行のステータスが queued
に設定されていることが、黄色のアイコンの右側で確認できます。 次は、GitHub がチェック実行を作成し、ステータスを更新するのを待てばよいでしょう。
ステップ 1.4. チェック実行を更新する
create_check_run
メソッドが実行されると、メソッドは GitHub に新しいチェック実行を作成するよう依� �します。 GitHub がチェック実行の作成を完了すると、created
アクションの check_run
webhook イベントを受信します。 このイベントは、チェックの実行を開始する合図です。
イベントハンドラーを更新し、created
アクションを待ち受けるようにしましょう。 イベントハンドラーを更新する際、rerequested
アクションに条件を追� できます。 誰かが [Re-run] ボタンをクリックして 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.v3+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.v3+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
、action_required
のいずれかになります。 この結果 (conclusion) は success
に、completed_at
の時刻は現在の時刻に、ステータスは completed
に設定します。
チェックが行っていることについてより詳しく指定することもできますが、それは次のセクションで行うことにします。 では、template_server.rb
を実行して、このコードを再びテストしましょう。
$ ruby template_server.rb
開いたプルリクエストに移動し、[Checks] タブをクリックします。 左上隅にある [Re-run all] ボタンをクリックしてく� さい。 チェック実行が 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 のエラーを視覚化するためにアノテーションを使用します。
リクエストされたアクションを利用するため、アプリケーション開発者はプルリクエストの [Checks] タブにボタンを作成できます。 このボタンがクリックされると、そのクリックにより GitHub App に 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 にプッシュする必要がありますが、そのためには書き込み権限が必要です。 先に進んでアプリケーションの [Repository contents] の権限を [Read & write] に今すぐ変更してく� さい。そうすれば、後で再び変更する必要がなくなります。 アプリケーションの権限を更新するには、以下の手� �に従います。
- アプリケーションの設定ページからアプリケーションを選択し、サイドバーの [Permissions & Webhooks] をクリックします。
- [Permissions] セクションで [Repository contents] を見つけて、隣にある [Access] ドロップダウンで [Read & write] を選択します。
- ページの下部でSave changes(変更を保存)をクリックしてく� さい。
- アプリケーションを自分のアカウントにインストールしたなら、メールをチェックして、新しい権限を受諾するリンクに従ってく� さい。 アプリケーションの権限あるいはwebhookを変更した� �合、そのアプリケーションをインストールしたユーザ(自分自身を含む)は、変更が有効になる前に新しい権限を承認しなければなりません。 インストールページにアクセスして、アプリケーションの隣の"Configure(設定)"をクリックしても、新しい権限を承認できます。 アプリケーションが異なる権限を要求していることを知らせるバナーがページの上部に表示されます。 "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
) を使用すると、元に戻すことはできません。 ステップ 2.7. セキュリティのヒントで、webhook を調べて、意図したものとは別のディレクトリを削除するためアプリケーションが使用する可能性のある、インジェクションされた悪意あるコマンドがないかを確認する方法について学びます。 例えば、悪意のある人が ./
というリポジトリ名の webhook を送信した� �合、アプリケーションはルートディレクトリを削除するかもしれません。 😱 もし、何らかの理由で webhook の送信者を検証するメソッド verify_webhook_signature
(template_server.rb
に含まれています) を使用しない� �合、リポジトリ名が有効であることを必ず確認するようにしてく� さい。
このコードが動作することをテストし、サーバーのデバッグ出力で RuboCop により� �告されたエラーを確認できます。 template_server.rb
サーバーを再起動し、以下のコマンドで、アプリケーションをテストする� �所のリポジトリに新しいプルリクエストを作成します。
$ 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
配列内にある各ファイルについてエラーを� �告します。エラーがある� �合、ファイル オブジェクトからデータを抽出する必要があります。
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 のアノテーションごとにチェック実行を更新するよう変更することも可能です。 上記のコードには、制限 を 50 に設定している変数 max_annotations
が含まれ、これは違反の部分を反復処理するループで使用されています。
offense_count
が 0 の� �合、CI テストは success
となります。 エラーがある� �合、このコードは結果を neutral
に設定します。これは、コードの文法チェッカーによるエラーを厳� �に強制することを防ぐためです。 た� し、文法エラーがある� �合にチェックスイートが失敗になるようにしたい� �合は、結果を failure
に変更できます。
エラーが� �告されると、上記のコードは ReboCop レポートの files
配列を反復処理します。 コードは各ファイルにおいてファイルパスを抽出し、アノテーションレベルを notice
に設定します。 さらに細かく、RuboCop Cop の各タイプに特定の警告レベルを設定することもできますが、このクイックスタートでは簡単さを優先し、すべてのエラーを notice
のレベルに設定します。
このコードはまた、offenses
配列の各エラーを反復処理し、違反の� �所とエラー メッセージを収集します。 必要な情� �を抽出後、コードは各エラーに対してアノテーションを作成し、それを annotations
配列に� �納します。 アノテーションは同一行の開始列と終了列のみをサポートしているため、開始行と終了行の値が同じである� �合にのみ annotation
オブジェクトに start_column
と end_column
が追� されます。
このコードはま� チェック実行のアノテーションを作成しません。 それを作成するコードは、次のセクションで追� します。
ステップ 2.5. CI テスト結果でチェック実行を更新する
GitHub から実行される各チェックには、output
オブジェクトが含まれ、そのオブジェクトには title
、summary
、text
、annotations
、images
が含まれます。 output
に必� �のパラメータは summary
と title
のみですが、これ� けでは詳細情� �が得られないので、このクイックスタートでは text
と annotations
も追� します。 ここに挙げるコードでは画像は追� しませんが、追� したければぜひどうぞ。
この例では、summary
は RuboCop からのサマリー情� �を利用し、出力を整形するためいくつかの改行 (\n
) を追� します。 text
パラメータに何を追� するかはカスタマイズできますが、この例では text
パラメータを RuboCop のバージョンに設定しています。 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.v3+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.v3+json'
)
さて、これで CI テストのステータスに基づいて結論を設定し、RuboCop の結果からの出力を追� しました。あなたは CI テストを作成したのです。 おめでとうございます。 🙌
また、上記のコードは、actions
オブジェクトを介して CI サーバーにリクエストされたアクションという機能も追� しています。 リクエストされたアクションは、追� のアクションを実行するためにチェック実行をリクエストできるボタンを GitHub の [Checks] タブに追� します。 追� のアクションは、アプリケーションが自由に設定できます。 たとえば、RuboCop には Ruby のコードで見つかったエラーを自動的に修正する機能があるので、CI サーバーはリクエストされたアクションボタンを使用して、自動的なエラー修正をユーザが許可することができます。 このボタンをクリックすると、アプリケーションは requested_action
アクションで check_run
イベントを受け取ります。 リクエストされた各アクションには、どのボタンがクリックされたかアプリケーションが判断するために使用する identifier
があります。
上記のコードには、ま� RuboCop が自動的にエラーを修正する処理がありません。 この処理については、次のセクションで追� します。 しかしまずは、template_server.rb
サーバーを再起動して新しいプルリクエストを作成し、さきほど作成した CI テストを見てみましょう。
$ ruby template_server.rb
アノテーションは [Checks] タブに表示されます。
リクエストされたアクションを追� することにより作成された [Fix this] ボタンに注目してく� さい。
すでにプルリクエストに含まれているファイルにアノテーションが関連している� �合、そのアノテーションは [Files changed] タブにも表示されます。
ステップ 2.6. RuboCop のエラーを自動的に修正する
ここまで来たのはすごいですよ! 👏 あなたはもう CI テストを作成しました。 このセクションでは、もう 1 つの機能を追� します。RuboCop を使用して、見つけたエラーを自動的に修正するために使用するための機能です。 すでに前のセクションで、[Fix this] ボタンを追� しました。 ここでは、ユーザが [Fix this] ボタンをクリックしたときにトリガーされる、requested_action
チェック実行イベントを処理するコードを追� します。
RuboCop ツールには、見つけたエラーを自動的に修正する --auto-correct
コマンドラインオプションの 機能 があります。 --auto-correct
機能を使用すると、サーバー上のローカルファイルに更新が適用されます。 RuboCop がこの作業をやってのけた後は、その変更を GitHub にプッシュする必要があります。
リポジトリにブッシュするには、アプリケーションに [Repository contents] への書き込み権限が必要です。 この権限については、ステップ 2.2. リポジトリをクローンするで既に [Read & write] に設定しているので、もう準備は整っています。
ファイルをコミットするには、どのユーザ名とメールアドレスをコミットに関連付けるか Git が知っている必要があります。 .env
ファイルにあと 2 つの環境変数を追� して、名前 (GITHUB_APP_USER_NAME
) とメールアドレス (GITHUB_APP_USER_EMAIL
) の設定を保存します。 アプリケーションにはあなたの名前を付けることもできます。この例では、メールアドレスは何でも構いません。 例:
GITHUB_APP_USER_NAME=Octoapp
GITHUB_APP_USER_EMAIL=octoapp@octo-org.com
作者およびコミッターの、名前およびメールアドレスを入力して .env
ファイルを更新したら、環境変数を読み取るコードを追� し、Git の設定を行う準備が整いました。 このコードは、もうすぐ追� することになります。
ユーザが [Fix this] ボタンをクリックすると、アプリケーションは requested_action
アクションタイプの check run 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
の条件の後に、 rerequested_action
イベントを処理するためもう 1 つ when
文を追� します。
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
サーバーをもう一度再起動し、新しいプルリクエストを次の通り作成しましょう。
$ ruby template_server.rb
ノート: 変更をテストする前に、Sinatraサーバーを再起動しなければなりません。 Ctrl-C
を入力してサーバーを停止し、ruby template_server.rb
をもう一度実行してく� さい。 アプリエーションのコードを変更するたびにこれを繰り返したくないなら、リローディングを調べてみてく� さい。
今回は、[Fix this] ボタンをクリックすると、RuboCop が [Checks] タブから見つけたエラーを自動的に修正します。
[Commits] タブには、Git コンフィグレーションで設定したユーザ名による新たなコミットが表示されています。 更新を確認するには、ブラウザを更新する必要がある� �合があります。
新たなコミットがリポジトリにプッシュされたので、[Checks] タブに 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 Development and Support Forumで助けやアドバイスを求めることができます。
-
Q: アプリケーションが GitHub にコードをプッシュしません。 RuboCop が自動的に行う修正が表示されません。
A: [Repository contents] に対する Read & write 権限があること、およびインストールトークンでリポジトリをクローンしていることを確認します。 ステップ 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 の [Checks] タブで表示されません。
A: Smee を再起動し、
template_server.rb
サーバーを再実行してく� さい。 -
Q: [Re-run all] ボタンが、GitHub の [Checks] タブで表示されません。
A: Smee を再起動し、
template_server.rb
サーバーを再実行してく� さい。
おわりに
このガイドの手� �を一通り終えたら、Checks API を使用して CI サーバーを作成することの基本が習得できています。 振り返ると、以下を行いました。
- Checks API イベントを受信し、チェック実行を作成するようサーバーを設定しました。
- リポジトリ内のコードをチェックし、エラーのアノテーションを作成するため RuboCop を使用しました。
- 文法エラーを自動的に修正する、リクエストされたアクションを実装しました。
次のステップ
以下は、次に行えることのいくつかのアイデアです。
- 現在、[Fix this] ボタンは常に表示されています。 ここまで書いたコードを更新し、RuboCop がエラーを見つけた時にのみ [Fix this] ボタンが表示されるようにしましょう。
- RuboCop がファイルを head ブランチに直接コミットしないようにしたい� �合、head ブランチに基づいて新しいブランチでプルリクエストを作成するようにコードを更新できます。