Introdução
Este tutorial demonstra como criar um servidor de CI (integração contínua) que executa testes em um novo código que é enviado por push para um repositório. O tutorial mostra como criar e configurar um GitHub App para atuar como um servidor que recebe e responde a eventos de webhook check_run
e check_suite
usando a API REST do GitHub.
Neste tutorial, você usará seu computador ou codespace como um servidor enquanto desenvolve o aplicativo. Depois que o aplicativo estiver pronto para uso em produção, você deverá implantar seu aplicativo em um servidor dedicado.
Este tutorial usa Ruby, mas você pode usar qualquer linguagem de programação que possa ser executada no servidor.
Este tutorial é dividido em duas partes:
- Na primeira parte, você aprenderá a configurar a estrutura para um servidor de CI usando a API REST do GitHub, criar execuções de verificação para testes de CI quando um repositório receber commits enviados recentemente por push e executar verificações novamente quando um usuário solicitar essa ação em GitHub.
- Na parte dois, você adicionará funcionalidade ao teste de CI adicionando um teste de linter ao servidor de CI. Você também criará anotações exibidas na guia Verificações e Arquivos Alterados de uma solicitação de pull e corrigirá automaticamente as recomendações do linter expondo um botão "Corrigir isso" na guia Verificações da solicitação de pull.
Sobre a CI (integração contínua)
A CI é uma prática de software que exige o commit do código em um repositório compartilhado. Fazer commits de códigos com frequência detecta erros com mais antecedência e reduz a quantidade de código necessária para depuração quando os desenvolvedores chegam à origem de um erro. As atualizações frequentes de código também facilitam o merge de alterações dos integrantes de uma equipe de desenvolvimento de software. Assim, os desenvolvedores podem se dedicar mais à gravação de códigos e se preocupar menos com erros de depuração ou conflitos de merge.
Um código de host do servidor de CI que executa testes de CI, como, por exemplo, linters de código (que verificam formatação de estilo), verificações de segurança, cobertura de código e outras verificações de novos commits de códigos em um repositório. OS ervidores de CI podem até criar e implementar código para servidores de treinamento ou produção. Para obter exemplos dos tipos de testes de CI que você pode criar com um GitHub App, consulte os aplicativos de integração contínua que estão disponíveis no GitHub Marketplace.
Sobre verificações
A API REST de GitHub permite que você configure testes de CI (verificações) que são executados automaticamente em cada commit de código em um repositório. A API relata informações detalhadas sobre cada marcar na guia Verificações da solicitação de pull em GitHub. Você pode usar as verificações em um repositório para determinar quando um commit de código introduz erros.
As verificações incluem execuções de verificação, conjuntos de verificações e status de commit.
- Uma execução de verificação é um teste de CI individual executado em um commit.
- Um conjunto de verificações é um grupo de execuções de verificação.
- Um status de commit marca o estado de um commit, por exemplo,
error
,failure
,pending
ousuccess
, e é visível em uma solicitação de pull em GitHub. Os conjuntos de verificações e as execuções de verificação contêm status de commit.
GitHub cria automaticamente eventos check_suite
para novos commits de código em um repositório usando o fluxo padrão, embora você possa alterar as configurações padrão. Para obter mais informações, confira "Pontos de extremidade da API REST para pacotes de verificação". Veja como funciona o fluxo-padrão:
- Quando alguém envia código por push para o repositório, GitHub envia automaticamente o evento
check_suite
com uma ação derequested
para todos os GitHub Apps instalados no repositório que têm a permissãochecks:write
. Este evento permite que os aplicativos saibam que o código foi enviado por push para o repositório e que o GitHub criou um conjunto de verificações automaticamente. - Quando seu aplicativo recebe esse evento, ele pode adicionar execuções de verificação a esse conjunto.
- As execuções de verificação podem incluir anotações exibidas em linhas de código específicas. As anotações ficam visíveis na guia Verificações. Quando você cria uma anotação para um arquivo que faz parte da solicitação de pull, as anotações também são mostradas na guia Arquivos alterados. Para obter mais informações, confira o objeto
annotations
em "Pontos de extremidade da API REST para execuções de verificação".
Para obter mais informações sobre verificações, confira "Pontos de extremidade da API REST para verificações" e "Como usar a API REST para interagir com verificações".
Pré-requisitos
Este tutorial pressupõe que você tenha uma compreensão básica da linguagem de programação Ruby.
Antes de começar, convém que você se familiarize com os seguintes conceitos:
As verificações também estão disponíveis para uso com a API do GraphQL, mas este tutorial se concentra na API REST. Para obter mais informações sobre os objetos GraphQL, confira Conjunto de verificações e Execução de verificação na documentação do GraphQL.
Instalação
As seções a seguir o conduzirão na configuração dos seguintes componentes:
- Um repositório para armazenar o código do seu aplicativo.
- Uma maneira de receber webhooks localmente.
- Um GitHub App que está inscrito nos eventos de webhook "Conjunto de verificações" e "Execução de verificação", tem permissão de gravação para verificações e usa uma URL de webhook que você pode receber localmente.
Criar um repositório para armazenar código para o GitHub App
-
Crie o repositório para armazenar o código do seu aplicativo. Para obter mais informações, confira "Criar um repositório".
-
Clone o repositório da etapa anterior. Para obter mais informações, confira "Clonar um repositório". Você pode usar um clone local ou GitHub Codespaces.
-
No terminal, navegue até o diretório em que o clone está armazenado.
-
Crie um arquivo Ruby chamado
server.rb
. Este arquivo conterá todo o código para o seu aplicativo. Você adicionará conteúdo a esse arquivo mais tarde. -
Se o diretório ainda não incluir o arquivo
.gitignore
, adicione o arquivo.gitignore
. Você adicionará conteúdo a esse arquivo mais tarde. Para obter mais informações sobre os arquivos.gitignore
, confira "Ignorar arquivos". -
Crie um arquivo chamado
Gemfile
. Esse arquivo descreverá as dependências gem de que seu código Ruby precisa. Adicione o seguinte conteúdo ao seuGemfile
:Ruby source 'http://rubygems.org' gem 'sinatra', '~> 2.0' gem 'jwt', '~> 2.1' gem 'octokit', '~> 4.0' gem 'puma' gem 'rubocop' gem 'dotenv' gem 'git'
source 'http://rubygems.org' gem 'sinatra', '~> 2.0' gem 'jwt', '~> 2.1' gem 'octokit', '~> 4.0' gem 'puma' gem 'rubocop' gem 'dotenv' gem 'git'
-
Crie um arquivo chamado
config.ru
. Esse arquivo configurará o servidor Sinatra para ser executado. Adicione o seguinte conteúdo ao seu arquivoconfig.ru
:Ruby require './server' run GHAapp
require './server' run GHAapp
Obter a URL de proxy de webhook
Para desenvolver seu aplicativo localmente, você pode usar a URL de proxy de webhook para encaminhar eventos de webhook de GitHub para seu computador ou codespace. Este tutorial usa o Smee.io para fornecer a URL de proxy de webhook e encaminhar os eventos.
-
Em um terminal, execute o seguinte comando para instalar o cliente Smee:
Shell npm install --global smee-client
npm install --global smee-client
-
No navegador, navegue até https://smee.io/.
-
Clique em Iniciar um novo canal.
-
Copie a URL completa em "URL de Proxy de Webhook".
-
Em um terminal, execute o comando a seguir para instalar o cliente Smee. Substitua
YOUR_DOMAIN
pela URL do proxy de webhook que você copiou na etapa anterior.Shell smee --url YOUR_DOMAIN --path /event_handler --port 3000
smee --url YOUR_DOMAIN --path /event_handler --port 3000
Você verá algo semelhante ao mostrado a seguir:
Forwarding https://smee.io/YOUR_DOMAIN to http://127.0.0.1:3000/event_handler Connected https://smee.io/YOUR_DOMAIN
O comando smee --url https://smee.io/YOUR_DOMAIN
instrui o Smee a encaminhar todos os eventos de webhook recebidos pelo canal do Smee ao cliente do Smee em execução no computador. A opção --path /event_handler
encaminha eventos para a rota /event_handler
. A opção --port 3000
especifica a porta 3000, que é a porta que você informará ao servidor para escutar, quando você adicionar mais código posteriormente no tutorial. Usando a Smee, o seu computador não precisa estar conectado à internet pública para receber os webhooks do GitHub. Você também pode abrir a URL da Smee no seu navegador para inspecionar as cargas do webhook quando entrarem.
Recomendamos deixar esta janela de terminal aberta e manter a Smee conectada enquanto você realiza as outras etapas deste guia. Embora você possa desconectar e reconectar o cliente Smee sem perder seu domínio exclusivo, talvez você ache mais fácil mantê-lo conectado e realizar outras tarefas de linha de comando em outra janela do terminal.
Registrar o GitHub App
Para este tutorial, você precisa registrar um GitHub App que:
- Tem webhooks ativos
- Usa uma URL de webhook que você pode receber localmente
- Tem a permissão de repositório "Verificações"
- Assina os eventos de webhook "Conjunto de verificações" e "Execução de verificação"
As etapas a seguir orientarão você na configuração de um GitHub App com essas configurações. Para saber mais sobre as configurações do GitHub App, confira "Registrar um Aplicativo GitHub".
- No canto superior direito de qualquer página do GitHub, clique na foto do seu perfil.
- Acesse as configurações da sua conta.
- Para um aplicativo de propriedade de uma conta pessoal, clique em Configurações.
- Para um aplicativo de propriedade de uma organização:
- Clique em Suas organizações.
- À direita da organização, clique em Configurações.
- Na barra lateral esquerda, clique em Configurações do desenvolvedor.
- Na barra lateral esquerda, clique em GitHub Apps .
- Clique em Novo Aplicativo do GitHub.
- Em "Nome do GitHub App", insira um nome para seu aplicativo. Por exemplo,
USERNAME-ci-test-app
em queUSERNAME
é o nome de usuário do GitHub. - Em "URL da home page", insira a URL do seu aplicativo. Por exemplo, você pode usar a URL do repositório criado para armazenar o código do aplicativo.
- Ignore as seções "Identificar e autorizar usuários" e "Pós-instalação" deste tutorial.
- Verifique se Ativo está selecionado em "Webhooks".
- Em "URL de Webhook", insira a URL de proxy de webhook da etapa anterior. Para obter mais informações, confira "Obter uma URL de proxy de webhook".
- Em "Segredo do webhook", insira uma cadeia de caracteres aleatória. Esse segredo é usado para verificar se os webhooks são enviados por GitHub. Salve essa cadeia de caracteres; você a usará mais tarde.
- Em "Permissões de repositório", ao lado de "Verificações", selecione Leitura e gravação.
- Em "Assinar eventos", selecione Conjunto de verificações e Execução de verificação.
- Em "Em que local este GitHub App pode ser instalado?", selecione Somente nesta conta. Você poderá alterar isso mais tarde se quiser publicar o aplicativo.
- Clique em Criar Aplicativo do GitHub.
Armazenar as informações e credenciais de identificação do aplicativo
Este tutorial mostrará como armazenar as credenciais do aplicativo e identificar as informações como variáveis de ambiente no arquivo .env
. Quando você implantar seu aplicativo, deverá alterar a forma como armazena as credenciais. Para obter mais informações, confira "Implantar seu aplicativo".
Verifique se você está em um computador seguro antes de executar essas etapas, pois você armazenará suas credenciais localmente.
-
No seu terminal, navegue até o diretório em que o clone está armazenado.
-
Crie um arquivo chamado
.env
no nível superior desse diretório. -
Adicione
.env
ao arquivo.gitignore
. Isso impedirá você de fazer um commit acidental das credenciais do aplicativo. -
Adicione o conteúdo a seguir ao arquivo
.env
. SubstituaYOUR_HOSTNAME
pelo nome de sua instância do GitHub Enterprise Server. Você atualizará os outros valores em uma etapa posterior.Shell GITHUB_APP_IDENTIFIER="YOUR_APP_ID" GITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET" GITHUB_PRIVATE_KEY="YOUR_PRIVATE_KEY"
GITHUB_APP_IDENTIFIER="YOUR_APP_ID" GITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET" GITHUB_PRIVATE_KEY="YOUR_PRIVATE_KEY"
-
Navegue até a página configurações do seu aplicativo:
-
No canto superior direito de qualquer página do GitHub, clique na foto do seu perfil.
-
Acesse as configurações da sua conta.
- Para um aplicativo de propriedade de uma conta pessoal, clique em Configurações.
- Para um aplicativo de propriedade de uma organização:
- Clique em Suas organizações.
- À direita da organização, clique em Configurações.
-
Na barra lateral esquerda, clique em Configurações do desenvolvedor.
-
Na barra lateral esquerda, clique em GitHub Apps .
-
Ao lado do nome do aplicativo, clique em Editar.
-
-
Na página de configurações do aplicativo, ao lado de "ID do aplicativo", localize a ID do aplicativo para o seu aplicativo.
-
No arquivo
.env
, substituaYOUR_APP_ID
pela ID do seu aplicativo. -
No arquivo
.env
, substituaYOUR_WEBHOOK_SECRET
pelo segredo do webhook do seu aplicativo. Se você esqueceu o segredo do webhook, em "Segredo do webhook (opcional)", clique em Alterar segredo. Insira um novo segredo e clique em Salvar alterações. -
Na página de configurações do aplicativo, em "Chaves privadas", clique em Gerar uma chave privada. Você verá um arquivo
.pem
de chave privada baixado no seu computador. -
Abra o arquivo
.pem
com um editor de texto ou use o seguinte comando na linha de comando para ver o conteúdo do arquivo:cat PATH/TO/YOUR/private-key.pem
. -
Copie e cole todo o conteúdo do arquivo no seu arquivo
.env
como o valor deGITHUB_PRIVATE_KEY
e adicione aspas duplas ao redor do valor inteiro.Veja um arquivo .env de exemplo:
GITHUB_APP_IDENTIFIER=12345 GITHUB_WEBHOOK_SECRET=your webhook secret GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- ... HkVN9... ... -----END RSA PRIVATE KEY-----"
Adicionar código para o seu GitHub App
Esta seção mostrará como adicionar código de modelo básico para seu GitHub App e explicará o que o código faz. Posteriormente no tutorial, você aprenderá a modificar esse código e adicionar conteúdo a ele para criar a funcionalidade do aplicativo.
Adicione o seguinte código ao arquivo server.rb
:
require 'sinatra/base' # Use the Sinatra web framework require 'octokit' # Use the Octokit Ruby library to interact with GitHub's REST API require 'dotenv/load' # Manages environment variables require 'json' # Allows your app to manipulate JSON data require 'openssl' # Verifies the webhook signature require 'jwt' # Authenticates a GitHub App require 'time' # Gets ISO 8601 representation of a Time object require 'logger' # Logs debug statements # This code is a Sinatra app, for two reasons: # 1. Because the app will require a landing page for installation. # 2. To easily handle webhook events. class GHAapp < Sinatra::Application # Sets the port that's used when starting the web server. set :port, 3000 set :bind, '0.0.0.0' # Expects the private key in PEM format. Converts the newlines. PRIVATE_KEY = OpenSSL::PKey::RSA.new(ENV['GITHUB_PRIVATE_KEY'].gsub('\n', "\n")) # Your registered app must have a webhook secret. # The secret is used to verify that webhooks are sent by GitHub. WEBHOOK_SECRET = ENV['GITHUB_WEBHOOK_SECRET'] # The GitHub App's identifier (type integer). APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] # Turn on Sinatra's verbose logging during development configure :development do set :logging, Logger::DEBUG end # Executed before each request to the `/event_handler` route before '/event_handler' do get_payload_request(request) verify_webhook_signature # 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 authenticate_app # Authenticate the app installation in order to run API operations authenticate_installation(@payload) end post '/event_handler' do # ADD EVENT HANDLING HERE # 200 # success status end helpers do # ADD CREATE_CHECK_RUN HELPER METHOD HERE # # ADD INITIATE_CHECK_RUN HELPER METHOD HERE # # ADD CLONE_REPOSITORY HELPER METHOD HERE # # ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE # # Saves the raw payload and converts the payload to JSON format def get_payload_request(request) # request.body is an IO or StringIO object # Rewind in case someone already read it request.body.rewind # The raw text of the body is required for webhook signature verification @payload_raw = request.body.read begin @payload = JSON.parse @payload_raw rescue => e fail 'Invalid JSON (#{e}): #{@payload_raw}' end end # Instantiate an Octokit client authenticated as a GitHub App. # GitHub App authentication requires that you construct a # JWT (https://jwt.io/introduction/) signed with the app's private key, # so GitHub can be sure that it came from the app and not altered by # a malicious third party. def authenticate_app payload = { # The time that this JWT was issued, _i.e._ now. iat: Time.now.to_i, # JWT expiration time (10 minute maximum) exp: Time.now.to_i + (10 * 60), # Your GitHub App's identifier number iss: APP_IDENTIFIER } # Cryptographically sign the JWT. jwt = JWT.encode(payload, PRIVATE_KEY, 'RS256') # Create the Octokit client, using the JWT as the auth token. @app_client ||= Octokit::Client.new(bearer_token: jwt) end # Instantiate an Octokit client, authenticated as an installation of a # GitHub App, to run API operations. def authenticate_installation(payload) @installation_id = payload['installation']['id'] @installation_token = @app_client.create_app_installation_access_token(@installation_id)[:token] @installation_client = Octokit::Client.new(bearer_token: @installation_token) end # Check X-Hub-Signature to confirm that this webhook was generated by # GitHub, and not a malicious third party. # # GitHub uses the WEBHOOK_SECRET, registered to the GitHub App, to # create the hash signature sent in the `X-HUB-Signature` header of each # webhook. This code computes the expected hash signature and compares it to # the signature sent in the `X-HUB-Signature` header. If they don't match, # this request is an attack, and you should reject it. GitHub uses the HMAC # hexdigest to compute the signature. The `X-HUB-Signature` looks something # like this: 'sha1=123456'. def verify_webhook_signature their_signature_header = request.env['HTTP_X_HUB_SIGNATURE'] || 'sha1=' method, their_digest = their_signature_header.split('=') our_digest = OpenSSL::HMAC.hexdigest(method, WEBHOOK_SECRET, @payload_raw) halt 401 unless their_digest == our_digest # The X-GITHUB-EVENT header provides the name of the event. # The action value indicates the which action triggered the event. logger.debug "---- received event #{request.env['HTTP_X_GITHUB_EVENT']}" logger.debug "---- action #{@payload['action']}" unless @payload['action'].nil? end end # Finally some logic to let us run this server directly from the command line, # or with Rack. Don't worry too much about this code. But, for the curious: # $0 is the executed file # __FILE__ is the current file # If they are the same—that is, we are running this file directly, call the # Sinatra run method run! if __FILE__ == $0 end
require 'sinatra/base' # Use the Sinatra web framework
require 'octokit' # Use the Octokit Ruby library to interact with GitHub's REST API
require 'dotenv/load' # Manages environment variables
require 'json' # Allows your app to manipulate JSON data
require 'openssl' # Verifies the webhook signature
require 'jwt' # Authenticates a GitHub App
require 'time' # Gets ISO 8601 representation of a Time object
require 'logger' # Logs debug statements
# This code is a Sinatra app, for two reasons:
# 1. Because the app will require a landing page for installation.
# 2. To easily handle webhook events.
class GHAapp < Sinatra::Application
# Sets the port that's used when starting the web server.
set :port, 3000
set :bind, '0.0.0.0'
# Expects the private key in PEM format. Converts the newlines.
PRIVATE_KEY = OpenSSL::PKey::RSA.new(ENV['GITHUB_PRIVATE_KEY'].gsub('\n', "\n"))
# Your registered app must have a webhook secret.
# The secret is used to verify that webhooks are sent by GitHub.
WEBHOOK_SECRET = ENV['GITHUB_WEBHOOK_SECRET']
# The GitHub App's identifier (type integer).
APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER']
# Turn on Sinatra's verbose logging during development
configure :development do
set :logging, Logger::DEBUG
end
# Executed before each request to the `/event_handler` route
before '/event_handler' do
get_payload_request(request)
verify_webhook_signature
# 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
authenticate_app
# Authenticate the app installation in order to run API operations
authenticate_installation(@payload)
end
post '/event_handler' do
# ADD EVENT HANDLING HERE #
200 # success status
end
helpers do
# ADD CREATE_CHECK_RUN HELPER METHOD HERE #
# ADD INITIATE_CHECK_RUN HELPER METHOD HERE #
# ADD CLONE_REPOSITORY HELPER METHOD HERE #
# ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #
# Saves the raw payload and converts the payload to JSON format
def get_payload_request(request)
# request.body is an IO or StringIO object
# Rewind in case someone already read it
request.body.rewind
# The raw text of the body is required for webhook signature verification
@payload_raw = request.body.read
begin
@payload = JSON.parse @payload_raw
rescue => e
fail 'Invalid JSON (#{e}): #{@payload_raw}'
end
end
# Instantiate an Octokit client authenticated as a GitHub App.
# GitHub App authentication requires that you construct a
# JWT (https://jwt.io/introduction/) signed with the app's private key,
# so GitHub can be sure that it came from the app and not altered by
# a malicious third party.
def authenticate_app
payload = {
# The time that this JWT was issued, _i.e._ now.
iat: Time.now.to_i,
# JWT expiration time (10 minute maximum)
exp: Time.now.to_i + (10 * 60),
# Your GitHub App's identifier number
iss: APP_IDENTIFIER
}
# Cryptographically sign the JWT.
jwt = JWT.encode(payload, PRIVATE_KEY, 'RS256')
# Create the Octokit client, using the JWT as the auth token.
@app_client ||= Octokit::Client.new(bearer_token: jwt)
end
# Instantiate an Octokit client, authenticated as an installation of a
# GitHub App, to run API operations.
def authenticate_installation(payload)
@installation_id = payload['installation']['id']
@installation_token = @app_client.create_app_installation_access_token(@installation_id)[:token]
@installation_client = Octokit::Client.new(bearer_token: @installation_token)
end
# Check X-Hub-Signature to confirm that this webhook was generated by
# GitHub, and not a malicious third party.
#
# GitHub uses the WEBHOOK_SECRET, registered to the GitHub App, to
# create the hash signature sent in the `X-HUB-Signature` header of each
# webhook. This code computes the expected hash signature and compares it to
# the signature sent in the `X-HUB-Signature` header. If they don't match,
# this request is an attack, and you should reject it. GitHub uses the HMAC
# hexdigest to compute the signature. The `X-HUB-Signature` looks something
# like this: 'sha1=123456'.
def verify_webhook_signature
their_signature_header = request.env['HTTP_X_HUB_SIGNATURE'] || 'sha1='
method, their_digest = their_signature_header.split('=')
our_digest = OpenSSL::HMAC.hexdigest(method, WEBHOOK_SECRET, @payload_raw)
halt 401 unless their_digest == our_digest
# The X-GITHUB-EVENT header provides the name of the event.
# The action value indicates the which action triggered the event.
logger.debug "---- received event #{request.env['HTTP_X_GITHUB_EVENT']}"
logger.debug "---- action #{@payload['action']}" unless @payload['action'].nil?
end
end
# Finally some logic to let us run this server directly from the command line,
# or with Rack. Don't worry too much about this code. But, for the curious:
# $0 is the executed file
# __FILE__ is the current file
# If they are the same—that is, we are running this file directly, call the
# Sinatra run method
run! if __FILE__ == $0
end
O restante desta seção explicará o que o código de modelo faz. Não há etapas que você precisa realizar para concluir esta seção. Se já estiver familiarizado com o código do modelo, vá direto para "Iniciar o servidor".
Compreensão do código de modelo
Abra o arquivo server.rb
em um editor de texto. Você verá comentários em todo este arquivo que fornecem contexto adicional sobre o código de modelo. Recomendamos ler esses comentários com atenção e até mesmo adicionar seus próprios comentários para acompanhar o novo código que você escrever.
Abaixo da lista de arquivos necessários, o primeiro código que você verá é a declaração class GHApp < Sinatra::Application
. Você irá escrever todo o código para o seu GitHub App dentro desta classe. As seções a seguir explicam detalhadamente o que o código faz dentro dessa classe.
- Definir a porta
- Ler as variáveis de ambiente
- Ativar o registro em log
- Definir um filtro
before
- Definir o manipulador de rotas
- Definir os métodos auxiliares
Definir a porta
A primeira coisa que você verá dentro da declaração class GHApp < Sinatra::Application
é set :port 3000
. Isso define a porta usada ao iniciar o servidor Web para que corresponda à porta para a qual você redirecionou suas cargas de webhook em "Obter uma URL de proxy de webhook".
# Sets the port that's used when starting the web server.
set :port, 3000
set :bind, '0.0.0.0'
Lê as variáveis de ambiente
Em seguida, essa classe lê as três variáveis de ambiente definidas em "Armazenar as informações e credenciais de identificação do aplicativo" e as armazena em variáveis a serem usadas posteriormente.
# Expects the private key in PEM format. Converts the newlines.
PRIVATE_KEY = OpenSSL::PKey::RSA.new(ENV['GITHUB_PRIVATE_KEY'].gsub('\n', "\n"))
# Your registered app must have a webhook secret.
# The secret is used to verify that webhooks are sent by GitHub.
WEBHOOK_SECRET = ENV['GITHUB_WEBHOOK_SECRET']
# The GitHub App's identifier (type integer).
APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER']
Ativar o registro em log
Em seguida, um bloco do código que habilita o login durante o desenvolvimento, que é o ambiente-padrão np Sinatra. Esse código ativa o log no nível DEBUG
para mostrar uma saída útil no terminal durante o desenvolvimento do aplicativo.
# Turn on Sinatra's verbose logging during development
configure :development do
set :logging, Logger::DEBUG
end
Definir um filtro before
O Sinatra usa filtros before
que permitem executar o código antes do manipulador de rotas. O bloco before
no modelo chama quatro métodos auxiliares: get_payload_request
, verify_webhook_signature
, authenticate_app
e authenticate_installation
. Para obter mais informações, confira "Filtros" e "Auxiliares" na documentação do Sinatra.
# Executed before each request to the `/event_handler` route
before '/event_handler' do
get_payload_request(request)
verify_webhook_signature
# 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
authenticate_app
# Authenticate the app installation in order to run API operations
authenticate_installation(@payload)
end
Cada um desses métodos auxiliares é definido posteriormente no código, no bloco de código que começa com helpers do
. Para obter mais informações, confira "Definir os métodos auxiliares".
Em verify_webhook_signature
, o código que começa com unless @payload
é uma medida de segurança. Se um nome de repositório for fornecido com um payload de webhook, esse código validará que o nome do repositório contém apenas caracteres alfabéticos latinos, hifens e sublinhados. Isso ajuda a garantir que não exista um ator mal-intencionado tentando executar comandos arbitrários ou injetar nomes de repositório falsos. Posteriormente, no bloco de código que começa com helpers do
, o método auxiliar verify_webhook_signature
também valida os payloads de webhook de entrada como uma medida de segurança adicional.
Defina um gerenciador de encaminhamento
Um encaminhamento vazio está incluído no código do modelo. Esse código processa todas as solicitações POST
para a rota /event_handler
. Você adicionará mais código a esse repositório posteriormente.
post '/event_handler' do
end
Definir os métodos auxiliares
Quatro métodos auxiliares são chamados no bloco before
do código de modelo. O bloco de código helpers do
define cada um desses métodos auxiliares.
Gerenciar a carga do webhook
O primeiro método auxiliar get_payload_request
captura o payload do webhook e o converte no formato JSON, o que facilita muito o acesso aos dados do payload.
Verificar a assinatura do webhook
O segundo método auxiliar verify_webhook_signature
faz a verificação da assinatura do webhook para garantir que o GitHub gerou o evento. Para saber mais sobre o código no método auxiliar verify_webhook_signature
, confira "Validação de entregas de webhooks". Se os webhooks estiverem seguros, este método registrará todas as cargas de entrada no seu terminal. O código do agente é útil para verificar se o seu servidor web está funcionando.
Efetuar a autenticação um GitHub App
O terceiro método auxiliar authenticate_app
permite que seu GitHub App seja autenticado, para que ele possa solicitar um token de instalação.
Para fazer chamadas à API, você usará a biblioteca Octokit. Para fazer qualquer coisa interessante com essa biblioteca, o seu GitHub App precisará se autenticar. Para obter mais informações sobre a biblioteca Octokit, confira a documentação do Octokit.
Os GitHub Apps têm três métodos de autenticação:
- Autenticar-se como um GitHub App usando um JWT (Token Web JSON).
- Autenticar-se como uma instalação específica de um GitHub App usando um token de acesso de instalação.
- Autenticar-se em nome de um usuário. Este tutorial não usará esse método de autenticação.
Você aprenderá como autenticar como uma instalação na próxima seção, "Autenticar-se como uma instalação."
Efetuar a autenticação como um GitHub App permite que você faça algumas coisas:
- Você pode recuperar informações de gerenciamento de alto nível sobre seu GitHub App.
- Você pode solicitar tokens de acesso para uma instalação do aplicativo.
Por exemplo, você irá efetuar a autenticação como um GitHub App para recuperar uma lista das contas (da organização e pessoal) que instalaram seu aplicativo. Mas esse método de autenticação não permite que você faça muitas coisas com a API. Para acessar os dados de um repositório e realizar operações em nome da instalação, você precisa efetuar a autenticação como uma instalação. Para fazer isso, você precisará efetuar a autenticação como um GitHub App primeiro para solicitar um token de acesso de instalação. Para obter mais informações, confira "Sobre a autenticação com um GitHub App".
Para usar a biblioteca Octokit.rb a fim de fazer chamadas à API, você precisará inicializar um cliente do Octokit autenticado como um GitHub App usando o método auxiliar authenticate_app
.
# Instantiate an Octokit client authenticated as a GitHub App.
# GitHub App authentication requires that you construct a
# JWT (https://jwt.io/introduction/) signed with the app's private key,
# so GitHub can be sure that it came from the app an not altered by
# a malicious third party.
def authenticate_app
payload = {
# The time that this JWT was issued, _i.e._ now.
iat: Time.now.to_i,
# JWT expiration time (10 minute maximum)
exp: Time.now.to_i + (10 * 60),
# Your GitHub App's identifier number
iss: APP_IDENTIFIER
}
# Cryptographically sign the JWT
jwt = JWT.encode(payload, PRIVATE_KEY, 'RS256')
# Create the Octokit client, using the JWT as the auth token.
@app_client ||= Octokit::Client.new(bearer_token: jwt)
end
O código acima gera um JWT (JSON Web Token) e o usa (com a chave privada do seu aplicativo) para inicializar o cliente do Octokit. GitHub verifica a autenticação de uma solicitação, verificando o token com a chave pública armazenada no aplicativo. Para saber mais sobre como esse código funciona, confira "Como gerar um JWT (Token Web JSON) para um Aplicativo GitHub".
Autenticar como uma instalação
O quarto e último método auxiliar, authenticate_installation
, inicializa um cliente Octokit autenticado como uma instalação, que você pode usar para fazer chamadas autenticadas à API.
Uma instalação refere-se a qualquer conta de usuário ou de organização que instalou o aplicativo. Mesmo que alguém conceda ao aplicativo acesso a mais de um repositório, ela só conta como uma instalação, porque está dentro da mesma conta.
# Instantiate an Octokit client authenticated as an installation of a
# GitHub App to run API operations.
def authenticate_installation(payload)
installation_id = payload['installation']['id']
installation_token = @app_client.create_app_installation_access_token(installation_id)[:token]
@installation_client = Octokit::Client.new(bearer_token: installation_token)
end
O método create_app_installation_access_token
do Octokit cria um token de instalação. Para obter mais informações, confira "create_installation_access_token" na documentação do Octokit.
Este método aceita dois argumentos:
- Instalação (inteiro): a ID de uma instalação do GitHub App
- Opções (hash, usa
{}
como padrão): um conjunto personalizável de opções
Sempre que um GitHub App recebe um webhook, ele inclui um objeto installation
com uma id
. Ao usar o cliente autenticado como um GitHub App, transmita essa ID para o método create_app_installation_access_token
a fim de gerar um token de acesso para cada instalação. Uma vez que você não está passando nenhuma opção para o método, as opções-padrão para um hash vazio. A resposta para create_app_installation_access_token
inclui dois campos: token
e expired_at
. O código de modelo seleciona o token na resposta e inicializa um cliente de instalação.
Com este método em vigor, cada vez que seu aplicativo recebe uma nova carga de webhook, ele cria um cliente para a instalação que acionou o evento. Este processo de autenticação permite que seu GitHub App funcione para todas as instalações de qualquer conta.
Iniciar o servidor
Seu aplicativo ainda não faz nada, mas, neste momento, você pode executá-lo no servidor.
-
No terminal, verifique se o Smee ainda está em execução. Para obter mais informações, confira "Obter uma URL de proxy de webhook".
-
Abra uma nova guia no terminal e
cd
no diretório em que você clonou o repositório criado anteriormente no tutorial. Para obter mais informações, confira "Criar um repositório para armazenar código para seu Aplicativo GitHub". O código do Ruby nesse repositório iniciará um servidor Web do Sinatra. -
Instale as dependências executando os dois seguintes comandos, um após o outro:
Shell gem install bundler
gem install bundler
Shell bundle install
bundle install
-
Depois de instalar as dependências, inicie o servidor executando este comando:
Shell bundle exec ruby server.rb
bundle exec ruby server.rb
Você verá uma resposta semelhante a essa:
> == Sinatra (v2.2.3) has taken the stage on 3000 for development with backup from Puma > Puma starting in single mode... > * Puma version: 6.3.0 (ruby 3.1.2-p20) ("Mugi No Toki Itaru") > * Min threads: 0 > * Max threads: 5 > * Environment: development > * PID: 14915 > * Listening on http://0.0.0.0:3000 > Use Ctrl-C to stop
Se você receber um erro, verifique se criou o arquivo
.env
no diretório que contémserver.rb
. -
Para testar o servidor, use o navegador e acesse
http://localhost:3000
.Se você vir uma página de erro que diz "O Sinatra não conhece essa cantiga", o aplicativo está funcionando conforme o esperado. Mesmo sendo uma página de erro, é uma página de erro do Sinatra, o que significa que o seu aplicativo está conectado ao servidor, conforme esperado. Você está vendo essa mensagem porque você não deu ao aplicativo mais nada para mostrar.
Testar se o servidor está escutando seu aplicativo
Você pode testar se o servidor está ouvindo seu aplicativo acionando um evento para receber. Você fará isso instalando o aplicativo em um repositório de teste, que enviará o evento installation
para seu aplicativo. Se o aplicativo o receber, você verá uma saída na guia terminal em que está executando server.rb
.
-
Crie um repositório a ser usado para testar o código do tutorial. Para obter mais informações, confira "Criar um repositório".
-
Instale o GitHub App no repositório que você acabou de criar. Para obter mais informações, confira "Instalando seu próprio Aplicativo GitHub". Durante o processo de instalação, escolha Selecionar somente repositórios e selecione o repositório que você criou na etapa anterior.
-
Depois de clicar em Instalar, examine a saída na guia do terminal em que você está executando
server.rb
. Você deverá ver algo como:> D, [2023-06-08T15:45:43.773077 #30488] DEBUG -- : ---- received event installation > D, [2023-06-08T15:45:43.773141 #30488]] DEBUG -- : ---- action created > 192.30.252.44 - - [08/Jun/2023:15:45:43 -0400] "POST /event_handler HTTP/1.1" 200 - 0.5390
Se você vir uma saída como essa, isso significa que seu aplicativo recebeu uma notificação de que ele foi instalado em sua conta do GitHub. O aplicativo estará em execução no servidor, conforme esperado.
Se você não vir essa saída, verifique se o Smee está sendo executado corretamente em outra guia do terminal. Se precisar reiniciar o Smee, também precisará desinstalar e reinstalar o aplicativo para enviar o evento
installation
para seu aplicativo novamente e ver a saída no terminal.
Se você estiver se perguntando de onde vem a saída do terminal acima, ela será escrita no código do modelo de aplicativo que você adicionou server.rb
em "Adicionar código para seu GitHub App".
Parte 1. Criar a interface da API de verificações
Nesta parte, você adicionará o código necessário para receber eventos de webhook check_suite
e criar e atualizar as execuções de verificação. Você também aprenderá como criar uma execução de verificação quando solicitou-se a verificação novamente no GitHub. Ao final desta seção, você poderá visualizar a execução de verificação que você criou em uma solicitação de pull do GitHub.
Sua execução de verificação não realizará nenhuma verificação no código nesta seção. Você adicionará essa funcionalidade na "Parte 2: Como criar um teste de CI".
Você já deve ter um canal do Smee configurado que encaminha cargas do webhook para o seu servidor local. Seu servidor deve estar em execução e conectado ao GitHub App que você registrou e instalou em um repositório de teste.
Estas são as etapas que você concluirá na Parte 1:
- Adicionar um tratamento de evento
- Criar uma execução de verificação
- Atualizar uma execução de verificação
Etapa 1.1. Adicionar gerenciamento de evento
Já que o aplicativo está inscrito nos eventos Verificar conjunto e Verificar execução, ele começará a receber os webhooks check_suite
e check_run
. GitHub envia cargas de webhook como solicitações POST
. Como você encaminhou o conteúdo do webhook do Smee para o http://localhost:3000/event_handler
, o servidor receberá o conteúdo da solicitação POST
na rota post '/event_handler'
.
Abra o arquivo server.rb
que você criou em "Adicionar código para seu GitHub App" e procure o código a seguir. Uma rota post '/event_handler'
vazia está incluída no código do modelo. O encaminhamento vazio tem a seguinte forma:
post '/event_handler' do
# ADD EVENT HANDLING HERE #
200 # success status
end
No bloco de código que começa com post '/event_handler' do
, onde diz # ADD EVENT HANDLING HERE #
, adicione o código a seguir. Essa rota manipulará o evento 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 # ADD CHECK_RUN METHOD HERE # end
# 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
# ADD CHECK_RUN METHOD HERE #
end
Cada evento enviado pelo GitHub inclui um cabeçalho de solicitação chamado HTTP_X_GITHUB_EVENT
, que indica o tipo de evento na solicitação POST
. Nesse momento, você só está interessado em eventos do tipo check_suite
, que são emitidos quando um novo conjunto de verificação é criado. Cada evento tem um campo action
adicional que indica o tipo de ação que disparou os eventos. Para check_suite
, o campo action
pode ser requested
, rerequested
ou completed
.
A ação requested
solicita uma execução de verificação sempre que o código é enviado por push para o repositório, e a ação rerequested
solicita que você execute novamente uma verificação do código que já está no repositório. Como as ações requested
e rerequested
exigem a criação de uma execução de verificação, você chamará um auxiliar com o nome create_check_run
. Vamos escrever esse método agora.
Etapa 1.2. Criar uma execução de verificação
Você adicionará esse novo método como auxiliar do Sinatra caso queira que outras rotas também o usem.
No bloco de código que começa com helpers do
, onde diz # ADD CREATE_CHECK_RUN HELPER METHOD HERE #
, adicione o seguinte código:
# 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 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
Esse código chama o ponto de extremidade POST /repos/{owner}/{repo}/check-runs
usando o método create_check_run do Octokit. Para obter mais informações sobre esse ponto de extremidade, confira "Pontos de extremidade da API REST para execuções de verificação".
Para criar uma execução de verificação, apenas dois parâmetros de entrada são necessários: name
e head_sha
. Nesse código, nomeamos a execução de verificação "Octo RuboCop", pois usaremos o RuboCop para implementar o teste de CI mais adiante no tutorial. Mas você pode escolher qualquer nome que desejar para a execução de verificação. Para obter mais informações sobre esse plug-in, consulte a Documentação do RuboCop.
Você só está fornecendo os parâmetros necessários agora para que a funcionalidade básica funcione, mas você irá atualizar a verificação de execução posteriormente enquanto coleta mais informações sobre a verificação de execução. Por padrão, GitHub define o status
como queued
.
O GitHub cria uma execução de verificação para uma SHA de commit específica e é por isso que head_sha
é um parâmetro necessário. Você pode encontrar commit SHA na carga do webhook. Embora você esteja apenas criando uma execução de verificação para o evento check_suite
no momento, é bom saber que o head_sha
está incluído nos objetos check_suite
e check_run
na carga do evento.
O código acima usa um operador ternário, que funciona como uma instrução if/else
, para verificar se o conteúdo contém um objeto check_run
. Se isso acontecer, você lerá o head_sha
do objeto check_run
, caso contrário, você o lerá do objeto check_suite
.
Testar o código
As etapas a seguir mostrarão como testar se o código funciona e que ele cria com êxito uma execução de verificação.
-
Execute o comando a seguir para reiniciar o servidor por meio do terminal. Se o servidor já estiver em execução, primeiro insira
Ctrl-C
no terminal para interromper o servidor e execute o comando a seguir para iniciar o servidor novamente.Shell ruby server.rb
ruby server.rb
-
Crie uma solicitação de pull no repositório de teste que você criou em "Testar se o servidor está escutando seu aplicativo". Esse é o repositório que você permitiu que o aplicativo acessasse.
-
Na solicitação de pull recém-criada, navegue até a guia Verificações. Você verá uma execução de verificação com o nome "Octo RuboCop" ou qualquer nome escolhido anteriormente para a execução de verificação.
Se você vir outros aplicativos na guia Verificações, significa que você tem outros aplicativos instalados em seu repositório que têm acesso de Leitura e gravação às verificações e que estão inscritos nos eventos Conjunto de verificações e Execução de verificação. Isso também pode significar que você tem os fluxos de trabalho de GitHub Actions no repositório que são disparados pelo evento pull_request
ou pull_request_target
.
Até agora, você disse ao GitHub para criar uma execução de verificação. O status da execução de verificação na solicitação de pull é definido como enfileirado com um ícone amarelo. Na próxima etapa, você aguardará que o GitHub crie a execução de verificação e atualize o status dela.
Etapa 1.3. Atualizar uma execução de verificação
Quando o método create_check_run
é executado, ele solicitada que GitHub crie uma execução de verificação. Quando o GitHub terminar de criar a execução de verificação, você receberá o evento de webhook check_run
com a ação created
. Esse evento é o sinal para começar a executar a verificação.
Você atualizará o manipulador de eventos para procurar a ação created
. Enquanto estiver atualizando o manipulador de eventos, você pode adicionar uma condição para a ação rerequested
. Quando alguém executa novamente um teste individual no GitHub clicando no botão "Executar novamente", o GitHub envia o evento de execução de verificação rerequested
para seu aplicativo. Quando uma execução de verificação for rerequested
, você reiniciará o processo e criará uma execução de verificação. Para fazer isso, você incluirá uma condição para o evento check_run
na rota post '/event_handler'
.
No bloco de código que começa com post '/event_handler' do
, onde diz # ADD CHECK_RUN METHOD HERE #
, adicione o seguinte código:
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 # ADD REQUESTED_ACTION METHOD HERE # end end
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
# ADD REQUESTED_ACTION METHOD HERE #
end
end
O GitHub envia todos os eventos para execuções de verificação created
para cada aplicativo instalado em um repositório que tenha as permissões de verificação necessárias. Isso significa que seu aplicativo receberá uma verificação que será executada por outros aplicativos. Uma execução de verificação created
é um pouco diferente de um conjunto de verificação requested
ou rerequested
, que o GitHub envia apenas para aplicativos que estão sendo solicitados a executar uma verificação. O código acima procura o ID do aplicativo da execução de verificação. Isto filtra todas as execuções de verificação de outros aplicativos no repositório.
Em seguida, você escreverá o método initiate_check_run
, que é o local em que você atualizará o status da execução de verificação e se preparará para iniciar o teste de CI.
Nesta seção, você ainda não iniciará o teste de CI, mas examinará como atualizar o status da execução de verificação de queued
para pending
e depois de pending
para completed
para ver o fluxo geral de uma execução de verificação. Na "Parte 2: Como criar um teste de CI", você adicionará o código que realmente executará o teste de CI.
Vamos criar o método initiate_check_run
e atualizar o status da execução de verificação.
No bloco de código que começa com helpers do
, onde diz # ADD INITIATE_CHECK_RUN HELPER METHOD HERE #
, adicione o seguinte código:
# 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
# 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
O código acima chama o ponto de extremidade PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}
usando o método Octokit update_check_run
e atualiza a execução de verificação que você já criou. Para obter mais informações sobre esse ponto de extremidade, confira "Pontos de extremidade da API REST para execuções de verificação".
Veja o que este código está fazendo. Primeiro, ele atualiza o status da execução de verificação para in_progress
e define implicitamente a hora started_at
com a hora atual. Na Parte 2 deste tutorial, você adicionará um código que inicia um teste de CI real em ***** RUN A CI TEST *****
. Por enquanto, você sairá da seção como um espaço reservado, para que o código que o segue apenas simule que o processo de CI seja bem-sucedido e todos os testes sejam aprovados. Por fim, o código atualiza o status da execução da verificação novamente para completed
.
Quando você usa a API REST para fornecer um status de execução de verificação de completed
, os parâmetros conclusion
e completed_at
são necessários. O conclusion
resume o resultado de uma execução de verificação e pode ser success
, failure
, neutral
, cancelled
, timed_out
, skipped
ou action_required
. Você definirá a conclusão como success
, a hora completed_at
com a hora atual, e o status como completed
.
Você também pode fornecer mais informações sobre o que a sua verificação está fazendo, mas você poderá fazer isso na próxima seção.
Testar o código
As etapas a seguir mostrarão como testar se o código funciona e que o novo botão "Executar novamente tudo" que você criou funciona.
-
Execute o comando a seguir para reiniciar o servidor por meio do terminal. Se o servidor já estiver em execução, primeiro insira
Ctrl-C
no terminal para interromper o servidor e execute o comando a seguir para iniciar o servidor novamente.Shell ruby server.rb
ruby server.rb
-
Crie uma solicitação de pull no repositório de teste que você criou em "Testar se o servidor está escutando seu aplicativo". Esse é o repositório que você permitiu que o aplicativo acessasse.
-
Na solicitação de pull recém-criada, navegue até a guia Verificações. Você verá um botão "Executar tudo novamente".
-
Clique no botão "Executar tudo novamente" no canto superior direito. O teste deve ser executado novamente e terminar com
success
.
Parte 2. Como criar um teste de CI
Agora que a interface foi criada para receber eventos da API e criar execuções de verificação, você pode criar uma execução de verificação que implemente um teste de CI.
O RuboCop é um formatador e um linter de código Ruby. Ele verifica o código Ruby para garantir que ele esteja em conformidade com o Guia de Estilo do Ruby. Para obter mais informações, confira a Documentação do RuboCop.
O RuboCop tem três funções principais:
- Linting para verificação do estilo do código
- Formatação de código
- Substitui os recursos nativos de linting do Ruby usando
ruby -w
Seu aplicativo executará o RuboCop no servidor de CI e irá criar uma execuções de verificação (neste caso, testes de CI), que relatarão os resultados que o RuboCop relata para o GitHub.
A API REST permite que você relate informações valiosas sobre cada execução de verificação, incluindo status, imagens, resumos, anotações e ações solicitadas.
As anotações são informações sobre linhas específicas de código em um repositório. Uma anotação permite que você identifique e visualize as partes exatas do código para as quais você gostaria de mostrar informações adicionais. Por exemplo, você pode mostrar essas informações como um comentário, erro ou aviso em uma linha de código específica. Este início rápido usa anotações para visualizar erros no RuboCop.
Para aproveitar as ações solicitadas, os desenvolvedores de aplicativos podem criar botões na guia Verificações das solicitações de pull. Quando alguém clica em um desses botões, o clique envia um evento requested_action
check_run
para o GitHub App. A ação tomada pelo aplicativo é completamente configurável pelo desenvolvedor do aplicativo. Este tutorial irá orientá-lo no processo de adição de um botão que permite aos usuários solicitar que o RuboCop corrija os erros que encontrar. O RuboCop dá suporte à correção automática de erros usando uma opção de linha de comando e você configurará a opção requested_action
para aproveitar essa opção.
Estas são as etapas que você concluirá nesta seção:
- Adicionar um arquivo do Ruby
- Permitir que o RuboCop clone o repositório de teste
- Executar o RuboCop
- Coletar erros do RuboCop
- Atualizar a execução de verificação com resultados dos testes de CI
- Corrigir erros do RuboCop automaticamente
Etapa 2.1. Adicionar um arquivo do Ruby
Você pode passar arquivos específicos ou diretórios inteiros para o RuboCop verificar. Nesse tutorial, você irá executar o RuboCop em um diretório inteiro. O RuboCop verifica apenas o código Ruby. Para testar seus dados do GitHub App, você precisará adicionar um arquivo Ruby em seu repositório que contenha erros para o RuboCop localizar. Depois de adicionar o arquivo Ruby a seguir ao repositório, você atualizará o marcar de CI para executar o RuboCop no código.
-
Navegue até o repositório de teste que você criou em "Testar se o servidor está escutando seu aplicativo". Esse é o repositório que você permitiu que o aplicativo acessasse.
-
Crie um arquivo chamado
myfile.rb
. Para obter mais informações, confira "Criar arquivos". -
Adicione o seguinte conteúdo a
myfile.rb
:Ruby # frozen_string_literal: true # 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
# frozen_string_literal: true # 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
-
Se você criou o arquivo localmente, faça commit do arquivo e envie-o por push para o repositório em GitHub.
Etapa 2.2. Permitir que o RuboCop clone o repositório de teste
O RuboCop está disponível como um utilitário da linha de comando. Isso significa que, se você quiser executar o RuboCop em um repositório, seu GitHub App precisará clonar uma cópia local do repositório no servidor de CI para que o RuboCop possa analisar os arquivos. Para fazer isso, seu código precisará ser capaz de executar operações do Git, e seu GitHub App precisará ter as permissões corretas para clonar um repositório.
Permitir operações do Git
Para executar operações Git em seu aplicativo Ruby, use a gem ruby-git. O Gemfile
que você criou em "Configuração" já inclui a gem ruby-git, e você a instalou quando executou bundle install
em "Iniciar o servidor."
Agora, na parte superior do arquivo server.rb
, abaixo dos outros itens require
, adicione o seguinte código:
require 'git'
require 'git'
Atualizar as permissões do aplicativo
Em seguida, você precisará atualizar suas permissões do GitHub App. Seu aplicativo precisará de permissão de leitura para "Conteúdo" para clonar um repositório. E, posteriormente neste tutorial, ele precisará de permissão de gravação para enviar conteúdo por push para GitHub. Para atualizar as permissões do aplicativo:
- Selecione seu aplicativo na página de configurações do aplicativo e clique em Permissões e eventos na barra lateral.
- Em "Permissões de repositório", ao lado de "Conteúdo", selecione Leitura e gravação.
- Clique em Salvar alterações na parte inferior da página.
- Se você instalou o aplicativo na sua conta, verifique seu e-mail e siga o link para aceitar novas permissões. Sempre que você alterar as permissões ou webhooks do seu aplicativo, os usuários que instalaram o aplicativo (incluindo você mesmo) precisarão aceitar as novas permissões antes que as alterações tenham efeito. Você também pode aceitar as novas permissões navegando até a página de instalações. Você verá um link sob o nome da página, permitindo que você saiba que o aplicativo está solicitando diferentes permissões. Clique em "Examinar solicitação" e depois em "Aceitar novas permissões".
Adicionar código para clonar um repositório
Para clonar um repositório, o código usará as permissões do GitHub Appe o SDK do Octokit para criar um token de instalação para seu aplicativo (x-access-token:TOKEN
) e usá-lo no seguinte comando de clonagem:
git clone https://x-access-token:TOKEN@github.com/OWNER/REPO.git
O código acima clona um repositório por HTTP. Isto exige o nome completo do repositório, que inclui o proprietário do repositório (usuário ou organização) e o nome do repositório. Por exemplo, o repositório octocat Hello-World tem o nome completo octocat/hello-world
.
Abra o arquivo server.rb
. No bloco de código que começa com helpers do
, onde diz # ADD CLONE_REPOSITORY HELPER METHOD HERE #
, adicione o seguinte código:
# 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
# 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
O código acima usa a gem ruby-git
para clonar o repositório usando o token de instalação do aplicativo. Ele clona o código no mesmo diretório que server.rb
. Para executar comandos Git no repositório, o código deve alterar para o diretório do repositório. Antes de alterar os diretórios, o código armazena o diretório de trabalho atual em uma variável (pwd
) para lembrar o local para retornar antes de sair do método clone_repository
.
No diretório do repositório, esse código busca e mescla as alterações mais recentes (@git.pull
), faz check-out do ref específico do Git (@git.checkout(ref)
). O código para fazer tudo isto encaixa-se perfeitamente no próprio método. Para realizar essas operações, o método precisa do nome e nome completo do repositório e que o ref faça checkout. O ref pode ser um commit SHA, branch ou tag. Quando terminar, o código alterará o diretório de volta para o diretório de trabalho original (pwd
).
Agora você tem um método que clona um repositório e faz check-out de um ref. Em seguida, você precisa adicionar código para obter os parâmetros de entrada necessários e chamar o novo método clone_repository
.
No bloco de código que começa com helpers do
, no método auxiliar initiate_check_run
, onde diz # ***** RUN A CI TEST *****
, adicione o seguinte código:
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) # ADD CODE HERE TO RUN RUBOCOP #
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)
# ADD CODE HERE TO RUN RUBOCOP #
O código acima obtém o nome completo do repositório e o SHA principal do commit do conteúdo do webhook check_run
.
Etapa 2.3. Executar o RuboCop
Até agora, o código clona o repositório e cria execuções de verificação usando o servidor de CI. Agora, você entrará nos detalhes do linter do RuboCop e das anotações de verificações.
Primeiro, você adicionará código para executar o RuboCop e salvar os erros de código de estilo no formato JSON.
No bloco de código que começa com helpers do
, localize o método auxiliar initiate_check_run
. Dentro desse método auxiliar, em clone_repository(full_repo_name, repository, head_sha)
, onde diz # ADD CODE HERE TO RUN RUBOCOP #
, adicione o seguinte código:
# Run RuboCop on all files in the repository @report = `rubocop '#{repository}' --format json` logger.debug @report `rm -rf #{repository}` @output = JSON.parse @report # ADD ANNOTATIONS CODE HERE #
# Run RuboCop on all files in the repository
@report = `rubocop '#{repository}' --format json`
logger.debug @report
`rm -rf #{repository}`
@output = JSON.parse @report
# ADD ANNOTATIONS CODE HERE #
O código acima executa o RuboCop em todos os arquivos no diretório do repositório. A opção --format json
salva uma cópia dos resultados do linting em um formato que possa ser analisado pelo computador. Para obter mais informações e um exemplo do formato JSON, confira "Formatador JSON" nos documentos do RuboCop. Esse código também analisa o JSON para que você possa acessar facilmente as chaves e os valores em seu GitHub App usando a variável @output
.
Depois de executar o RuboCop e salvar os resultados do linting, esse código executa o comando rm -rf
para remover o check-out do repositório. Como o código armazena os resultados do RuboCop em uma variável @report
, ele pode remover com segurança o check-out do repositório.
O comando rm -rf
não pode ser desfeito. Para manter seu aplicativo seguro, o código neste tutorial verifica os webhooks de entrada em busca de comandos mal-intencionados injetados que podem ser usados para remover um diretório diferente do pretendido pelo seu aplicativo. Por exemplo, se um ator malicioso enviasse um webhook com o nome de repositório ./
, seu aplicativo removeria o diretório raiz. O método verify_webhook_signature
valida o remetente do webhook. O manipulador de eventos verify_webhook_signature
também verifica se o nome do repositório é válido. Para obter mais informações, confira "Definir um filtro before
".
Testar o código
As etapas a seguir mostrarão como testar se o código funciona e exibir os erros relatados pelo RuboCop.
-
Execute o comando a seguir para reiniciar o servidor por meio do terminal. Se o servidor já estiver em execução, primeiro insira
Ctrl-C
no terminal para interromper o servidor e execute o comando a seguir para iniciar o servidor novamente.Shell ruby server.rb
ruby server.rb
-
No repositório em que você adicionou o arquivo
myfile.rb
, crie uma solicitação de pull. -
Na guia do terminal em que o servidor está sendo executado, você deverá ver a saída de depuração que contém erros de lint. Os erros de lint são impressos sem nenhuma formatação. Você pode copiar e colar a saída de depuração em uma ferramenta da Web, como o formatador JSON, para formatar a saída JSON como no seguinte exemplo:
{ "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 } }
Etapa 2.4. Coletar erros do RuboCop
A variável @output
contém os resultados JSON analisados do relatório do RuboCop. Conforme na saída de exemplo da etapa anterior, os resultados contêm uma seção summary
que seu código pode usar para determinar rapidamente se há erros. O código a seguir definirá a conclusão da execução de verificação como success
quando não houver erros relatados. O RuboCop relata erros para cada arquivo na matriz files
, portanto, se houver erros, você precisará extrair alguns dados do objeto de arquivo.
Os pontos de extremidade da API REST para gerenciar execuções de verificação permitem que você crie anotações para linhas de código específicas. Ao criar ou atualizar uma execução de verificação, você pode adicionar anotações. Neste tutorial, você atualizará a execução de verificação com anotações, usando o ponto de extremidade PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}
. Para obter mais informações sobre esse ponto de extremidade, confira "Pontos de extremidade da API REST para execuções de verificação".
A API limita o número de anotações a um máximo de 50 por solicitação. Para criar mais de 50 anotações, você precisará fazer várias solicitações para o ponto de extremidade "Atualizar uma execução de verificação". Por exemplo, para criar 105 anotações, você precisaria fazer três solicitações separadas à API. Cada uma das duas primeiras solicitações teria 50 anotações e a terceira solicitação incluiria as cinco anotações restantes. Cada vez que você atualizar a execução de verificação, as anotações são anexadas à lista de anotações que já existem para a execução de verificação.
Uma execução de verificação espera anotações como um array de objetos. Cada objeto de anotação deve incluir o path
, start_line
, end_line
, annotation_level
e message
. O RuboCop também fornece o start_column
e end_column
, portanto, você pode incluir esses parâmetros opcionais na anotação. As anotações só dão suporte a start_column
e end_column
na mesma linha. Para saber mais, confira o objeto annotations
em "Pontos de extremidade da API REST para execuções de verificação".
Agora, você adicionará código para extrair as informações necessárias do RuboCop para criar cada anotação.
Após o código adicionado na etapa anterior, onde diz # ADD ANNOTATIONS CODE HERE #
, adicione o seguinte código:
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 # ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #
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
# ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #
Este código limita o número total de anotações a 50. Mas você pode modificar este código para atualizar a verificação de execução para cada lote de 50 anotações. O código acima inclui a variável max_annotations
que define o limite como 50, que é usado no loop que itera nas ofensas.
Quando a offense_count
é zero, o teste de CI é um success
. Se houver erros, esse código definirá a conclusão como neutral
para evitar a imposição estrita de erros de linters de código. Mas você pode alterar a conclusão para failure
se quiser garantir que o conjunto de verificação falhe quando houver erros de linting.
Quando erros são relatados, o código acima itera na matriz files
no relatório do RuboCop. Para cada arquivo, ele extrai o caminho do arquivo e define o nível de anotação como notice
. Você pode ir ainda mais além e definir níveis de aviso específicos para cada tipo de RuboCop Cop, mas para manter as coisas mais simples neste tutorial, todos os erros são definidos como um nível de notice
.
Esse código também itera em cada erro na matriz de offenses
e coleta o local da mensagem de erro e ofensa. Depois de extrair as informações necessárias, o código cria uma anotação para cada erro e as armazena na matriz annotations
. Como as anotações só dão suporte a colunas de início e fim na mesma linha, start_column
e end_column
só são adicionadas ao objeto annotation
se os valores de linha inicial e final forem os mesmos.
Esse código ainda não cria uma anotação para a execução de verificação. Você irá adicionar esse código na próxima seção.
Etapa 2.5. Atualizar a execução de verificação com resultados dos testes de CI
Cada execução de verificação do GitHub contém um objeto output
que inclui um title
, summary
, text
, annotations
e images
. Os parâmetros summary
e title
são os únicos necessários para o output
, mas estes sozinhos não oferecem muitos detalhes, portanto, este tutorial também adiciona text
e annotations
.
Para o summary
, este exemplo usa as informações de resumo do RuboCop e adiciona linhas novas (\n
) para formatar a saída. Você pode personalizar o que adicionar ao parâmetro text
, mas este exemplo define o parâmetro text
com a versão do RuboCop. O código a seguir define summary
e text
.
Após o código adicionado na etapa anterior, onde diz # ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #
, adicione o seguinte código:
# 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']}"
# 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']}"
Agora, seu código deve ter todas as informações necessárias para atualizar sua execução de verificação. Em "Etapa 1.3. Atualizar uma execução de verificação", você adicionou o código para definir o status da execução de verificação como success
. Você precisará atualizar esse código para usar a variável conclusion
definida com base nos resultados do RuboCop (para success
ou neutral
). Aqui está o código que você adicionou anteriormente ao arquivo server.rb
:
# 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'
)
Substitua-o pelo seguinte código:
# 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' )
# 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'
)
Agora que o seu código define uma conclusão com base no status do teste CI e que você adicionou a saída dos resultados do RuboCop, você criou um teste de CI.
O código acima também adiciona ao servidor de CI um recurso chamado ações solicitadas, por meio do objeto actions
. Para obter mais informações, confira "Solicitar outras ações de uma execução de verificação". As ações solicitadas adicionam um botão à aba Verificações no GitHub que permite que alguém solicite execução de verificação para tomar medidas adicionais. A ação adicional é completamente configurável pelo seu aplicativo. Por exemplo, uma vez que o RuboCop tem um recurso para corrigir automaticamente os erros que encontra no código Ruby, seu servidor de CI pode usar um botão de ações solicitadas para permitir que as pessoas solicitem correções automáticas de erros. Quando alguém clica no botão, o aplicativo recebe o evento check_run
com uma ação requested_action
. Cada ação solicitada tem um identifier
que o aplicativo usa para determinar qual botão foi clicado.
O código acima ainda não exige que o RuboCop corrija erros automaticamente. Você adicionará isso posteriormente no tutorial.
Testar o código
As etapas a seguir mostrarão como testar se o código funciona e exibir o teste de CI que você acabou de criar.
-
Execute o comando a seguir para reiniciar o servidor por meio do terminal. Se o servidor já estiver em execução, primeiro insira
Ctrl-C
no terminal para interromper o servidor e execute o comando a seguir para iniciar o servidor novamente.Shell ruby server.rb
ruby server.rb
-
No repositório em que você adicionou o arquivo
myfile.rb
, crie uma solicitação de pull. -
Na solicitação de pull que você acabou de criar, navegue até a guia Verificações. Você deve ver anotações para cada um dos erros encontrados pelo RuboCop. Observe o botão "Corrigir isso" que você criou ao adicionar uma ação solicitada.
Etapa 2.6. Corrigir erros do RuboCop automaticamente
Até agora, você criou um teste de CI. Nesta seção, você irá adicionar mais um recurso que usa RuboCop para corrigir automaticamente os erros que encontra. Você já adicionou o botão "Corrigir isso" na "Etapa 2.5. Atualizar a execução de verificação com os resultados do teste de CI." Agora você adicionará o código para manipular o evento de execução de verificação requested_action
que é disparado quando alguém clicar no botão "Corrigir isso".
A ferramenta RuboCop oferece a opção de linha de comando --auto-correct
para corrigir automaticamente os erros encontrados. Para obter mais informações, confira "Autocorreção de infrações" na documentação do RuboCop. Quando você usa o recurso --auto-correct
, as atualizações são aplicadas aos arquivos locais no servidor. Você precisará enviar as alterações por push para GitHub depois que o RuboCop fizer as correções.
Para enviar por push para um repositório, seu aplicativo precisa ter permissões de gravação de "Conteúdo" em um repositório. Você já definiu essa permissão como Leitura e gravação na "Etapa 2.2. Permitir que o RuboCop clone o repositório de teste".
Para fazer commit dos arquivos, o Git precisa saber qual nome de usuário e email associar ao commit. Em seguida, você adicionará variáveis de ambiente para armazenar o nome e o endereço de email que seu aplicativo usará quando ele fizer commits do Git.
-
Abra o arquivo
.env
que você criou anteriormente neste tutorial. -
Adicione as variáveis de ambiente a seguir ao seu arquivo
.env
. SubstituaAPP_NAME
pelo nome do aplicativo eEMAIL_ADDRESS
por qualquer email que você gostaria de usar para este exemplo.Shell GITHUB_APP_USER_NAME="APP_NAME" GITHUB_APP_USER_EMAIL="EMAIL_ADDRESS"
GITHUB_APP_USER_NAME="APP_NAME" GITHUB_APP_USER_EMAIL="EMAIL_ADDRESS"
Em seguida, você precisará adicionar código para ler as variáveis de ambiente e definir a configuração do Git. Você irá adicionar esse código em breve.
Quando alguém clica no botão "Corrigir isso", seu aplicativo recebe o webhook de execução de verificação com o tipo de ação requested_action
.
Na "Etapa 1.3. Atualizar uma execução de verificação, você atualizou o event_handler
no arquivo server.rb
para lidar com a procura de ações no evento check_run
. Você já tem uma instrução case para lidar com os tipos de ação created
e rerequested
:
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
# ADD REQUESTED_ACTION METHOD HERE #
end
end
Após a instrução case rerequested
, onde diz # ADD REQUESTED_ACTION METHOD HERE #
, adicione o seguinte código:
when 'requested_action' take_requested_action
when 'requested_action'
take_requested_action
Esse código chama um novo método que manipulará todos os eventos requested_action
para seu aplicativo.
No bloco de código que começa com helpers do
, onde diz # ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #
, adicione o seguinte método auxiliar:
# 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
# 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
O código acima clona um repositório, assim como o código que você adicionou na "Etapa 2.2. Permitir que o RuboCop clone o repositório de teste". Uma instrução if
verifica se o identificador da ação solicitada corresponde ao identificador do botão do RuboCop (fix_rubocop_notices
). Quando eles correspondem, o código clona o repositório, define o nome de usuário e o email do Git e executa o RuboCop com a opção --auto-correct
. A opção --auto-correct
aplica as alterações aos arquivos do servidor de CI local automaticamente.
Os arquivos são alterados localmente, mas você ainda deverá enviá-los por push para GitHub. Você usará a gem ruby-git
para fazer commit de todos os arquivos. O Git tem um único comando que prepara todos os arquivos modificados ou excluídos e faz o commit deles: git commit -a
. Para fazer a mesma coisa usando ruby-git
, o código acima usa o método commit_all
. Em seguida, o código envia por push os arquivos do commit para o GitHub usando o token de instalação, usando o mesmo método de autenticação que o comando clone
do Git. Por fim, ele remove o diretório do repositório para garantir que o diretório de trabalho seja preparado para o próximo evento.
O código que você escreveu agora conclui o servidor de integração contínua criado usando um GitHub App e faz a verificação. Para ver o código final completo do aplicativo, confira "Exemplo de código completo".
Testar o código
As etapas a seguir mostrarão como testar se o código funciona e que o RuboCop pode corrigir automaticamente os erros que encontra.
-
Execute o comando a seguir para reiniciar o servidor por meio do terminal. Se o servidor já estiver em execução, primeiro insira
Ctrl-C
no terminal para interromper o servidor e execute o comando a seguir para iniciar o servidor novamente.Shell ruby server.rb
ruby server.rb
-
No repositório em que você adicionou o arquivo
myfile.rb
, crie uma solicitação de pull. -
Na solicitação de pull criada, navegue até a guia Verificações e clique no botão "Corrigir isso" para corrigir automaticamente os erros que o RuboCop encontrou.
-
Navegue até a guia Commits. Você deverá ver um commit novo feito pelo nome de usuário definido na configuração do Git. Talvez seja necessário atualizar seu navegador para ver a atualização.
-
Navegue até a guia Verificações. Você deverá ver um novo pacote de verificações para Octo RuboCop. Mas desta vez não deverá haver erros, porque o RuboCop corrigiu todos eles.
Exemplo de código completo
É assim que o código final no server.rb
deve ser, depois que você seguiu todas as etapas neste tutorial. Também há comentários em todo o código que fornecem contexto adicional.
require 'sinatra/base' # Use the Sinatra web framework require 'octokit' # Use the Octokit Ruby library to interact with GitHub's REST API require 'dotenv/load' # Manages environment variables require 'json' # Allows your app to manipulate JSON data require 'openssl' # Verifies the webhook signature require 'jwt' # Authenticates a GitHub App require 'time' # Gets ISO 8601 representation of a Time object require 'logger' # Logs debug statements # This code is a Sinatra app, for two reasons: # 1. Because the app will require a landing page for installation. # 2. To easily handle webhook events. class GHAapp < Sinatra::Application # Sets the port that's used when starting the web server. set :port, 3000 set :bind, '0.0.0.0' # Expects the private key in PEM format. Converts the newlines. PRIVATE_KEY = OpenSSL::PKey::RSA.new(ENV['GITHUB_PRIVATE_KEY'].gsub('\n', "\n")) # Your registered app must have a webhook secret. # The secret is used to verify that webhooks are sent by GitHub. WEBHOOK_SECRET = ENV['GITHUB_WEBHOOK_SECRET'] # The GitHub App's identifier (type integer). APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] # Turn on Sinatra's verbose logging during development configure :development do set :logging, Logger::DEBUG end # Executed before each request to the `/event_handler` route before '/event_handler' do get_payload_request(request) verify_webhook_signature # 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 authenticate_app # Authenticate the app installation in order to run API operations authenticate_installation(@payload) end post '/event_handler' do # 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 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 when 'requested_action' take_requested_action end end end 200 # success status end helpers do # 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 # 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' ) 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) # Run RuboCop on all files in the repository @report = `rubocop '#{repository}' --format json` logger.debug @report `rm -rf #{repository}` @output = JSON.parse @report 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 # 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']}" # 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' ) end # 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 # 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 # Saves the raw payload and converts the payload to JSON format def get_payload_request(request) # request.body is an IO or StringIO object # Rewind in case someone already read it request.body.rewind # The raw text of the body is required for webhook signature verification @payload_raw = request.body.read begin @payload = JSON.parse @payload_raw rescue => e fail 'Invalid JSON (#{e}): #{@payload_raw}' end end # Instantiate an Octokit client authenticated as a GitHub App. # GitHub App authentication requires that you construct a # JWT (https://jwt.io/introduction/) signed with the app's private key, # so GitHub can be sure that it came from the app and not altered by # a malicious third party. def authenticate_app payload = { # The time that this JWT was issued, _i.e._ now. iat: Time.now.to_i, # JWT expiration time (10 minute maximum) exp: Time.now.to_i + (10 * 60), # Your GitHub App's identifier number iss: APP_IDENTIFIER } # Cryptographically sign the JWT. jwt = JWT.encode(payload, PRIVATE_KEY, 'RS256') # Create the Octokit client, using the JWT as the auth token. @app_client ||= Octokit::Client.new(bearer_token: jwt) end # Instantiate an Octokit client, authenticated as an installation of a # GitHub App, to run API operations. def authenticate_installation(payload) @installation_id = payload['installation']['id'] @installation_token = @app_client.create_app_installation_access_token(@installation_id)[:token] @installation_client = Octokit::Client.new(bearer_token: @installation_token) end # Check X-Hub-Signature to confirm that this webhook was generated by # GitHub, and not a malicious third party. # # GitHub uses the WEBHOOK_SECRET, registered to the GitHub App, to # create the hash signature sent in the `X-HUB-Signature` header of each # webhook. This code computes the expected hash signature and compares it to # the signature sent in the `X-HUB-Signature` header. If they don't match, # this request is an attack, and you should reject it. GitHub uses the HMAC # hexdigest to compute the signature. The `X-HUB-Signature` looks something # like this: 'sha1=123456'. def verify_webhook_signature their_signature_header = request.env['HTTP_X_HUB_SIGNATURE'] || 'sha1=' method, their_digest = their_signature_header.split('=') our_digest = OpenSSL::HMAC.hexdigest(method, WEBHOOK_SECRET, @payload_raw) halt 401 unless their_digest == our_digest # The X-GITHUB-EVENT header provides the name of the event. # The action value indicates the which action triggered the event. logger.debug "---- received event #{request.env['HTTP_X_GITHUB_EVENT']}" logger.debug "---- action #{@payload['action']}" unless @payload['action'].nil? end end # Finally some logic to let us run this server directly from the command line, # or with Rack. Don't worry too much about this code. But, for the curious: # $0 is the executed file # __FILE__ is the current file # If they are the same—that is, we are running this file directly, call the # Sinatra run method run! if __FILE__ == $0 end
require 'sinatra/base' # Use the Sinatra web framework
require 'octokit' # Use the Octokit Ruby library to interact with GitHub's REST API
require 'dotenv/load' # Manages environment variables
require 'json' # Allows your app to manipulate JSON data
require 'openssl' # Verifies the webhook signature
require 'jwt' # Authenticates a GitHub App
require 'time' # Gets ISO 8601 representation of a Time object
require 'logger' # Logs debug statements
# This code is a Sinatra app, for two reasons:
# 1. Because the app will require a landing page for installation.
# 2. To easily handle webhook events.
class GHAapp < Sinatra::Application
# Sets the port that's used when starting the web server.
set :port, 3000
set :bind, '0.0.0.0'
# Expects the private key in PEM format. Converts the newlines.
PRIVATE_KEY = OpenSSL::PKey::RSA.new(ENV['GITHUB_PRIVATE_KEY'].gsub('\n', "\n"))
# Your registered app must have a webhook secret.
# The secret is used to verify that webhooks are sent by GitHub.
WEBHOOK_SECRET = ENV['GITHUB_WEBHOOK_SECRET']
# The GitHub App's identifier (type integer).
APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER']
# Turn on Sinatra's verbose logging during development
configure :development do
set :logging, Logger::DEBUG
end
# Executed before each request to the `/event_handler` route
before '/event_handler' do
get_payload_request(request)
verify_webhook_signature
# 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
authenticate_app
# Authenticate the app installation in order to run API operations
authenticate_installation(@payload)
end
post '/event_handler' do
# 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
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
when 'requested_action'
take_requested_action
end
end
end
200 # success status
end
helpers do
# 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
# 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'
)
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)
# Run RuboCop on all files in the repository
@report = `rubocop '#{repository}' --format json`
logger.debug @report
`rm -rf #{repository}`
@output = JSON.parse @report
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
# 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']}"
# 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'
)
end
# 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
# 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
# Saves the raw payload and converts the payload to JSON format
def get_payload_request(request)
# request.body is an IO or StringIO object
# Rewind in case someone already read it
request.body.rewind
# The raw text of the body is required for webhook signature verification
@payload_raw = request.body.read
begin
@payload = JSON.parse @payload_raw
rescue => e
fail 'Invalid JSON (#{e}): #{@payload_raw}'
end
end
# Instantiate an Octokit client authenticated as a GitHub App.
# GitHub App authentication requires that you construct a
# JWT (https://jwt.io/introduction/) signed with the app's private key,
# so GitHub can be sure that it came from the app and not altered by
# a malicious third party.
def authenticate_app
payload = {
# The time that this JWT was issued, _i.e._ now.
iat: Time.now.to_i,
# JWT expiration time (10 minute maximum)
exp: Time.now.to_i + (10 * 60),
# Your GitHub App's identifier number
iss: APP_IDENTIFIER
}
# Cryptographically sign the JWT.
jwt = JWT.encode(payload, PRIVATE_KEY, 'RS256')
# Create the Octokit client, using the JWT as the auth token.
@app_client ||= Octokit::Client.new(bearer_token: jwt)
end
# Instantiate an Octokit client, authenticated as an installation of a
# GitHub App, to run API operations.
def authenticate_installation(payload)
@installation_id = payload['installation']['id']
@installation_token = @app_client.create_app_installation_access_token(@installation_id)[:token]
@installation_client = Octokit::Client.new(bearer_token: @installation_token)
end
# Check X-Hub-Signature to confirm that this webhook was generated by
# GitHub, and not a malicious third party.
#
# GitHub uses the WEBHOOK_SECRET, registered to the GitHub App, to
# create the hash signature sent in the `X-HUB-Signature` header of each
# webhook. This code computes the expected hash signature and compares it to
# the signature sent in the `X-HUB-Signature` header. If they don't match,
# this request is an attack, and you should reject it. GitHub uses the HMAC
# hexdigest to compute the signature. The `X-HUB-Signature` looks something
# like this: 'sha1=123456'.
def verify_webhook_signature
their_signature_header = request.env['HTTP_X_HUB_SIGNATURE'] || 'sha1='
method, their_digest = their_signature_header.split('=')
our_digest = OpenSSL::HMAC.hexdigest(method, WEBHOOK_SECRET, @payload_raw)
halt 401 unless their_digest == our_digest
# The X-GITHUB-EVENT header provides the name of the event.
# The action value indicates the which action triggered the event.
logger.debug "---- received event #{request.env['HTTP_X_GITHUB_EVENT']}"
logger.debug "---- action #{@payload['action']}" unless @payload['action'].nil?
end
end
# Finally some logic to let us run this server directly from the command line,
# or with Rack. Don't worry too much about this code. But, for the curious:
# $0 is the executed file
# __FILE__ is the current file
# If they are the same—that is, we are running this file directly, call the
# Sinatra run method
run! if __FILE__ == $0
end
Próximas etapas
Agora você deve ter um aplicativo que recebe eventos de API, cria execuções de verificação, usa o RuboCop para localizar erros do Ruby, cria anotações em uma solicitação de pull e corrige automaticamente erros de linter. Em seguida, convém que você expanda o código do aplicativo, implante-o e torne-o público.
Se você tiver alguma dúvida, inicie uma discussão do GitHub Community na categoria API e Webhooks.
Modificar o código do aplicativo
Este tutorial demonstrou como criar um botão "Corrigir isso" que sempre é exibido em solicitações de pull no repositório. Tente atualizar o código para exibir o botão "Corrija isso" somente quando o RuboCop encontrar erros.
Se preferir que o RuboCop não faça commit dos arquivos diretamente no branch principal, atualize o código para em vez disso criar uma solicitação de pull com um novo branch com base no branch principal.
Implante seu aplicativo
Este tutorial demonstrou como desenvolver o aplicativo localmente. Quando estiver pronto para implantar o aplicativo, você precisará fazer alterações para atendê-lo e manter a credencial do aplicativo segura. As etapas que você executar dependem do servidor que você usa, mas as seções a seguir oferecem diretrizes gerais.
Hospedar o aplicativo em um servidor
Este tutorial usou seu computador ou o codespace como servidor. Depois que o aplicativo estiver pronto para uso em produção, você deverá implantar seu aplicativo em um servidor dedicado. Por exemplo, você pode usar o Serviço de Aplicativo do Azure.
Atualizar a URL do webhook
Depois de ter um servidor configurado para receber o tráfego de webhook de GitHub, atualize a URL do webhook nas configurações do aplicativo. Você não deve usar o Smee.io para encaminhar os webhooks em produção.
Atualizar a configuração :port
Ao implantar o aplicativo, você desejará alterar a porta onde o servidor está escutando. O código já informa ao servidor para escutar todos os adaptadores de rede disponíveis definindo :bind
como 0.0.0.0
.
Por exemplo, você pode definir uma variável PORT
no arquivo .env
no seu servidor para indicar a porta em que o servidor deve escutar. Em seguida, você pode atualizar o local em que o código define :port
para que o servidor ouça na porta de implantação:
set :port, ENV['PORT']
set :port, ENV['PORT']
Proteger as credenciais do aplicativo
Você nunca deve divulgar a chave privada ou o segredo do webhook do aplicativo. Este tutorial armazenou as credenciais do aplicativo em um arquivo .env
gitignored. Ao implantar seu aplicativo, você deve escolher uma forma segura de armazenar as credenciais e atualizar o código para obter o valor de acordo. Por exemplo, você pode armazenar as credenciais com um serviço de gerenciamento de segredos, como o Azure Key Vault. Quando o aplicativo é executado, ele pode recuperar as credenciais e armazená-las em variáveis de ambiente no servidor em que seu aplicativo está implantado.
Para obter mais informações, confira "Práticas recomendadas para criar um aplicativo do GitHub".
Compartilhar o aplicativo
Se você quiser compartilhar seu aplicativo com outros usuários e organizações, torne o aplicativo público. Para obter mais informações, confira "Tornar um aplicativo do GitHub público ou privado".
Seguir as práticas recomendadas
Você deve ter como objetivo seguir as melhores práticas com seu GitHub App. Para obter mais informações, confira "Práticas recomendadas para criar um aplicativo do GitHub".