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 ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ฐธ์กฐํ์ธ์.
ํ์ ์กฐ๊ฑด
์ด ๊ฐ์ด๋์์๋ Ruby ๋ฐ GitHub REST API์ ์ต์ํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. REST API์ ๋ํ ์์ธํ ๋ด์ฉ์ REST API ์์์(๋ฅผ) ์ฐธ์กฐํ์ธ์.
์ฐธ๊ณ : Octokit.rb ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด octokit
๋ณด์์ ์ค์นํ๊ณ ๊ฐ์ ธ์์ผ ํฉ๋๋ค. ์ด ๊ฐ์ด๋์์๋ Ruby์ ๊ท์น์ ๋ฐ๋ผ import ๋ฌธ์ ์ฌ์ฉํฉ๋๋ค. ๋ค๋ฅธ ์ค์น ๋ฐฉ๋ฒ์ ๋ํ ์์ธํ ๋ด์ฉ์ Octokit.rb README์ ์ค์น ์น์
์ ์ฐธ์กฐํ์ธ์.
์ธ์คํด์คํ ๋ฐ ์ธ์ฆ
๊ฒฝ๊ณ
์ธ์ฆ ์๊ฒฉ ์ฆ๋ช ์ ์ํธ์ฒ๋ผ ์ฒ๋ฆฌํฉ๋๋ค.
์๊ฒฉ ์ฆ๋ช ์ ์์ ํ๊ฒ ์ ์งํ๊ธฐ ์ํด ๋น๋ฐ๋ก ์ ์ฅํ๊ณ GitHub Actions๋ฅผ ํตํด ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ GitHub Actions์์ ๋น๋ฐ ์ฌ์ฉ์(๋ฅผ) ์ฐธ์กฐํ์ธ์.
๋ํ ์๊ฒฉ ์ฆ๋ช ์ Codespaces ๋น๋ฐ๋ก ์ ์ฅํ๊ณ Codespaces์์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ ์๋ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ GitHub Codespaces์ ๋ํ ๊ณ์ ๋ณ ๋น๋ฐ ๊ด๋ฆฌ์(๋ฅผ) ์ฐธ์กฐํ์ธ์.
์ด(๊ฐ) ์ต์ ์ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ, ๋ค๋ฅธ 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(์ผ)๋ก ์ธ์ฆ
์กฐ์ง ๋๋ ๋ค๋ฅธ ์ฌ์ฉ์๋ฅผ ๋์ ํ์ฌ API๋ฅผ ์ฌ์ฉํ๋ ค๋ ๊ฒฝ์ฐ GitHub์์ GitHub App์(๋ฅผ) ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. GitHub Apps์ ์๋ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ ํด๋น ์๋ํฌ์ธํธ์ ๋ํ REST ์ฐธ์กฐ ์ค๋ช ์์ ํ์ํ GitHub App ํ ํฐ ์ ํ์ด ํ์๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ GitHub ์ฑ ๋ฑ๋ก ๋ฐ GitHub ์ฑ์ ์ฌ์ฉํ ์ธ์ฆ ์ ๋ณด์(๋ฅผ) ์ฐธ์กฐํ์ธ์.
octokit
์ด(๊ฐ) ํ์ํ ๋์ GitHub App์ ์ ๋ณด๋ฅผ ์ต์
์ผ๋ก ์ ๋ฌํ์ฌ Octokit::Client
์ธ์คํด์ค๋ฅผ ๋ง๋ญ๋๋ค. ๋ค์ ์์ ์์๋ APP_ID
์(๋ฅผ) ์ฑ์ ID๋ก, PRIVATE_KEY
์(๋ฅผ) ์ฑ์ ํ๋ผ์ด๋น ํค๋ก, ๊ทธ๋ฆฌ๊ณ INSTALLATION_ID
์(๋ฅผ) ๋์ ์ธ์ฆํ๋ ค๋ ์ฑ ์ค์น์ ID๋ก ๋ฐ๊ฟ๋๋ค. ์ฑ ID๋ฅผ ์ฐพ๊ณ ์ฑ์ ์ค์ ํ์ด์ง ์ค์ ์์ ํ๋ผ์ด๋น ํค๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ GitHub ์ฑ์ ๋ํ ํ๋ผ์ด๋น ํค ๊ด๋ฆฌ์(๋ฅผ) ์ฐธ์กฐํ์ธ์. GET /users/{username}/installation
, GET /repos/{owner}/{repo}/installation
, GET /orgs/{org}/installation
์๋ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ์ฌ ์ค์น ID๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ GitHub Apps์ ๋ํ REST API ์๋ํฌ์ธํธ์(๋ฅผ) ์ฐธ์กฐํ์ธ์.
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_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
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ์ํํ ์ ์์ต๋๋ค. IDE์์ ์๋ ์์ฑ ๋ฐ ์
๋ ฅ์ ์ด์ฉํ๋ ค๋ ๊ฒฝ์ฐ rest
๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ด์ง๋ฅผ ๋งค๊ธด ์๋ํฌ์ธํธ์ ๊ฒฝ์ฐ paginate
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ๋ฐ์ดํฐ ํ์ด์ง๋ฅผ ์์ฒญํ ์ ์์ต๋๋ค.
request
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ์ํํฉ๋๋ค.
request
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ๋ง๋ค๋ ค๋ฉด HTTP ๋ฉ์๋์ ๊ฒฝ๋ก๋ฅผ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํฉ๋๋ค. ํด์์ ๋ณธ๋ฌธ, ์ฟผ๋ฆฌ, ๊ฒฝ๋ก ๋งค๊ฐ ๋ณ์๋ฅผ ๋ ๋ฒ์งธ ์ธ์๋ก ํจ์คํฉ๋๋ค. ์๋ฅผ ๋ค์ด /repos/{owner}/{repo}/issues
์ GET
์ ์์ฒญํ๊ณ 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
ํค๋๋ฅผ ์ ๋ฌํ๋ ค๋ฉด ๋ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌ๋๋ ํด์์ 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
๋ฆฌํฌ์งํ ๋ฆฌ์์ ๋ชจ๋ ์ด์๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ํ ๋ฒ์ 100๊ฐ์ ์ด์๋ฅผ ์์ฒญํ์ง๋ง ํจ์๋ ๋ฐ์ดํฐ์ ๋ง์ง๋ง ํ์ด์ง์ ๋๋ฌํ ๋๊น์ง ๋ฐํ๋์ง ์์ต๋๋ค.
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_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()
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์ ์์ ์์๋ ๊ฒฐ๊ณผ์ ํ ํ์ด์ง๋ฅผ ํ ๋ฒ์ ๊ฐ์ ธ์ค๊ณ ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ ์ ํ์ด์ง์์ ๊ฐ ๊ฐ์ฒด๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. ์ ๋ชฉ์ "test"๊ฐ ํฌํจ๋ ์ด์์ ๋๋ฌํ๋ฉด ์คํฌ๋ฆฝํธ๋ ๋ฐ๋ณต์ ์ค์งํ๊ณ ์ฒ๋ฆฌ๋ ๊ฐ ๊ฐ์ฒด์ ์ด์ ์ ๋ชฉ ๋ฐ ์ด์ ์์ฑ์๋ฅผ ๋ฐํํฉ๋๋ค. ๋ฐ๋ณต๊ธฐ๋ ํ์ด์ง๋ฅผ ๋งค๊ธด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํ ๊ฐ์ฅ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ ๋ฉ์๋์
๋๋ค.
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
์๋ํฌ์ธํธ ๋ฉ์๋๋ฅผ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ๊ณ ๋งค๊ฐ ๋ณ์๋ฅผ ๋ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํฉ๋๋ค.
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๋ ์๋ต(response.status
) ๋ฐ ์๋ต ํค๋(response.headers
)์ HTTP ์ํ ์ฝ๋๋ฅผ ํฌํจํ๋ ์ค๋ฅ๋ฅผ throwํฉ๋๋ค. ์ฝ๋์์ ์ด๋ฌํ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด 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 Epoch ์ด ๋จ์๋ก ์๋ ค์ฃผ๋ 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
ํจ์๋ ๋์ด์ค๊ธฐ ์์ฒญ์ ๋ํด ๋ณ๊ฒฝ๋ ๋ชจ๋ ํ์ผ์ ๊ฐ์ ธ์ต๋๋ค. comment_if_data_files_changed
ํจ์๋ get_changed_files
ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ๋์ด์ค๊ธฐ ์์ฒญ์ด ๋ณ๊ฒฝ๋ ํ์ผ์ด ํ์ผ ๊ฒฝ๋ก์ /data/
๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ ํจ์๋ ๋์ด์ค๊ธฐ ์์ฒญ์ ๋ํด ์ค๋ช
์ ๋ฌ๊ฒ ๋ฉ๋๋ค.
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 ์ค๋ช ์์ ์ฐธ์กฐํ์ธ์.