Octokit.rb について
Ruby を使用して GitHub の REST API と対話するスクリプトを記述する場合、GitHub では、Octokit.rb SDK を使用することをお勧めします。 Octokit.rb は GitHub によって管理されています。 SDK によってベスト プラクティスが実装されており、Ruby を使用して REST API を簡単に操作できます。 Octokit.rb は、最新のあらゆるブラウザー、Node.rb、Deno で動作します。 Octokit.rb について詳しくは、Octokit.rb の README を参照してください。
前提条件
このガイドでは、ユーザーが Ruby と GitHub REST API について理解していることを前提としています。 REST API について詳しくは、「REST API を使用した作業の開始」をご覧ください。
Octokit.rb ライブラリを使うには、octokit gem をインストールしてインポートする必要があります。 このガイドでは、Ruby の規約に従って import ステートメントを使用します。 さまざまなインストールの方法について詳しくは、Octokit.rb の README の「インストール」セクションを参照してください。
インスタンス化と認証
警告: 認証の資格情報はパスワードと同じように扱ってください。
資格情報を安全な状態に保つには、ご利用の資格情報をシークレットとして格納し、GitHub Actions を介してスクリプトを実行します。 詳しくは、「GitHub Actions でのシークレットの使用」を参照してください。
これを使用できない場合、別の CLI サービスを使用して資格情報を安全に格納することを検討してください。
personal access token で認証を行う
個人用に GitHub REST API を使用する場合は、personal access tokenを作成できます。 personal access tokenの作成について詳しくは、「個人用アクセス トークンを管理する」をご覧ください。
まず、octokit ライブラリが必要です。 次に、personal access tokenを access_token オプションとして渡し、Octokit のインスタンスを作成します。 次の例では、YOUR-TOKEN を personal access token に置き換えます。
require 'octokit' octokit = Octokit::Client.new(access_token: 'YOUR-TOKEN')
require 'octokit'
octokit = Octokit::Client.new(access_token: 'YOUR-TOKEN')
GitHub App による認証
Organization または他のユーザーの代わりに API を使用する場合、GitHub では、GitHub App の使用が推奨されます。 エンドポイントが GitHub Apps で使用できる場合、そのエンドポイントの REST リファレンス ドキュメントには、どの種類の GitHub App トークンが必要かがと示されます。 詳細については、「GitHub App の登録」および「GitHub アプリでの認証について」を参照してください。
octokit を要求する代わりに、GitHub App の情報をオプションとして渡してインスタンス Octokit::Client を作成します。 次の例では、APP_ID をアプリ IDに、PRIVATE_KEY をアプリの秘密キーに、INSTALLATION_ID を代わりに認証するアプリのインストールの ID に置き換えます。 アプリの ID を見つけて、アプリの設定ページで秘密キーを生成できます。 詳しくは、「GitHub Apps の秘密キーの管理」を参照してください。 インストール ID は GET /users/{username}/installation、GET /repos/{owner}/{repo}/installation、または GET /orgs/{org}/installation のエンドポイントで取得できます。 詳細については、「GitHub Apps用 REST API エンドポイント」を参照してください。HOSTNAME をお使いの GitHub Enterprise Server インスタンスの名前に置き換えます。
require 'octokit' app = Octokit::Client.new( client_id: APP_ID, client_secret: PRIVATE_KEY, installation_id: INSTALLATION_ID ) octokit = Octokit::Client.new(bearer_token: app.create_app_installation.access_token)
require 'octokit'
app = Octokit::Client.new(
client_id: APP_ID,
client_secret: PRIVATE_KEY,
installation_id: INSTALLATION_ID
)
octokit = Octokit::Client.new(bearer_token: app.create_app_installation.access_token)
GitHub Actions における認証
GitHub Actions ワークフローで API を使用する場合、GitHub では、トークンを作成するのではなく、組み込み GITHUB_TOKEN で認証することが推奨されます。 permissions キーを使用して、GITHUB_TOKEN へのアクセス許可を付与できます。 GITHUB_TOKEN について詳しくは、「自動トークン認証」をご覧ください。
ワークフローがワークフローのリポジトリの外部にあるリソースにアクセスする必要がある場合は、GITHUB_TOKEN を使用できません。 その場合は、資格情報をシークレットとして格納し、次の例で GITHUB_TOKEN を実際のシークレットの名前に置き換えます。 シークレットについて詳しくは、「GitHub Actions でのシークレットの使用」をご覧ください。
run キーワードを使用してGitHub Actions ワークフローで Ruby スクリプトを実行する場合は、GITHUB_TOKEN の値を環境変数として格納できます。 スクリプトは、ENV['VARIABLE_NAME'] として環境変数にアクセスできます。
たとえば、このワークフロー ステップでは、TOKEN という環境変数に GITHUB_TOKEN が格納されます。
- name: Run script
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ruby .github/actions-scripts/use-the-api.rb
ワークフローが実行するスクリプトは、認証に ENV['TOKEN'] を使用します。
require 'octokit' octokit = Octokit::Client.new(access_token: ENV['TOKEN'])
require 'octokit'
octokit = Octokit::Client.new(access_token: ENV['TOKEN'])
認証なしでのインスタンス化
REST API は認証なしで使用できますが、レート制限が低く、一部のエンドポイントを使用することができません。 認証を行わずにインスタンス Octokit を作成するには、この access_token オプションを渡さないでください。
require 'octokit' octokit = Octokit::Client.new
require 'octokit'
octokit = Octokit::Client.new
要求の作成
Octokit では、要求を行う複数の方法がサポートされています。 エンドポイントの HTTP 動詞とパスがわかっている場合は、request メソッドを使用して要求を行うことができます。 rest メソッドを使用すると、IDE と入力でのオートコンプリートを利用できます。 ページ分割されたエンドポイントの場合は、paginate メソッドを使用して複数のページのデータを要求できます。
request メソッドを使用して要求を行う
request メソッドを使用して要求を行うには、HTTP メソッドとパスを最初の引数として渡します。 ハッシュ内の本文、クエリ、またはパスのパラメーターを 2 番目の引数として渡します。 たとえば、GET 要求を /repos/{owner}/{repo}/issues に行い、owner、repo、per_page パラメーターを渡すには、次のようにします。
octokit.request("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)
octokit.request("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)
request メソッドでは Accept: application/vnd.github+json ヘッダーが自動的に渡されます。 追加のヘッダーまたは別の Accept ヘッダーを渡すには、2 番目の引数として渡されるハッシュに headers オプションを追加します。 headers オプションの値は、キーがヘッダー名で値がヘッダー値のハッシュです。 たとえば、値が text/plain の content-type ヘッダーを送信する場合は次のようになります。
octokit.request("POST /markdown/raw", text: "Hello **world**", headers: { "content-type" => "text/plain" })
octokit.request("POST /markdown/raw", text: "Hello **world**", headers: { "content-type" => "text/plain" })
rest エンドポイント メソッドを使用して要求を行う
すべての REST API エンドポイントには、Octokit に関連付けられた rest エンドポイント メソッドがあります。 これらのメソッドは、通常、便宜上 IDE でオートコンプリートされます。 任意のパラメーターをハッシュとしてメソッドに渡すことができます。
octokit.rest.issues.list_for_repo(owner: "github", repo: "docs", per_page: 2)
octokit.rest.issues.list_for_repo(owner: "github", repo: "docs", per_page: 2)
ページ分割された要求の作成
エンドポイントがページ分割されていて、複数のページの結果をフェッチする場合は、paginate メソッドを使用できます。 paginate は、最後のページに達するまで結果の次のページをフェッチし、すべての結果を配列として返します。 いくつかのエンドポイントは、ページ分割された結果を配列として返すのではなく、ページ分割された結果をオブジェクト内の配列として返します。 生の結果がオブジェクトであっても、paginate は常にアイテムの配列を返します。
たとえば、上記の例では github/docs リポジトリからすべての issue が取得されます。 一度に 100 の issue が要求されますが、データの最後のページに達するまで関数は返されません。
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
このメソッドは paginate オプションのブロックを受け入れます。これは、結果の各ページを処理するために使用できます。 これにより、応答から必要なデータのみを収集できます。 たとえば、次の例では、タイトルに "test" を含む issue が返されるまで、結果がフェッチされます。 返されたデータのページには、issue のタイトルと作成者のみが格納されます。
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100) do |response, done|
response.data.map do |issue|
if issue.title.include?("test")
done.call
end
{ title: issue.title, author: issue.user.login }
end
end
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100) do |response, done|
response.data.map do |issue|
if issue.title.include?("test")
done.call
end
{ title: issue.title, author: issue.user.login }
end
end
すべての結果を一度にフェッチする代わりに、octokit.paginate.iterator() を使用して一度に 1 つのページを反復処理できます。 たとえば、次の例では、一度に 1 ページの結果をフェッチし、次のページをフェッチする前に、ページの各オブジェクトを処理します。 タイトルに "test" を含む issue に達すると、スクリプトは反復を停止し、処理された各オブジェクトの issue タイトルと issue 作成者を返します。 反復子は、ページ分割されたデータをフェッチするための最もメモリ効率の高いメソッドです。
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
issue_data = []
break_loop = false
iterator.each do |data|
break if break_loop
data.each do |issue|
if issue.title.include?("test")
break_loop = true
break
else
issue_data << { title: issue.title, author: issue.user.login }
end
end
end
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
issue_data = []
break_loop = false
iterator.each do |data|
break if break_loop
data.each do |issue|
if issue.title.include?("test")
break_loop = true
break
else
issue_data << { title: issue.title, author: issue.user.login }
end
end
end
rest エンドポイント メソッドでも、paginate メソッドを使用できます。 rest エンドポイント メソッドを最初の引数として、任意のパラメーターを 2 番目の引数として渡します。
iterator = octokit.paginate.iterator(octokit.rest.issues.list_for_repo, owner: "github", repo: "docs", per_page: 100)
iterator = octokit.paginate.iterator(octokit.rest.issues.list_for_repo, owner: "github", repo: "docs", per_page: 100)
改ページ位置の自動修正について詳しくは、「REST API 内での改ページ位置の自動修正の使用」をご覧ください。
エラーのキャッチ
すべてのエラーのキャッチ
場合によって、GitHub REST API からエラーが返されることがあります。 たとえば、アクセス トークンの有効期限が切れている場合や、必要なパラメーターを省略した場合にエラーが発生します。 Octokit.rb は 400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、422 Unprocessable Entity 以外のエラーが発生すると、要求を自動的に再試行します。 再試行後も API エラーが発生した場合、Octokit.rb は、応答の HTTP 状態コード (response.status) と応答ヘッダー (response.headers) を含むエラーをスローします。 これらのエラーはコードで対処する必要があります。 たとえば、try/catch ブロックを使用してエラーをキャッチできます。
begin
files_changed = []
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", owner: "github", repo: "docs", pull_number: 22809, per_page: 100)
iterator.each do | data |
files_changed.concat(data.map {
| file_data | file_data.filename
})
end
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
begin
files_changed = []
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", owner: "github", repo: "docs", pull_number: 22809, per_page: 100)
iterator.each do | data |
files_changed.concat(data.map {
| file_data | file_data.filename
})
end
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
目的のエラー コードの処理
場合によっては、GitHub は 4xx 状態コードを使用してエラー以外の応答を示します。 使用しているエンドポイントでこれを行う場合は、特定のエラーに対する処理を追加できます。 たとえば、リポジトリに星が付いていない場合、GET /user/starred/{owner}/{repo} エンドポイントは 404 を返します。 次の例では、404 応答を使用して、リポジトリに星が付いていないことを示しています。その他のすべてのエラー コードはエラーとして扱われます。
begin
octokit.request("GET /user/starred/{owner}/{repo}", owner: "github", repo: "docs")
puts "The repository is starred by me"
rescue Octokit::NotFound => error
puts "The repository is not starred by me"
rescue Octokit::Error => error
puts "An error occurred while checking if the repository is starred: #{error&.response&.data&.message}"
end
begin
octokit.request("GET /user/starred/{owner}/{repo}", owner: "github", repo: "docs")
puts "The repository is starred by me"
rescue Octokit::NotFound => error
puts "The repository is not starred by me"
rescue Octokit::Error => error
puts "An error occurred while checking if the repository is starred: #{error&.response&.data&.message}"
end
レート制限エラーの処理
レート制限エラーが発生した場合は、待機後に要求を再試行できます。 レート制限がある場合、GitHub は 403 Forbidden エラーで応答し、x-ratelimit-remaining 応答ヘッダーの値は "0" になります。 応答ヘッダーには、現在のレート制限ウィンドウがリセットされる時刻を UTC エポック秒数で示す x-ratelimit-reset ヘッダーが含まれます。 要求は、x-ratelimit-reset で指定された時刻より後に再試行できます。
def request_retry(route, parameters)
begin
response = octokit.request(route, parameters)
return response
rescue Octokit::RateLimitExceeded => error
reset_time_epoch_seconds = error.response.headers['x-ratelimit-reset'].to_i
current_time_epoch_seconds = Time.now.to_i
seconds_to_wait = reset_time_epoch_seconds - current_time_epoch_seconds
puts "You have exceeded your rate limit. Retrying in #{seconds_to_wait} seconds."
sleep(seconds_to_wait)
retry
rescue Octokit::Error => error
puts error
end
end
response = request_retry("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)
def request_retry(route, parameters)
begin
response = octokit.request(route, parameters)
return response
rescue Octokit::RateLimitExceeded => error
reset_time_epoch_seconds = error.response.headers['x-ratelimit-reset'].to_i
current_time_epoch_seconds = Time.now.to_i
seconds_to_wait = reset_time_epoch_seconds - current_time_epoch_seconds
puts "You have exceeded your rate limit. Retrying in #{seconds_to_wait} seconds."
sleep(seconds_to_wait)
retry
rescue Octokit::Error => error
puts error
end
end
response = request_retry("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)
応答の使用
request メソッドは、要求が成功した場合に応答オブジェクトを返します。 応答オブジェクトには data (エンドポイントによって返される応答本文)、status (HTTP 応答コード)、url (要求の URL)、および headers (応答ヘッダーを含むハッシュ) が含まれます。 特に指定しない限り、応答本文は JSON 形式となります。 一部のエンドポイントは応答本文を返しません。このような場合、data プロパティは省略されます。
response = octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", owner: "github", repo: "docs", issue_number: 11901)
puts "The status of the response is: #{response.status}"
puts "The request URL was: #{response.url}"
puts "The x-ratelimit-remaining response header is: #{response.headers['x-ratelimit-remaining']}"
puts "The issue title is: #{response.data['title']}"
response = octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", owner: "github", repo: "docs", issue_number: 11901)
puts "The status of the response is: #{response.status}"
puts "The request URL was: #{response.url}"
puts "The x-ratelimit-remaining response header is: #{response.headers['x-ratelimit-remaining']}"
puts "The issue title is: #{response.data['title']}"
同様に、paginate メソッドは応答オブジェクトを返します。 request が成功した場合、response オブジェクトにはデータ、状態、URL、ヘッダーが含まれます。
response = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
puts "#{response.data.length} issues were returned"
puts "The title of the first issue is: #{response.data[0]['title']}"
response = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
puts "#{response.data.length} issues were returned"
puts "The title of the first issue is: #{response.data[0]['title']}"
サンプル スクリプト
Octokit.rb を使用するスクリプトの完全な例を次に示します。 このスクリプトは Octokit をインポートし、Octokit の新しいインスタンスを作成します。 personal access tokenの代わりに、GitHub App で認証する場合は、Octokit の代わりに App をインポートしてインスタンス化します。 詳しくは、このガイドの「GitHub App による認証」を参照してください。
get_changed_files 関数は、pull request で変更されたすべてのファイルを取得します。 comment_if_data_files_changed 関数は get_changed_files 関数を呼び出します。 pull request で変更されたいずれかのファイルのファイル パスに /data/ が含まれている場合、この関数が pull request にコメントを付けます。
require "octokit"
octokit = Octokit::Client.new(access_token: "YOUR-TOKEN")
def get_changed_files(octokit, owner, repo, pull_number)
files_changed = []
begin
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", owner: owner, repo: repo, pull_number: pull_number, per_page: 100)
iterator.each do | data |
files_changed.concat(data.map {
| file_data | file_data.filename
})
end
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
files_changed
end
def comment_if_data_files_changed(octokit, owner, repo, pull_number)
changed_files = get_changed_files(octokit, owner, repo, pull_number)
if changed_files.any ? {
| file_name | /\/data\//i.match ? (file_name)
}
begin
comment = octokit.create_pull_request_review_comment(owner, repo, pull_number, "It looks like you changed a data file. These files are auto-generated. \n\nYou must revert any changes to data files before your pull request will be reviewed.")
comment.html_url
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
end
end
# Example usage
owner = "github"
repo = "docs"
pull_number = 22809
comment_url = comment_if_data_files_changed(octokit, owner, repo, pull_number)
puts "A comment was added to the pull request: #{comment_url}"
require "octokit"
octokit = Octokit::Client.new(access_token: "YOUR-TOKEN")
def get_changed_files(octokit, owner, repo, pull_number)
files_changed = []
begin
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", owner: owner, repo: repo, pull_number: pull_number, per_page: 100)
iterator.each do | data |
files_changed.concat(data.map {
| file_data | file_data.filename
})
end
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
files_changed
end
def comment_if_data_files_changed(octokit, owner, repo, pull_number)
changed_files = get_changed_files(octokit, owner, repo, pull_number)
if changed_files.any ? {
| file_name | /\/data\//i.match ? (file_name)
}
begin
comment = octokit.create_pull_request_review_comment(owner, repo, pull_number, "It looks like you changed a data file. These files are auto-generated. \n\nYou must revert any changes to data files before your pull request will be reviewed.")
comment.html_url
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
end
end
# Example usage
owner = "github"
repo = "docs"
pull_number = 22809
comment_url = comment_if_data_files_changed(octokit, owner, repo, pull_number)
puts "A comment was added to the pull request: #{comment_url}"
注: これは基本的な例にすぎません。 実際には、エラー処理と条件付きチェックを使用して、さまざまなシナリオを処理する必要がある場合があります。
次のステップ
GitHub REST API と Octokit.rb の操作の詳細については、次のリソースを参照してください。
- Octokit.rb について詳しくは、Octokit.rb のドキュメントを参照してください。
- GitHub の使用可能な REST API エンドポイント (要求と応答の構造など) の詳細については、GitHub REST API に関するドキュメント を参照してください。