Acerca de Octokit.rb
Si quieres escribir un script mediante Ruby para interactuar con la API de REST de GitHub, GitHub recomienda usar el SDK de Octokit.rb. GitHub mantiene Octokit.rb. El SDK implementa los procedimientos recomendados y facilita la interacción con la API de REST a través de Ruby. Octokit.rb funciona con todos los navegadores modernos, Node.js y Deno. Para más información sobre Octokit.rb, consulta el archivo README de Octokit.rb.
Requisitos previos
En esta guía se supone que sabes usar Ruby y la API de REST GitHub. Para obtener más información sobre la API REST, consulta Introducción a la API REST.
Debes instalar e importar la gema octokit
para usar la biblioteca de Octokit.rb. En esta guía se usan instrucciones de importación de acuerdo con Ruby. Para obtener más información sobre los diferentes métodos de instalación, consulta la sección Instalación del archivo README de Octokit.rb.
Creación de instancias y autenticación
Warning
Considera tus credenciales de autenticación una contraseña.
Para proteger tus credenciales, puedes almacenarlas como secreto y ejecutar el script a través de GitHub Actions. Para más información, consulta Uso de secretos en Acciones de GitHub.
Si esto no es posible, considera el uso de otro servicio de CLI para almacenar tus credenciales de forma segura.
Autenticación con un personal access token
Si deseas usar la API de REST de GitHub para uso personal, puedes crear un personal access token. Para obtener más información sobre la creación de un personal access token, consulta Administración de tokens de acceso personal.
En primer lugar, debes requerir la biblioteca octokit
. A continuación, pasa tu personal access token como la opción access_token
al crear una instancia de Octokit
. En el ejemplo siguiente, reemplaza YOUR-TOKEN
por tu personal access token.
require 'octokit' octokit = Octokit::Client.new(access_token: 'YOUR-TOKEN')
require 'octokit'
octokit = Octokit::Client.new(access_token: 'YOUR-TOKEN')
Autenticación con una GitHub App
Si deseas usar la API en nombre de una organización u otro usuario, GitHub recomienda usar un GitHub App. Si un punto de conexión está disponible para GitHub Apps, en la documentación de referencia de REST para ese punto de conexión se indicará que tipo de token de GitHub App se requiere. Para más información, consulta Registro de una instancia de GitHub App y Acerca de la autenticación con una aplicación de GitHub.
En lugar de requerir octokit
, crea una instancia de Octokit::Client
al transferir la información de tu GitHub App como opciones. En el ejemplo siguiente, reemplaza APP_ID
por el identificador de la aplicación, PRIVATE_KEY
por la clave privada de la aplicación y INSTALLATION_ID
por el ID de instalación de la aplicación en nombre de la cual quieras autenticarte. Puedes encontrar el identificador de la aplicación y generar una clave privada en la página de configuración de la aplicación. Para más información, consulta Administración de claves privadas para aplicaciones de GitHub. Puedes obtener un identificador de instalación con GET /users/{username}/installation
, GET /repos/{owner}/{repo}/installation
o los puntos de conexión de GET /orgs/{org}/installation
. Para más información, consulta Puntos de conexión de la API de REST para GitHub Apps. Reemplaza HOSTNAME
por el nombre de tu instancia de 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)
Autenticación en GitHub Actions
Si deseas usar la API en un flujo de trabajo de GitHub Actions, GitHub recomienda autenticarse con el GITHUB_TOKEN
integrado en lugar de crear un token. Puedes conceder permisos a GITHUB_TOKEN
con la clave permissions
. Para obtener más información sobre GITHUB_TOKEN
, consulta Autenticación automática de tokens.
Si el flujo de trabajo necesita acceder a los recursos fuera del repositorio del flujo de trabajo, no podrás usar GITHUB_TOKEN
. En ese caso, almacena tus credenciales como secreto y reemplaza GITHUB_TOKEN
en el ejemplo siguiente por el nombre del secreto. Para obtener más información sobre secretos, consulta Uso de secretos en Acciones de GitHub.
Si usas la palabra clave run
para ejecutar el script de Ruby en tus flujos de trabajo de GitHub Actions, puedes almacenar el valor de GITHUB_TOKEN
como una variable de entorno. El script puede acceder a la variable de entorno como ENV['VARIABLE_NAME']
.
Por ejemplo, este paso de flujo de trabajo almacena GITHUB_TOKEN
en una variable de entorno denominada TOKEN
:
- name: Run script
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ruby .github/actions-scripts/use-the-api.rb
El script que el flujo de trabajo ejecuta usa ENV['TOKEN']
para autenticarse:
require 'octokit' octokit = Octokit::Client.new(access_token: ENV['TOKEN'])
require 'octokit'
octokit = Octokit::Client.new(access_token: ENV['TOKEN'])
Creación de instancias sin autenticación
Puedes usar la API de REST sin autenticación, aunque tendrás un límite de velocidad inferior y no podrás usar algunos puntos de conexión. Para crear una instancia de Octokit
sin autenticar, no pases la opción access_token
.
require 'octokit' octokit = Octokit::Client.new
require 'octokit'
octokit = Octokit::Client.new
Realización de solicitudes
Octokit admite varias formas de realizar solicitudes. Puedes usar el método request
para realizar solicitudes si conoces el verbo HTTP y la ruta de acceso del punto de conexión. Puedes usar el método rest
si quieres aprovechar la función de autocompletar en el IDE y escribir. En el caso de los puntos de conexión paginados, puedes usar el método paginate
para solicitar varias páginas de datos.
Uso del método request
para realizar solicitudes
Para usar el método request
para realizar solicitudes, pasa el método HTTP y la ruta de acceso como primer argumento. Pasa cualquier parámetro de cuerpo, consulta o ruta en un hash como segundo argumento. Por ejemplo, para realizar una solicitud GET
a /repos/{owner}/{repo}/issues
y pasar los parámetros owner
, repo
y 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)
El método request
pasa automáticamente el encabezado Accept: application/vnd.github+json
. Para pasar encabezados adicionales o un encabezado Accept
diferente, añade una opción headers
al hash que se pasa como segundo argumento. El valor de la opción headers
es un hash con los nombres de encabezado como claves y valores de encabezado como valores. Por ejemplo, para enviar un encabezado content-type
con un valor de text/plain
:
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" })
Uso de métodos de punto de conexión rest
para realizar solicitudes
Cada punto de conexión de la API de REST tiene un método de punto de conexión asociado rest
en Octokit. Por lo general, estos métodos se autocompletan en el IDE para mayor comodidad. Puedes pasar cualquier parámetro como un hash al método.
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)
Realización de solicitudes paginadas
Si el punto de conexión está paginado y quieres capturar más de una página de resultados, puedes usar el método paginate
. paginate
capturará la siguiente página de resultados hasta llegar a la última página y, a continuación, devolverá todos los resultados como una matriz. Algunos puntos de conexión devuelven resultados paginados como una matriz en un objeto, en lugar de devolverlos como una matriz. paginate
siempre devuelve una matriz de elementos, aun cuando el resultado sin procesar sea un objeto.
Por ejemplo, el siguiente ejemplo obtiene todas las incidencias del repositorio github/docs
. Aunque se solicitan 100 incidencias a la vez, la función no regresará hasta que se alcance la última página de datos.
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)
El método paginate
acepta un bloque opcional, que puedes usar para procesar cada página de resultados. Esto te permite recopilar únicamente los datos que deseas de la respuesta. Por ejemplo, el ejemplo siguiente continúa capturando resultados hasta que se devuelve un problema que incluye "test" en el título. Para las páginas de datos que se han devuelto, solo se almacenan el título del problema y el autor.
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
En lugar de capturar todos los resultados a la vez, puedes usar octokit.paginate.iterator()
para recorrer en iteración una sola página a la vez. Por ejemplo, en el ejemplo siguiente se captura una página de resultados a la vez y se procesa cada objeto de la página antes de capturar la página siguiente. Una vez que se alcanza un problema que incluye "prueba" en el título, el script detiene la iteración y devuelve el título del problema y el autor del problema de cada objeto que se procesó. El iterador es el método más eficiente en memoria para obtener datos paginados.
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
También puedes usar el método paginate
con los métodos de punto de conexión rest
. Pasa el método de punto de conexión rest
como primer argumento y cualquier parámetro como segundo argumento.
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)
Para obtener más información sobre la paginación, consulta Uso de la paginación en la API de REST.
Almacenamiento en caché de los errores
Detección de todos los errores
A veces, la API de REST de GitHub devolverá un error. Por ejemplo, recibirás un error si el token de acceso ha expirado o si has omitido un parámetro necesario. Octokit.rb reintenta automáticamente la solicitud cuando obtiene un error distinto de 400 Bad Request
, 401 Unauthorized
, 403 Forbidden
, 404 Not Found
y 422 Unprocessable Entity
. Si se produce un error de API incluso después de varios reintentos, Octokit.rb genera un error que incluye el código de estado HTTP de la respuesta (response.status
) y los encabezados de respuesta (response.headers
). Debes controlar estos errores en el código. Por ejemplo, puedes usar un bloque try/catch para detectar errores:
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
Control de los códigos de error previstos
A veces, los datos GitHub usan un código de estado 4xx para indicar una respuesta que no es de error. Si el punto de conexión que usa lo hace, puedes agregar control adicional para errores específicos. Por ejemplo, el punto de conexión GET /user/starred/{owner}/{repo}
devolverá un valor 404
si el repositorio no está destacado. En el ejemplo siguiente se usa la respuesta 404
para indicar que el repositorio no se ha destacado; todos los demás códigos de error se tratan como errores.
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
Control de errores de límite de frecuencia
Si recibes un error de límite de frecuencia, es posible que quieras volver a realizar la solicitud después de esperar. Cuando se limita la velocidad, GitHub responde con un error 403 Forbidden
y el valor del encabezado de respuesta x-ratelimit-remaining
será "0"
. Los encabezados de respuesta incluirán un encabezado x-ratelimit-reset
, que indica la hora a la que se restablece la ventana de límite de velocidad actual, en segundos de época UTC. Puedes volver a intentar realizar tu solicitud después del tiempo especificado por 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)
Análisis de la respuesta
El método request
devuelve un objeto de respuesta si la solicitud se ha realizado correctamente. El objeto de respuesta contiene data
(el cuerpo de la respuesta devuelto por el punto de conexión), status
(el código de respuesta HTTP), url
(la dirección URL de la solicitud) y headers
(un hash que contiene los encabezados de respuesta). A menos que se especifique lo contrario, el cuerpo de la respuesta está en formato JSON. Algunos puntos de conexión no devuelven un cuerpo de respuesta; en esos casos, se omite la propiedad 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']}"
Del mismo modo, el método paginate
devuelve un objeto de respuesta. Si la request
se realizó correctamente, el objeto response
contendrá datos, estado, dirección URL y encabezados.
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']}"
Script de ejemplo
Este es un script de ejemplo completo que usa Octokit.rb. El script importa Octokit
y crea una nueva instancia de Octokit
. Si quieres autenticarte con una GitHub App en lugar de con un personal access token, tendrás que crear e importar instancias de App
en lugar de Octokit
. Para más información, consulta Autenticación con una GitHub App en esta guía.
La función get_changed_files
obtiene todos los archivos modificados para una solicitud de incorporación de cambios. La función comment_if_data_files_changed
llama a la función get_changed_files
. Si alguno de los archivos que cambió la solicitud de incorporación de cambios incluye /data/
en la ruta de acceso del archivo, la función comentará la solicitud de incorporación de cambios.
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}"
Note
Este es solo un ejemplo básico. En la práctica, es posible que desees usar el control de errores y las comprobaciones condicionales para controlar varios escenarios.
Pasos siguientes
Para obtener más información sobre cómo trabajar con la API de REST de GitHub y Octokit.rb, consulta los siguientes recursos:
- Para más información sobre Octokit.rb, consulta la documentación de Octokit.rb.
- Para encontrar información detallada sobre los puntos de conexión de la API de REST de GitHub, incluidas sus estructuras de solicitud y respuesta, consulta Documentación de API REST para GitHub.