Skip to main content

Проверка CI в здании с помощью приложения GitHub

Создайте сервер непрерывной интеграции для выполнения тестов с помощью GitHub App и проверок.

Введение

В этом руководстве показано, как создать сервер непрерывной интеграции (CI), который выполняет тесты на новом коде, отправленном в репозиторий. В этом руководстве показано, как создать и настроить GitHub App для работы в качестве сервера, который получает и реагирует на check_run``check_suite события веб-перехватчика, используя REST API GitHub.

В этом руководстве вы будете использовать компьютер или пространство кода в качестве сервера при разработке приложения. После того как приложение будет готово к использованию в рабочей среде, необходимо развернуть приложение на выделенном сервере.

В этом руководстве используется Ruby, но вы можете использовать любой язык программирования, который можно запустить на сервере.

Это руководство разбито на две части:

  • В одной части вы узнаете, как настроить платформу для сервера CI с помощью REST API GitHub, создать новые проверки для тестов CI, когда репозиторий получает вновь отправленные фиксации, и повторно запустить проверку, когда пользователь запрашивает это действие на GitHub.
  • В первой части вы добавите функциональные возможности в тест CI, добавив linter test на сервер CI. Вы также создадите заметки, отображаемые на вкладке "Проверки и изменения файлов " запроса на вытягивание, и автоматически исправьте рекомендации linter, открыв кнопку "Исправить это" на вкладке "Проверки " запроса на вытягивание.

Сведения о непрерывной интеграции (CI)

CI — это практика программного обеспечения, которая требует частой фиксации кода в общем репозитории. Фиксация кода быстрее и чаще вызывает ошибки и уменьшает объем кода, отладку которого разработчику необходимо выполнить при поиске источника ошибки. Частые обновления кода также упрощают слияние изменений, полученные от разных участников группы разработчиков программного обеспечения. Это отлично подходит для разработчиков, которые могут тратить больше времени на написание кода и меньше на отладку ошибок или разрешение конфликтов слияния.

На сервере CI размещается код, который выполняет тесты CI, такие как анализаторы кода (которые проверяют форматирование стиля), проверки безопасности, объем протестированного кода и другие проверки на соответствие новым фиксациям кода в репозитории. Серверы CI могут даже создавать и развертывать код на промежуточных или рабочих серверах. Примеры типов тестов CI, которые можно создать с помощью GitHub App, см . в приложениях непрерывной интеграции, доступных в GitHub Marketplace.

Сведения о проверках

REST API %% данных variables.product.prodname_dotcom %}позволяет настроить тесты CI (проверки), которые автоматически выполняются для каждой фиксации кода в репозитории. API сообщает подробные сведения о каждой проверке на вкладке "Проверки** запроса **на вытягивание" на GitHub. Вы можете использовать проверки в репозитории, чтобы определить, когда фиксация кода вызывает ошибки.

Проверки включают запуски, наборы проверок и состояния фиксации.

  • Выполнение проверки — это отдельный тест CI, который выполняется в фиксации.
  • Набор __ проверок — это группа запусков проверки.
  • Состояние фиксации помечает состояние фиксации, например error, failure``pendingили success, и отображается в запросе на вытягивание GitHub. Как наборы проверок, так и запуски проверки содержат состояния фиксации.

GitHub автоматически создает check_suite события для новых фиксаций кода в репозитории с помощью потока по умолчанию, хотя можно изменить параметры по умолчанию. Дополнительные сведения см. в разделе Конечные точки REST API для контрольных наборов. Этот поток по умолчанию выполняется следующим образом:

  1. Когда пользователь отправляет код в репозиторий, GitHub автоматически отправляет check_suite событие с действием requested всех GitHub Apps, установленных в репозитории с разрешением checks:write . Это событие позволяет приложениям знать, что код был отправлен в репозиторий, и что GitHub автоматически создал новый набор проверок.
  2. Когда приложение получает это событие, оно может добавить выполнения проверки в этот набор.
  3. Выполнения проверки могут включать заметки, отображаемые в определенных строках кода. Заметки отображаются на вкладке "Проверки ". При создании заметки для файла, который является частью запроса на вытягивание, заметки также отображаются на вкладке "Измененные файлы". Дополнительные сведения см. в разделе annotations "Объект" в Конечные точки REST API для проверки выполнения.

Дополнительные сведения о проверках см. в разделе [AUTOTITLE и Конечные точки REST API для проверок](/rest/guides/using-the-rest-api-to-interact-with-checks).

Необходимые компоненты

В этом руководстве предполагается, что у вас есть базовое представление о языке программирования Ruby.

Прежде чем приступить к работе, вы можете ознакомиться со следующими понятиями:

Проверки также доступны для использования с API GraphQL, но в этом руководстве основное внимание уделяется REST API. Дополнительные сведения о объектах GraphQL см. в разделе Check Suite и Check Run в документации GraphQL.

Настройка

В следующих разделах вы узнаете, как настроить следующие компоненты:

  • Репозиторий для хранения кода для приложения.
  • Способ локального получения веб-перехватчиков.
  • GitHub App, который подписан на события веб-перехватчика "Check suite" и "Check Run", имеет разрешение на запись для проверок и использует URL-адрес веб-перехватчика, который можно получить локально.

Создайте репозиторий для хранения кода для GitHub App

  1. Создайте репозиторий для хранения кода для приложения. Дополнительные сведения см. в разделе Создание репозитория.

  2. Клонируйте репозиторий на предыдущем шаге. Дополнительные сведения см. в разделе Клонирование репозитория. Вы можете использовать локальный клон или GitHub Codespaces.

  3. В терминале перейдите в каталог, в котором хранится клон.

  4. Создайте файл Ruby с именем server.rb. Этот файл будет содержать весь код для приложения. Позже вы добавите содержимое в этот файл.

  5. Если каталог еще не содержит .gitignore файл, добавьте .gitignore файл. Позже вы добавите содержимое в этот файл. Дополнительные сведения о файлах см. в .gitignore разделе Пропуск файлов.

  6. Создайте файл с именем Gemfile. Этот файл описывает зависимости драгоценных камней, необходимые коду Ruby. Добавьте в приложение Gemfileследующее содержимое:

    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'
    
  7. Создайте файл с именем config.ru. Этот файл настроит сервер Sinatra для запуска. Добавьте в файл следующее содержимое config.ru :

    Ruby
    require './server'
    run GHAapp
    

Получение URL-адреса прокси-сервера веб-перехватчика

Для локальной разработки приложения можно использовать URL-адрес прокси-сервера веб-перехватчика для пересылки событий веб-перехватчика из GitHub на компьютер или пространство кода. В этом руководстве используется Smee.io для предоставления URL-адреса прокси-сервера веб-перехватчика и перенаправления событий.

  1. В терминале выполните следующую команду, чтобы установить клиент Smee:

    Shell
    npm install --global smee-client
    
  2. В браузере перейдите по адресу https://smee.io/.

  3. Нажмите кнопку " Пуск нового канала".

  4. Скопируйте полный URL-адрес в разделе "URL-адрес прокси-сервера веб-перехватчика".

  5. В терминале выполните следующую команду, чтобы запустить клиент Smee. Замените YOUR_DOMAIN URL-адрес прокси-сервера веб-перехватчика, скопированный на предыдущем шаге.

    Shell
    smee --url YOUR_DOMAIN --path /event_handler --port 3000
    

    Вы должны увидеть следующий результат:

    Forwarding https://smee.io/YOUR_DOMAIN to http://127.0.0.1:3000/event_handler
    Connected https://smee.io/YOUR_DOMAIN
    

Команда smee --url https://smee.io/YOUR_DOMAIN указывает Smee перенаправить все события веб-перехватчика (полученные каналом Smee) клиенту Smee, работающему на компьютере. Параметр --path /event_handler перенаправит события в /event_handler маршрут. Параметр --port 3000 задает порт 3000, который является портом, который будет сообщать серверу прослушивать при добавлении дополнительного кода далее в руководстве. Используя Smee, компьютер не должен быть открыт в общедоступном Интернете для получения веб-перехватчиков из GitHub. Вы также можете открыть этот веб-адрес Smee в браузере, чтобы проверить полезные данные веб-перехватчика по мере их поступления.

Мы рекомендуем оставить это окно терминала открытым и сохранить подключение Smee во время выполнения остальных действий, описанных в этом руководстве. Хотя вы можете отключить и повторно подключить клиент Smee, не теряя уникальный домен, вы можете легко оставить его подключенным и выполнить другие задачи командной строки в другом окне терминала.

Регистрация GitHub App

В этом руководстве необходимо зарегистрировать GitHub App, что:

  • Имеет активные веб-перехватчики
  • Использует URL-адрес веб-перехватчика, который можно получить локально
  • Имеет разрешение репозитория "Проверки"
  • Подписывается на события веб-перехватчика "Контрольный набор" и "Проверить запуск"

Следующие действия помогут вам настроить GitHub App с этими параметрами. Дополнительные сведения о параметрах GitHub App см. в разделе Регистрация приложения GitHub.

  1. В правом верхнем углу любой страницы на GitHubщелкните рисунок профиля.

  2. Перейдите к настройкам учетной записи.

    • Для приложения, принадлежащих личная учетная запись, нажмите кнопку "Параметры".
    • Для приложения, принадлежащих организации:
      1. Щелкните Your organizations (Ваши организации).
      2. Справа от организации нажмите кнопку "Параметры".
    • Для приложения, принадлежающего предприятия:
      1. Если вы используете Enterprise Managed Users, щелкните "Ваше предприятие ", чтобы перейти непосредственно к параметрам учетной записи предприятия.
      2. Если вы используете личная учетная запись, щелкните "Ваши предприятия", а затем справа от предприятия нажмите кнопку "Параметры".
  3. Перейдите к параметрам GitHub App .

    • Для приложения, принадлежащих личная учетная запись или организации:
      1. На левой боковой панели щелкните Параметры разработчика, а затем выберите GitHub Apps.
    • Для приложения, принадлежаемого предприятием:
      1. На левой боковой панели в разделе "Параметры", щелкните **GitHub Apps.
  4. Щелкните "Создать" GitHub App.

  5. В поле "GitHub App name" введите имя приложения. Например, USERNAME-ci-test-app где USERNAME находится имя пользователя GitHub.

  6. В разделе "URL-адрес домашней страницы" введите URL-адрес приложения. Например, можно использовать URL-адрес репозитория, созданного для хранения кода приложения.

  7. Пропустите разделы "Идентификация и авторизация пользователей" и "После установки" для этого руководства.

  8. Убедитесь, что параметр "Активный" выбран в разделе "Веб-перехватчики".

  9. В разделе "URL-адрес веб-перехватчика" введите URL-адрес прокси-сервера веб-перехватчика ранее. Дополнительные сведения см. в разделе "Получение URL-адреса прокси-сервера веб-перехватчика".

  10. В разделе "Секрет веб-перехватчика" введите случайную строку. Этот секрет используется для проверки отправки веб-перехватчиков GitHub. Сохраните эту строку; вы будете использовать его позже.

  11. В разделе "Разрешения репозитория" рядом с элементом "Проверки" выберите "Чтение и запись".

  12. В разделе "Подписка на события" выберите "Контрольный набор " и "Проверить выполнение".

  13. В разделе "Где можно установить данные GitHub App?", выберите только в этой учетной записи. Это можно изменить позже, если вы хотите опубликовать приложение.

    Примечание.

    Если данные GitHub App зарегистрированы в организации, этот шаг не применяется.

  14. Нажмите кнопку "Создать GitHub App.

Хранение сведений и учетных данных приложения

В этом руководстве показано, как хранить учетные данные приложения и определять их как переменные среды в .env файле. При развертывании приложения необходимо изменить способ хранения учетных данных. Дополнительные сведения см. в разделе "Развертывание приложения".

Перед выполнением этих действий убедитесь, что вы находитесь на защищенном компьютере, так как вы будете хранить учетные данные локально.

  1. В терминале перейдите в каталог, в котором хранится клон.

  2. Создайте файл, вызывающийся .env на верхнем уровне этого каталога.

  3. Добавьте .env в .gitignore файл. Это позволит предотвратить случайное фиксацию учетных данных приложения.

  4. Добавьте в файл следующее содержимое .env . Вы обновите значения на следующем шаге.

    Shell
    GITHUB_APP_IDENTIFIER="YOUR_APP_ID"
    GITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET"
    GITHUB_PRIVATE_KEY="YOUR_PRIVATE_KEY"
    
  5. Перейдите на страницу параметров приложения:

    1. В правом верхнем углу любой страницы на GitHubщелкните рисунок профиля.

    2. Перейдите к настройкам учетной записи.

      • Для приложения, принадлежащих личная учетная запись, нажмите кнопку "Параметры".
      • Для приложения, принадлежащих организации:
        1. Щелкните Your organizations (Ваши организации).
        2. Справа от организации нажмите кнопку "Параметры".
      • Для приложения, принадлежающего предприятия:
        1. Если вы используете Enterprise Managed Users, щелкните "Ваше предприятие ", чтобы перейти непосредственно к параметрам учетной записи предприятия.
        2. Если вы используете личная учетная запись, щелкните "Ваши предприятия", а затем справа от предприятия нажмите кнопку "Параметры".
    3. На левой боковой панели щелкните Параметры разработчика.

    4. На левой боковой панели щелкните GitHub Apps.

    5. Рядом с именем приложения нажмите кнопку "Изменить".

  6. На странице параметров приложения рядом с идентификатором приложения найдите идентификатор приложения.

  7.        `.env` В файле замените `YOUR_APP_ID` идентификатор приложения.
    
  8.        `.env` В файле замените `YOUR_WEBHOOK_SECRET` секрет веб-перехватчика для приложения. Если вы забыли секрет веб-перехватчика, в разделе "Секрет веб-перехватчика (необязательно)" нажмите кнопку **"Изменить секрет**". Введите новый секрет и нажмите кнопку **"Сохранить изменения**".
    
  9. На странице параметров приложения в разделе "Закрытые ключи" нажмите кнопку "Создать закрытый ключ". Вы увидите файл закрытого ключа .pem , скачанный на компьютер.

  10.        `.pem` Откройте файл с текстовым редактором или используйте следующую команду в командной строке, чтобы отобразить содержимое файла: `cat PATH/TO/YOUR/private-key.pem`
    
  11. Скопируйте и вставьте все содержимое файла в .env файл в качестве значения GITHUB_PRIVATE_KEYи добавьте двойные кавычки по всему значению.

    Ниже приведен пример ENV-файла:

    GITHUB_APP_IDENTIFIER=12345
    GITHUB_WEBHOOK_SECRET=your webhook secret
    GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
    ...
    HkVN9...
    ...
    -----END RSA PRIVATE KEY-----"
    

Добавьте код для GitHub App

В этом разделе показано, как добавить базовый код шаблона для GitHub App, а также объяснить, что делает код. Далее в руководстве вы узнаете, как изменить и добавить его в этот код, чтобы создать функциональные возможности приложения.

Добавьте в файл следующий код server.rb шаблона:

Ruby
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

В остальной части этого раздела объясняется, что делает код шаблона. При работе с этим разделом не нужно выполнять никаких действий. Если вы уже знакомы с кодом шаблона, вы можете перейти к запуску сервера.

Общие сведения о коде шаблона

Откройте файл server.rb в текстовом редакторе. В файле отображаются комментарии, которые предоставляют дополнительный контекст для кода шаблона. Рекомендуем внимательно прочитать эти комментарии и даже добавлять собственные для сопровождения нового кода, который вы пишете.

Под списком обязательных файлов отображается первый код, который вы увидите class GHApp < Sinatra::Application , — это объявление. Вы напишете весь код для данных GitHub App внутри этого класса. В следующих разделах подробно объясняется, что делает код внутри этого класса.

Настройка порта

Первое, что вы увидите внутри class GHApp < Sinatra::Application объявления.set :port 3000 Это задает порт, используемый при запуске веб-сервера, чтобы сопоставить порт, который вы перенаправили полезные данные веб-перехватчика в URL-адрес прокси-сервера веб-перехватчика.

  # 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']

Включение ведения журнала

Далее приведен блок кода, который включает ведение журнала во время разработки, что является средой по умолчанию в Sinatra. Этот код включает ведение журнала на DEBUG уровне, чтобы отобразить полезные выходные данные в терминале при разработке приложения.

# Turn on Sinatra's verbose logging during development
configure :development do
  set :logging, Logger::DEBUG
end

          `before` Определение фильтра

Sinatra использует before фильтры, позволяющие выполнять код перед обработчиком маршрутов. Блок before в шаблоне вызывает четыре вспомогательных метода: get_payload_request, , verify_webhook_signature, authenticate_appи authenticate_installation. Дополнительные сведения см. в разделе "Фильтры и вспомогательные службы " в документации по 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

Каждый из этих вспомогательных методов определяется позже в коде в блоке кода, который начинается с helpers do. Дополнительные сведения см. в разделе "Определение вспомогательных методов".

В разделе verify_webhook_signatureкода, который начинается с unless @payload , является мерой безопасности. Если имя репозитория предоставляет полезные данные веб-перехватчика, этот код проверяет, содержит ли имя репозитория только латинские алфавитные символы, дефисы и символы подчеркивания. Это помогает убедиться, что неправильный субъект не пытается выполнять произвольные команды или вводить имена ложных репозиторий. Позже в блоке кода, который начинается с helpers do, verify_webhook_signature вспомогательный метод также проверяет входящие полезные данные веб-перехватчика в качестве дополнительной меры безопасности.

Определение обработчика маршрутов

Пустой маршрут включается в код шаблона. Этот код обрабатывает все запросы POST к маршруту /event_handler. Далее вы добавите в нее дополнительный код.

post '/event_handler' do

end

Определение вспомогательных методов

Четыре вспомогательных метода вызываются в before блоке кода шаблона. Блок helpers do кода определяет каждый из этих вспомогательных методов.

Обработка полезных данных веб-перехватчика

Первый вспомогательный метод get_payload_request захватывает полезные данные веб-перехватчика и преобразует его в формат JSON, что упрощает доступ к данным полезных данных.

Проверка сигнатуры веб-перехватчика

Второй вспомогательный метод verify_webhook_signature выполняет проверку подписи веб-перехватчика, чтобы убедиться, что GitHub создало событие. Дополнительные сведения о коде в вспомогательном методе verify_webhook_signature см. в разделе Проверка доставки веб-перехватчика. Если веб-перехватчики защищены, этот метод записывает все входящие полезные данные в терминал. Код средства ведения журнала полезен при проверке работы веб-сервера.

Проверка подлинности от имени GitHub App

Третий вспомогательный метод authenticate_app позволяет GitHub App проходить проверку подлинности, поэтому он может запросить маркер установки.

Для выполнения вызовов API вы будете использовать библиотеку Octokit. Для выполнения каких-либо интересных действий с этой библиотекой потребуется выполнить проверку подлинности GitHub App. Дополнительные сведения о библиотеке Octokit см. в документации по Octokit.

GitHub Apps имеют три метода проверки подлинности:

  • Проверка подлинности в виде GitHub App с помощью веб-маркера JSON (JWT).
  • Проверка подлинности в качестве конкретной установки GitHub App с помощью маркера доступа к установке.
  • Проверка подлинности от имени пользователя. В этом руководстве этот метод проверки подлинности не будет использоваться.

Вы узнаете о проверке подлинности в качестве установки в следующем разделе. Проверка подлинности в качестве установки.

Проверка подлинности от имени GitHub App позволяет выполнить несколько действий:

  • Вы можете получить обобщенные сведения об управлении GitHub App.
  • Вы можете запросить маркеры доступа для установки приложения.

Например, вы будете проходить проверку подлинности как GitHub App для получения списка учетных записей (организации и личных), установленных приложением. Но этот способ проверки подлинности не широкого спектра действий с API. Чтобы получить доступ к данным репозитория и выполнять операции от имени установки, необходимо пройти проверку подлинности в качестве установки. Для этого сначала необходимо пройти проверку подлинности в виде GitHub App для запроса маркера доступа к установке. Дополнительные сведения см. в разделе Об аутентификации с помощью приложения GitHub.

Прежде чем использовать библиотеку Octokit.rb для вызова API, необходимо инициализировать клиент Octokit, прошедший проверку подлинности как GitHub App, используя вспомогательный 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

Приведенный выше код создает JSON Web Token (JWT) и использует его (вместе с закрытым ключом приложения) для инициализации клиента Octokit. GitHub проверяет аутентификацию запроса, проверяя токен с помощью сохранённого публичного ключа приложения. Дополнительные сведения о том, как работает этот код, см. в разделе Генерация веб-токена JSON (JWT) для приложения GitHub.

Проверка подлинности от имени установки

Четвертый и последний вспомогательный метод authenticate_installation, инициализирует клиент Octokit, прошедший проверку подлинности в качестве установки, которую можно использовать для выполнения вызовов API с проверкой подлинности.

Под установкой понимается учетная запись любого пользователя или организации, которые установили приложение. Даже если кто-то предоставляет приложению доступ к нескольким репозиториям в этой учетной записи, он считается только одной установкой, так как она находится в одной учетной записи.

# 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

Метод Octokit create_app_installation_access_token создает маркер установки. Дополнительные сведения см . в create_installation_access_token документации по Octokit.

Этот метод принимает два аргумента:

  • Установка (целое число): идентификатор установки GitHub App
  • Параметры (хэш, по умолчанию {}): настраиваемый набор параметров.

Каждый раз, когда GitHub App получает веб-перехватчик, он включает installation объект с idобъектом. Используя клиент, прошедший проверку подлинности в качестве GitHub App, этот идентификатор передается create_app_installation_access_token методу для создания маркера доступа для каждой установки. Так как вы не передаете параметры в метод, то параметры по умолчанию имеют пустой хэш. Ответ для create_app_installation_access_token следующих двух полей: token и expired_at. Код шаблона выбирает маркер в ответе и инициализирует клиент установки.

При использовании этого метода каждый раз, когда приложение получает новые полезные данные веб-перехватчика, он создает клиент для установки, которая активировала событие. Этот процесс проверки подлинности позволяет GitHub App работать для всех установок в любой учетной записи.

Запуск сервера

Приложение пока ничего не делает, но на этом этапе его можно запустить на сервере.

  1. В терминале убедитесь, что Smee все еще работает. Дополнительные сведения см. в разделе "Получение URL-адреса прокси-сервера веб-перехватчика".

  2. Откройте новую вкладку в терминале и cd в каталог, где вы клонировали репозиторий, созданный ранее в руководстве. Для получения дополнительной информации см. Создать репозиторий для хранения кода для вашего GitHub App. Код Ruby в этом репозитории запустит веб-сервер Sinatra.

  3. Установите зависимости, выполнив следующие две команды, одну после другой:

    Shell
    gem install bundler
    
    Shell
    bundle install
    
  4. После установки зависимостей запустите сервер, выполнив следующую команду:

    Shell
    bundle exec ruby server.rb
    

    Вы должны получить примерно следующий ответ:

    > == 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
    

    Если появится сообщение об ошибке, убедитесь, что вы создали файл .env в каталоге с server.rb.

  5. Чтобы протестировать сервер, перейдите в браузер http://localhost:3000.

    Если вы видите страницу ошибки, которая говорит: "Sinatra не знает этого дитти", приложение работает должным образом. Несмотря на то, что это страница ошибки, такая страница Sinatra означает, что ваше приложение подключено к серверу должным образом. Вы видите это сообщение, так как вы ничего не предоставили приложению для отображения.

Проверка того, что сервер прослушивает ваше приложение

Вы можете проверить, ожидает ли сервер передачи данных приложения, активируя событие для получения. Для этого необходимо установить приложение в тестовом репозитории, которое отправит installation событие в приложение. Если приложение получает его, вы увидите выходные данные на вкладке терминала, в которой вы работаете server.rb.

  1. Создайте новый репозиторий для тестирования кода учебника. Дополнительные сведения см. в разделе Создание репозитория.

  2. Установите только что созданный репозиторий GitHub App. Дополнительные сведения см. в разделе Установка собственного приложения GitHub. В процессе установки выберите только репозитории выбора и выберите репозиторий, созданный на предыдущем шаге.

  3. После нажатия кнопки "Установить" просмотрите выходные данные на вкладке терминала, в которой выполняется работа server.rb. Отобразятся примерно следующие сведения:

    > 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
    

    Если вы видите выходные данные, это означает, что ваше приложение получило уведомление о том, что оно было установлено в вашей учетной записи GitHub . Приложение выполняется на сервере, как ожидалось.

    Если вы не видите эти выходные данные, убедитесь, что Smee работает правильно на другой вкладке терминала. Если вам нужно перезапустить Smee, обратите внимание, что вам также потребуется удалить и переустановить приложение, чтобы отправить installation событие в приложение еще раз и просмотреть выходные данные в терминале.

Если вы хотите узнать, откуда поступает приведенный выше результат терминала, он написан в коде шаблона приложения, который server.rb вы добавили в add code for your GitHub App.

Часть 1. Создание API проверок

В этой части вы добавите код, необходимый для получения check_suite событий веб-перехватчика, и создадите и обновите запуски проверки. Вы также узнаете, как создавать проверки при повторном запросе проверки на GitHub. В конце этого раздела вы сможете просмотреть выполнение проверки, созданное в запросе на вытягивание GitHub.

Выполнение проверки не будет выполнять никаких проверок кода в этом разделе. Вы добавите эту функцию в части 2. Создание теста CI.

У вас уже должен быть настроен канал Smee, который перенаправит полезные данные веб-перехватчика на локальный сервер. Сервер должен работать и подключаться к данным GitHub App, зарегистрированным и установленным в тестовом репозитории.

Ниже указанны шаги, которые вы выполните в части 1.

  1.        [Добавление средств обработки событий](#step-11-add-event-handling)
    
  2.        [Создание выполнения проверки](#step-12-create-a-check-run)
    
  3.        [Обновление выполнения проверки](#step-13-update-a-check-run)
    

Шаг 1.1. Добавление средств обработки событий

Так как приложение подписано на набор проверки и проверьте события выполнения, оно получит**** иcheck_suite веб-перехватчики. GitHub отправляет полезные данные веб-перехватчика в виде POST запросов. Так как вы перенаправили полезные данные веб-перехватчика Smee в http://localhost:3000/event_handler, сервер получит полезные данные запроса POST на маршруте post '/event_handler'.

          `server.rb` Откройте файл, созданный в [разделе "Добавление кода" для GitHub App](#add-code-for-your-github-app), и найдите следующий код. Пустой `post '/event_handler'` маршрут уже включен в код шаблона. Пустой маршрут выглядит следующим образом:
  post '/event_handler' do

    # ADD EVENT HANDLING HERE #

    200 # success status
  end

В блоке кода, который начинается с post '/event_handler' doтого, где он говорит # ADD EVENT HANDLING HERE #, добавьте следующий код. Этот маршрут будет обрабатывать check_suite событие.

Ruby
    # 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

Каждое событие, которое отправляет GitHub включает заголовок HTTP_X_GITHUB_EVENTзапроса, который указывает тип события в запросе POST . Сейчас вас интересуют только события типа check_suite, которые возникают при создании нового набора проверок. Каждое событие имеет дополнительное поле action, указывающее тип действия, которое активировало события. Для check_suite поле action может находится в состоянии requested, rerequested или completed.

Действие requested запрашивает выполнение проверки при каждом отправлении кода в репозиторий, а действие rerequested запрашивает повторное выполнение проверки кода, который уже есть в репозитории. Так как действия requested и rerequested требуют создания выполнения проверки, вы вызовете вспомогательное приложение под названием create_check_run. Давайте напишем этот метод сейчас.

Шаг 1.2. Создание выполнения проверки

Добавьте этот новый метод в качестве вспомогательного приложения Sinatra, если необходимо, чтобы другие маршруты также использовали его.

В блоке кода, который начинается с helpers doтого места, где он говорит # ADD CREATE_CHECK_RUN HELPER METHOD HERE #, добавьте следующий код:

Ruby
    # 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

Этот код вызывает конечную точку POST /repos/{owner}/{repo}/check-runs с помощью метода Octokit create_check_run. Дополнительные сведения о конечной точке см. в разделе Конечные точки REST API для проверки выполнения.

Чтобы создать выполнение проверки, требуются только два входных параметра: name и head_sha. В этом коде мы назовем выполнение проверки "Octo RuboCop", так как мы будем использовать RuboCop для реализации теста CI далее в руководстве. Но вы можете выбрать любое имя, нужное для выполнения проверки. Дополнительные сведения о RuboCop см. в документации по RuboCop.

Теперь вы предоставляете только необходимые параметры, чтобы получить базовые функции, но вы обновите выполнение проверки позже во время сбора дополнительных сведений о нем. По умолчанию GitHub задает значение status``queued.

GitHub создает запуск проверки для определенной фиксации SHA, поэтому head_sha является обязательным параметром. SHA фиксации можно найти в полезных данных веб-перехватчика. Несмотря на то, что вы создаете только выполнение проверки для события check_suite прямо сейчас, будет не лишним знать, что head_sha включено в объекты check_suite и check_run в полезных данных события.

Приведенный выше код использует тернарный оператор, который работает как if/else оператор, чтобы проверить, содержит check_run ли полезные данные объект. Если это так, вы считываете head_sha из объекта check_run, в противном случае вы считываете его из объекта check_suite.

Тестирование кода

В следующих шагах показано, как проверить работу кода и успешно создать новый запуск проверки.

  1. Выполните следующую команду, чтобы перезапустить сервер из терминала. Если сервер уже запущен, сначала введите Ctrl-C в терминале, чтобы остановить сервер, а затем выполните следующую команду, чтобы снова запустить сервер.

    Shell
    ruby server.rb
    
  2. Создайте запрос на вытягивание в тестовом репозитории, созданном в тестовом режиме, который сервер прослушивает приложение. Это репозиторий, к которому вы предоставили доступ к приложению.

  3. В созданном запросе на вытягивание перейдите на вкладку "Проверки ". Вы увидите проверку выполнения с именем Octo RuboCop или любым именем, выбранным ранее для выполнения проверки.

Если вы видите другие приложения на вкладке "Проверки ", это означает, что в репозитории установлены другие приложения с доступом на чтение и запись для проверок и подписаны на события check suite и Check run . Это также может означать, что у вас есть рабочие процессы GitHub Actions в репозитории, которые активируются событием или pull_request событиемpull_request_target.

До сих пор вы сказали GitHub для создания проверки выполнения. Состояние выполнения проверки в запросе на вытягивание устанавливается в очередь с желтым значком. На следующем шаге вы подождите, пока GitHub создадите проверку и обновите его состояние.

Шаг 1.3. Обновление выполнения проверки

          `create_check_run` При запуске метода запрашивается GitHub для создания нового запуска проверки. Когда GitHub завершит создание проверки, вы получите `check_run` событие веб-перехватчика с действием `created` . Это событие является вашим сигналом для начала выполнения проверки.

Вы обновите обработчик событий, чтобы искать created действие. При обновлении обработчика событий можно добавить условие для действия rerequested. Когда кто-то повторно запускает один тест на GitHub путем нажатия кнопки "Повторно запустить", GitHub отправляет rerequested событие выполнения проверки в приложение. При выполнении rerequestedпроверки вы начнете процесс во всем процессе и создадите новый запуск проверки. Для этого вы добавите условие для check_run события в post '/event_handler' маршрут.

В блоке кода, который начинается с post '/event_handler' doтого места, где он говорит # ADD CHECK_RUN METHOD HERE #, добавьте следующий код:

Ruby
    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

GitHub отправляет все события для created проверки выполнения в каждое приложение, установленное в репозитории с необходимыми разрешениями проверки. Это означает, что ваше приложение будет получать выполнения проверки, созданные другими приложениями. created Выполнение проверки отличается от requested набора или rerequested набора проверок, который GitHub отправляет только приложениям, запрашиваемым для выполнения проверки. Указанный код ищет идентификатор приложения для выполнения проверки. При этом выполняется фильтрация всех выполнений проверки для других приложений в репозитории.

Затем необходимо написать метод initiate_check_run, в котором вы обновите состояние выполнения проверки и подготовитесь к запуску теста CI.

В этом разделе вы еще не будете запускать тест CI, но узнаете, как изменить состояние выполнения проверки с queued на pending, а затем с pending на completed, чтобы увидеть общий поток выполнения проверки. В части 2. Создание теста CI вы добавите код, который фактически выполняет тест CI.

Давайте создадим метод initiate_check_run и обновим состояние выполнения проверки.

В блоке кода, который начинается с helpers doтого места, где он говорит # ADD INITIATE_CHECK_RUN HELPER METHOD HERE #, добавьте следующий код:

Ruby
    # 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

Приведенный выше код вызывает конечную точку PATCH /repos/{owner}/{repo}/check-runs/{check_run_id} с помощью update_check_run метода Octokit и обновляет уже созданный запуск проверки. Дополнительные сведения о конечной точке см. в разделе Конечные точки REST API для проверки выполнения.

Вот что делает этот код. Во-первых, он обновляет состояние выполнения проверки in_progress и неявно задает значение started_at для текущего времени. В части 2 этого руководства вы добавите код, который запускает реальный тест CI в ***** RUN A CI TEST *****разделе . Пока оставьте этот раздел в качестве заполнителя, поэтому код, который следует за ним, будет просто имитировать успешность процесса CI и прохождение всех тестов. Наконец, код обновляет состояние выполнения проверки до completed.

При использовании REST API для предоставления состояния completed``conclusion выполнения проверки необходимы параметры и completed_at параметры. Суммирует conclusion результат выполнения проверки и может быть success, failure, , neutral, , cancelled, timed_out``skippedили action_required. Вы задали значение success, значение completed_at для текущего времени и значение completed для состояния.

Вы также можете предоставить дополнительные сведения о том, что делает ваша проверка, но вы перейдете к ним в следующем разделе.

Тестирование кода

В следующих шагах показано, как проверить, работает ли код, и что новая кнопка "Повторно запустить все", которую вы создали.

  1. Выполните следующую команду, чтобы перезапустить сервер из терминала. Если сервер уже запущен, сначала введите Ctrl-C в терминале, чтобы остановить сервер, а затем выполните следующую команду, чтобы снова запустить сервер.

    Shell
    ruby server.rb
    
  2. Создайте запрос на вытягивание в тестовом репозитории, созданном в тестовом режиме, который сервер прослушивает приложение. Это репозиторий, к которому вы предоставили доступ к приложению.

  3. В созданном запросе на вытягивание перейдите на вкладку "Проверки ". Вы увидите кнопку "Повторно запустить все".

  4. Нажмите кнопку "Повторно запустить все" в правом верхнем углу. Тест должен выполняться снова и заканчиваться success.

Part 2 (Развертывание виртуальных машин в облаке, часть 2). Создание теста CI

Теперь, когда вы создали интерфейс для получения событий API и создания проверок, можно создать запуск проверки, реализующий тест CI.

RuboCop — это анализатор кода Ruby и форматировщик. Он проверяет код Ruby, чтобы убедиться, что он соответствует руководству по стилю Ruby. Дополнительные сведения см. в документации по RuboCop.

RuboCop располагает тремя основными функциями:

  • Анализ кода для проверки стиля кода
  • Форматирование кода
  • Заменяет собственные возможности анализа кода Ruby с помощью ruby -w

Приложение будет запускать RuboCop на сервере CI и создавать тестовые запуски (тесты CI в данном случае), которые сообщают о результатах, которые RuboCop сообщает GitHub.

REST API позволяет сообщать подробные сведения о каждом выполнении проверки, включая состояния, изображения, сводки, заметки и запрошенные действия.

Заметки — это сведения о конкретных строках кода в репозитории. Заметка позволяет выявить и визуализировать точные части кода, для которых необходимо отобразить дополнительные сведения. Например, можно показать эту информацию как комментарий, ошибку или предупреждение в определенной строке кода. В этом руководстве используются заметки для визуализации ошибок RuboCop.

Чтобы воспользоваться преимуществами запрошенных действий, разработчики приложений могут создавать кнопки на вкладке Проверки запросов на вытягивание. Когда кто-то щелкает requested_action``check_run одну из этих кнопок, щелчок отправляет событие в GitHub App. Действие, выполняемое приложением, полностью настраивается разработчиком приложения. В этом руководстве описано, как добавить кнопку, которая позволяет пользователям запрашивать, чтобы RuboCop исправил ошибки, которые он находит. RuboCop поддерживает автоматическое исправление ошибок с помощью параметра командной строки, и вы настроите requested_action, чтобы воспользоваться преимуществами этого параметра.

Ниже указанны шаги, которые вы выполните при работе с этим разделом.

  1.        [Добавление файла Ruby](#step-21-add-a-ruby-file)
    
  2.        [Разрешить RuboCop клонировать репозиторий тестов](#step-22-allow-rubocop-to-clone-the-test-repository)
    
  3.        [Запуск RuboCop](#step-23-run-rubocop)
    
  4.        [Сбор ошибок RuboCop](#step-24-collect-rubocop-errors)
    
  5.        [Обновление выполнения проверки с помощью результатов теста CI](#step-25-update-the-check-run-with-ci-test-results)
    
  6.        [Автоматическое исправление ошибок RuboCop](#step-26-automatically-fix-rubocop-errors)
    

Шаг 2.1. Добавление файла Ruby

Вы можете передать определенные файлы или целые каталоги в RuboCop для проверки. В этом руководстве вы запустите RuboCop в целом каталоге. RuboCop проверяет только код Ruby. Чтобы протестировать данные GitHub App, необходимо добавить в репозиторий файл Ruby, содержащий ошибки для поиска RuboCop. После добавления следующего файла Ruby в репозиторий вы обновите проверку CI, чтобы запустить RuboCop в коде.

  1. Перейдите к репозиторию тестов, созданному в тестовом режиме, который сервер прослушивает приложение. Это репозиторий, к которому вы предоставили доступ к приложению.

  2. Создайте файл с именем myfile.rb. Дополнительные сведения см. в разделе Создание новых файлов.

  3. Добавьте в 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
    
  4. Если вы создали файл локально, убедитесь, что вы зафиксируйте и отправьте файл в репозиторий на GitHub.

Шаг 2.2. Разрешить RuboCop клонировать репозиторий тестов

RuboCop доступен в виде служебной программы командной строки. Это означает, что если вы хотите запустить RuboCop в репозитории, данные GitHub App потребуется клонировать локальную копию репозитория на сервере CI, чтобы RuboCop смог проанализировать файлы. Для этого коду потребуется возможность выполнения операций Git, а для клонирования репозитория необходимо иметь правильные разрешения на GitHub App.

Разрешить операции Git

Для выполнения операций Git в приложении Ruby можно использовать драгоценный камень ruby-git. Созданный Gemfile в программе установки[ уже содержит драгоценный камень ruby-git, и вы установили его при запуске bundle install.](#start-the-server)

Теперь в верхней части server.rb файла под другими require элементами добавьте следующий код:

Ruby
require 'git'

Обновление разрешений приложения

Затем необходимо обновить разрешения GitHub App. Приложению потребуется разрешение на чтение содержимого для клонирования репозитория. Кроме того, в этом руководстве потребуется разрешение на запись для отправки содержимого в GitHub. Чтобы обновить разрешения приложения:

  1. Выберите приложение на странице параметров приложения и щелкните "Разрешения" и "События " на боковой панели.
  2. В разделе "Разрешения репозитория" рядом с "Содержимое" выберите "Чтение и запись".
  3. Нажмите Сохранить изменения в нижней части страницы.
  4. Если вы установили приложение в своей учетной записи, проверьте свой почтовый ящик и перейдите по ссылке, чтобы принять новые разрешения. Каждый раз, когда вы изменяете разрешения или веб-перехватчики приложения, пользователи, которые установили приложение (включая вас самих), должны принять новые разрешения, прежде чем изменения вступят в силу. Вы также можете принять новые разрешения, перейдя на страницу установки. Вы увидите ссылку под именем приложения, чтобы узнать, что приложение запрашивает различные разрешения. Нажмите кнопку "Рецензирование запроса", а затем нажмите кнопку "Принять новые разрешения".

Добавление кода для клонирования репозитория

Чтобы клонировать репозиторий, код будет использовать разрешения GitHub Appи пакет SDK Octokit для создания маркера установки для приложения (x-access-token:TOKEN) и его использования в следующей команде клонирования:

git clone https://x-access-token:[email protected]/OWNER/REPO.git

Указанная выше команда клонирует репозиторий по протоколу HTTPS. Для него требуется полное имя репозитория, в том числе владелец репозитория (пользователь или организация) и имя репозитория. Например, репозиторий octocat Hello-World имеет полное имя octocat/hello-world.

          `server.rb` Откройте файл. В блоке кода, который начинается с `helpers do`того места, где он говорит `# ADD CLONE_REPOSITORY HELPER METHOD HERE #`, добавьте следующий код:
Ruby
    # Clones the repository to the current working directory, updates the
    # contents using Git pull, and checks out the ref.
    #
    # full_repo_name  - The owner and repo. Ex: octocat/hello-world
    # repository      - The repository name
    # ref             - The branch, commit SHA, or tag to check out
    def clone_repository(full_repo_name, repository, ref)
      @git = Git.clone("https://x-access-token:#{@installation_token.to_s}@github.com/#{full_repo_name}.git", repository)
      pwd = Dir.getwd()
      Dir.chdir(repository)
      @git.pull
      @git.checkout(ref)
      Dir.chdir(pwd)
    end

Указанный код использует драгоценный камень ruby-git для клонирования репозитория с помощью маркера установки приложения. Он клонирует код в том же каталоге, что server.rbи . Чтобы выполнить команды Git в репозитории, код необходимо изменить в каталоге репозитория. Перед изменением каталогов код сохраняет текущую рабочую папку в переменной (pwd), чтобы помнить, куда вернуться перед выходом из метода clone_repository.

В каталоге репозитория этот код извлекает и объединяет последние изменения (@git.pull) и проверяет конкретный ссылочный код Git (@git.checkout(ref)). Код, который делает все это, хорошо подходит в своем собственном методе. Для выполнения этих операций методу требуется имя и полное имя репозитория, а также ссылка для извлечения. Ссылка может быть SHA фиксации, ветвью или тегом. По завершении код изменяет каталог обратно в исходный рабочий каталог (pwd).

Теперь у вас есть метод, который клонирует репозиторий и извлекает ссылку. Далее необходимо добавить код, чтобы получить необходимые входные параметры и вызвать новый метод clone_repository.

В блоке кода, который начинается с helpers do, в вспомогательном initiate_check_run методе, где он говорит # ***** RUN A CI TEST *****, добавьте следующий код:

Ruby
    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 #

Указанный выше код получает полное имя репозитория и заголовок SHA фиксации из полезных данных веб-перехватчика check_run.

Шаг 2.3. Запуск RuboCop

До сих пор код клонирует репозиторий и создает проверки запусков с помощью сервера CI. Теперь вы перейдете к деталям РубоCop linter и проверяет заметки.

Во-первых, вы добавите код для запуска RuboCop и сохраните ошибки кода стиля в формате JSON.

В блоке кода, который начинается с helpers do, найдите вспомогательный initiate_check_run метод. В этом вспомогательном методе в разделе , где clone_repository(full_repo_name, repository, head_sha)он говорит # ADD CODE HERE TO RUN RUBOCOP #, добавьте следующий код:

Ruby
        # 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 #

Указанный код запускает RuboCop для всех файлов в каталоге репозитория. --format json Параметр сохраняет копию подкладки в формате синтаксического анализа компьютера. Дополнительные сведения и пример формата JSON см . в разделе "Форматирование JSON" в документации RuboCop. Этот код также анализирует JSON, чтобы получить доступ к ключам и значениям в GitHub App с помощью переменной @output .

После запуска RuboCop и сохранения результатов подкладки этот код запускает команду rm -rf , чтобы удалить выход из репозитория. Так как код сохраняет ruboCop, @report он может безопасно удалить выход из репозитория.

Команда rm -rf не может быть отменена. Чтобы обеспечить безопасность приложения, код в этом руководстве проверяет входящие веб-перехватчики для внедренных вредоносных команд, которые можно использовать для удаления другого каталога, отличного от предполагаемого приложения. Например, если злоумышленник отправил веб-перехватчик с именем репозитория ./, приложение удалит корневой каталог. Метод verify_webhook_signature проверяет отправителя веб-перехватчика. Обработчик verify_webhook_signature событий также проверяет допустимое имя репозитория. Дополнительные сведения см. в before разделе "Определение фильтра".

Тестирование кода

Ниже показано, как проверить работу кода и просмотреть ошибки, сообщаемые RuboCop.

  1. Выполните следующую команду, чтобы перезапустить сервер из терминала. Если сервер уже запущен, сначала введите Ctrl-C в терминале, чтобы остановить сервер, а затем выполните следующую команду, чтобы снова запустить сервер.

    Shell
    ruby server.rb
    
  2. В репозитории, где вы добавили myfile.rb файл, создайте новый запрос на вытягивание.

  3. На вкладке терминала, на которой запущен сервер, вы увидите выходные данные отладки, содержащие ошибки подкладки. Ошибки подкладки печатаются без форматирования. Вы можете скопировать и вставить выходные данные отладки в веб-инструмент, например средство форматирования JSON, чтобы отформатировать выходные данные JSON, как показано в следующем примере:

    {
      "metadata": {
        "rubocop_version": "0.60.0",
        "ruby_engine": "ruby",
        "ruby_version": "2.3.7",
        "ruby_patchlevel": "456",
        "ruby_platform": "universal.x86_64-darwin18"
      },
      "files": [
        {
          "path": "Octocat-breeds/octocat.rb",
          "offenses": [
            {
              "severity": "convention",
              "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.",
              "cop_name": "Style/StringLiterals",
              "corrected": false,
              "location": {
                "start_line": 17,
                "start_column": 17,
                "last_line": 17,
                "last_column": 22,
                "length": 6,
                "line": 17,
                "column": 17
              }
            },
            {
              "severity": "convention",
              "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.",
              "cop_name": "Style/StringLiterals",
              "corrected": false,
              "location": {
                "start_line": 17,
                "start_column": 25,
                "last_line": 17,
                "last_column": 29,
                "length": 5,
                "line": 17,
                "column": 25
              }
            }
          ]
        }
      ],
      "summary": {
        "offense_count": 2,
        "target_file_count": 1,
        "inspected_file_count": 1
      }
    }
    

Шаг 2.4. Сбор ошибок RuboCop

Переменная @output содержит проанализированные результаты JSON отчета RuboCop. Как показано в примере выходных данных на предыдущем шаге, результаты содержат summary раздел, который код может использовать для быстрого определения наличия ошибок. В указанном ниже коде задается вывод о выполнении проверки success при отсутствии сообщений об ошибках. RuboCop сообщает об ошибках для каждого файла в массиве files, поэтому при наличии ошибок необходимо извлечь некоторые данные из объекта файла.

Конечные точки REST API для управления проверками позволяют создавать заметки для определенных строк кода. При создании или обновлении выполнения проверки можно добавить заметки. В этом руководстве вы обновите выполнение проверки с заметками с помощью конечной PATCH /repos/{owner}/{repo}/check-runs/{check_run_id} точки. Дополнительные сведения о конечной точке см. в разделе Конечные точки REST API для проверки выполнения.

API ограничивает количество заметок не более 50 на запрос. Чтобы создать более 50 заметок, необходимо выполнить несколько запросов к конечной точке "Обновить проверку выполнения". Например, чтобы создать 105 заметок, необходимо выполнить три отдельных запроса к API. Первые два запроса будут иметь 50 заметок, а третий запрос будет включать пять оставшихся заметок. При каждом обновлении проверки выполнения заметки добавляются в список заметок, которые уже доступны для выполнения проверки.

При выполнении проверки ожидаются заметки в виде массива объектов. Каждый объект заметки должен включать path, start_line, end_line, annotation_level и message. RuboCop также предоставляет start_column и end_column, позволяя включить эти необязательные параметры в заметку. Заметки поддерживают только start_column и end_column в одной строке. Дополнительные сведения см. в разделе annotations "Объект" в Конечные точки REST API для проверки выполнения.

Теперь вы добавите код для извлечения необходимых сведений из RuboCop, необходимых для создания каждой заметки.

В разделе кода, добавленного на предыдущем шаге, в котором он говорит # ADD ANNOTATIONS CODE HERE #, добавьте следующий код:

Ruby
    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 #

Этот код ограничивает общее количество заметок до 50. Но этот код можно изменить, чтобы обновить выполнение проверки для каждого пакета из 50 заметок. Указанный код включает переменную max_annotations, задающую ограничение в 50, которая используется в цикле, выполняющем итерацию по нарушениям.

Если значение offense_count равно нулю, тест CI имеет значение success. При наличии ошибок этот код задает для заключения значение neutral, чтобы предотвратить строгое применение ошибок из анализатора кода. Но значение заключения можно изменить на failure, если необходимо убедиться, что набор проверок завершается ошибкой при возникновении ошибок анализа кода.

При обнаружении ошибок указанный код выполняет итерацию по массиву files в отчете RuboCop. Для каждого файла он извлекает путь к файлу и задает значение notice для уровня заметки. Вы можете пойти еще дальше и задать определенные уровни предупреждений для каждого типа RuboCop Cop, но чтобы упростить работу в этом руководстве, все ошибки заданы на уровне notice.

Этот код также выполняет итерацию по каждой ошибке в массиве offenses и собирает расположение нарушения и сообщения об ошибке. После извлечения необходимых сведений код создает заметку для каждой ошибки и сохраняет ее в массиве annotations. Так как заметки поддерживают только начальные и конечные столбцы в одной строке, start_column и end_column добавляются в объект annotation только в том случае, если значения начальной и конечной строк совпадают.

Этот код еще не создает заметку для выполнения проверки. Вы добавите этот код в следующем разделе.

Шаг 2.5. Обновление выполнения проверки с помощью результатов теста CI

Каждый запуск проверки из GitHub содержит output объект, содержащий titleобъект , summaryа также text``annotations``images. И summary``title являются единственными необходимыми параметрами для outputэтого, но только они не предлагают много подробных сведений, поэтому в этом руководстве также добавляется text и annotations.

          `summary`В этом примере используется сводная информация из RuboCop и добавляет новые строки (`\n`) для форматирования выходных данных. Вы можете настроить то, что вы добавляете в параметр `text`, но в этом примере для параметра `text` устанавливается версия RuboCop. Следующий код задает `summary` и `text`.

В разделе кода, добавленного на предыдущем шаге, в котором он говорит # ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #, добавьте следующий код:

Ruby
        # 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']}"

Теперь код должен иметь все сведения, необходимые для обновления выполнения проверки. На шаге 1.3. Обновите выполнение проверки, добавьте код, чтобы задать состояние выполнения successпроверки. Вам потребуется обновить этот код, чтобы использовать переменную conclusion, заданную на основе результатов RuboCop (в success или neutral). Ниже приведен код, добавленный ранее в 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'
)

Замените этот код следующим кодом:

Ruby
        # 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'
        )

Теперь, когда код задает вывод на основе состояния теста CI и добавляет выходные данные из результатов RuboCop, вы создали тест CI.

Приведенный выше код также добавляет функцию, вызываемую запрошенным действием, на сервер CI через actions объект. (Обратите внимание, это не связано с GitHub Actions.) Для получения дополнительной информации см. Запрос дальнейших действий после проверки. Запрошенные действия добавляют кнопку на вкладке "Проверки " на GitHub, что позволяет пользователю запрашивать выполнение проверки для выполнения дополнительных действий. Дополнительное действие полностью настраивается приложением. Например, так как RuboCop имеет возможность автоматически исправлять ошибки, обнаруженные в коде Ruby, сервер CI может использовать кнопку запрошенных действий, чтобы разрешить пользователям запрашивать автоматические исправления ошибок. Когда кто-то нажимает кнопку, приложение получает событие check_run с действием requested_action. Каждое запрошенное действие имеет identifier, используемый приложением для определения нажатия кнопки.

Указанный код не содержит ошибки, которые еще не исправляются автоматически с помощью RuboCop. Далее вы добавите это в учебнике.

Тестирование кода

Ниже показано, как проверить работу кода и просмотреть только что созданный тест CI.

  1. Выполните следующую команду, чтобы перезапустить сервер из терминала. Если сервер уже запущен, сначала введите Ctrl-C в терминале, чтобы остановить сервер, а затем выполните следующую команду, чтобы снова запустить сервер.

    Shell
    ruby server.rb
    
  2. В репозитории, где вы добавили myfile.rb файл, создайте новый запрос на вытягивание.

  3. В созданном запросе на вытягивание перейдите на вкладку "Проверки ". Вы должны увидеть заметки для каждой из обнаруженных ошибок RuboCop. Также обратите внимание на кнопку "Исправить это", созданную путем добавления запрошенного действия.

Шаг 2.6. Автоматическое исправление ошибок RuboCop

До сих пор вы создали тест CI. В этом разделе вы добавите еще одну возможность, которая использует RuboCop для автоматического исправления обнаруженных ошибок. Вы уже добавили кнопку "Исправить это" на шаге 2.5. Обновите выполнение проверки с помощью результатов теста CI. Теперь вы добавите код для обработки requested_action события выполнения проверки, которое активируется при нажатии кнопки "Исправить это".

Средство RuboCop предлагает --auto-correct параметр командной строки для автоматического исправления ошибок, которые он находит. Дополнительные сведения см. в документации по RuboCop по автозамене . При использовании возможности --auto-correct обновления применяются к локальным файлам на сервере. Вам потребуется отправить изменения в GitHub после того, как RuboCop вносит исправления.

Чтобы отправить в репозиторий, приложение должно иметь разрешения на запись для "Содержимое" в репозитории. Вы уже установили это разрешение на чтение и запись обратно в шаге 2.2. Разрешить RuboCop клонировать тестовый репозиторий.

Чтобы зафиксировать файлы, Git должен знать, какое имя пользователя и адрес электронной почты необходимо связать с фиксацией. Затем вы добавите переменные среды для хранения имени и адреса электронной почты, которые будет использоваться приложением при фиксации Git.

  1. Откройте файл, .env созданный ранее в этом руководстве.

  2. Добавьте в файл следующие переменные .env среды. Замените APP_NAME именем приложения и EMAIL_ADDRESS любым сообщением электронной почты, которое вы хотите использовать в этом примере.

    Shell
    GITHUB_APP_USER_NAME="APP_NAME"
    GITHUB_APP_USER_EMAIL="EMAIL_ADDRESS"
    

Затем необходимо добавить код для чтения переменных среды и задания конфигурации Git. Вы добавите этот код в ближайшее время.

Когда кто-то нажимает кнопку "Исправить это", ваше приложение получает веб-перехватчик выполнения проверки с типом действия requested_action.

На шаге 1.3. Обновите запуск проверки, который вы обновили event_handler в server.rb файле, чтобы найти действия в событии check_run . У вас уже есть инструкция CASE для обработки типов действий created и 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
          `rerequested` После случая, когда говорится`# ADD REQUESTED_ACTION METHOD HERE #`, добавьте следующий код:
Ruby
    when 'requested_action'
      take_requested_action

Этот код вызывает новый метод, который будет обрабатывать все события requested_action для приложения.

В блоке кода, который начинается с helpers doтого, где он говорит # ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #, добавьте следующий вспомогательный метод:

Ruby
    # Handles the check run `requested_action` event
    # See /webhooks/event-payloads/#check_run
    def take_requested_action
      full_repo_name = @payload['repository']['full_name']
      repository     = @payload['repository']['name']
      head_branch    = @payload['check_run']['check_suite']['head_branch']

      if (@payload['requested_action']['identifier'] == 'fix_rubocop_notices')
        clone_repository(full_repo_name, repository, head_branch)

        # Sets your commit username and email address
        @git.config('user.name', ENV['GITHUB_APP_USER_NAME'])
        @git.config('user.email', ENV['GITHUB_APP_USER_EMAIL'])

        # Automatically correct RuboCop style errors
        @report = `rubocop '#{repository}/*' --format json --auto-correct`

        pwd = Dir.getwd()
        Dir.chdir(repository)
        begin
          @git.commit_all('Automatically fix Octo RuboCop notices.')
          @git.push("https://x-access-token:#{@installation_token.to_s}@github.com/#{full_repo_name}.git", head_branch)
        rescue
          # Nothing to commit!
          puts 'Nothing to commit'
        end
        Dir.chdir(pwd)
        `rm -rf '#{repository}'`
      end
    end

Приведенный выше код клонирует репозиторий так же, как и код, добавленный на шаге 2.2. Разрешить RuboCop клонировать тестовый репозиторий. Инструкция if проверяет, соответствует ли идентификатор запрошенного действия идентификатору кнопки RuboCop (fix_rubocop_notices). Когда они совпадают, код клонирует репозиторий, задает имя пользователя и адрес электронной почты Git, а затем запускает RuboCop с параметром --auto-correct. Параметр --auto-correct автоматически применяет изменения к файлам локального сервера CI.

Файлы изменяются локально, но вам по-прежнему потребуется отправить их в GitHub. Вы будете использовать драгоценный ruby-git камень для фиксации всех файлов. В Git есть одна команда, которая выполняет этапы всех измененных или удаленных файлов и фиксирует их: git commit -a. Чтобы сделать то же самое с помощью ruby-git, указанный код использует метод commit_all. Затем код отправляет зафиксированные файлы в GitHub с помощью маркера установки, используя тот же метод проверки подлинности, что и команда Git clone . Наконец, он удаляет каталог репозитория, чтобы убедиться, что рабочая папка подготовлена к следующему событию.

Написанный код теперь завершает сервер непрерывной интеграции, созданный с помощью GitHub App и проверок. Чтобы просмотреть полный окончательный код приложения, см . полный пример кода.

Тестирование кода

В следующих шагах показано, как проверить, работает ли код, и что RuboCop может автоматически исправить ошибки, которые он находит.

  1. Выполните следующую команду, чтобы перезапустить сервер из терминала. Если сервер уже запущен, сначала введите Ctrl-C в терминале, чтобы остановить сервер, а затем выполните следующую команду, чтобы снова запустить сервер.

    Shell
    ruby server.rb
    
  2. В репозитории, где вы добавили myfile.rb файл, создайте новый запрос на вытягивание.

  3. В созданном запросе на вытягивание перейдите на вкладку "Проверки " и нажмите кнопку "Исправить это", чтобы автоматически исправить обнаруженные ошибки RuboCop.

  4. Перейдите на вкладку "Фиксации ". Вы увидите новую фиксацию по имени пользователя, заданному в конфигурации Git. Возможно, потребуется обновить браузер, чтобы увидеть обновление.

  5. Перейдите на вкладку "Проверки ". Вы увидите новый контрольный набор для Octo RuboCop. Но на этот раз не должно быть ошибок, потому что RuboCop исправил их все.

Полный пример кода

Это то, как должен выглядеть окончательный код server.rb после выполнения всех действий, описанных в этом руководстве. В коде также есть комментарии, которые предоставляют дополнительный контекст.

Ruby
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

Следующие шаги

Теперь у вас должно быть приложение, которое получает события API, создает проверки, использует RuboCop для поиска ошибок Ruby, создает заметки в запросе на вытягивание и автоматически устраняет ошибки linter. Затем вы можете развернуть код приложения, развернуть приложение и сделать приложение общедоступным.

Если у вас есть вопросы, запустите обсуждение GitHub Community в категории API и веб-перехватчиков.

Изменение кода приложения

В этом руководстве показано, как создать кнопку "Исправить это", которая всегда отображается в запросах на вытягивание в репозитории. Попробуйте обновить код, чтобы отобразить кнопку "Исправить эту" только при обнаружении ошибок RuboCop.

Если вы предпочитаете, что RuboCop не фиксирует файлы непосредственно в главная ветвь, обновите код, чтобы вместо этого создать запрос на вытягивание с новой ветвью, основанной на главная ветвь.

Развертывание приложения

В этом руководстве показано, как разрабатывать приложение локально. Когда вы будете готовы к развертыванию приложения, необходимо внести изменения в обслуживание приложения и обеспечить безопасность учетных данных приложения. Действия, которые вы выполняете, зависят от используемого сервера, но в следующих разделах приведены общие рекомендации.

Размещение приложения на сервере

В этом руководстве используется компьютер или пространство кода в качестве сервера. После того как приложение будет готово к использованию в рабочей среде, необходимо развернуть приложение на выделенном сервере. Например, можно использовать Azure App Service.

Обновление URL-адреса веб-перехватчика

После установки сервера для получения трафика веб-перехватчика из GitHubобновите URL-адрес веб-перехватчика в параметрах приложения. Не следует использовать Smee.io для пересылки веб-перехватчиков в рабочей среде.

          `:port` Обновление параметра

При развертывании приложения необходимо изменить порт, в котором прослушивается сервер. Код уже сообщает серверу прослушивать все доступные сетевые интерфейсы, установив для :bindэтого значение0.0.0.0.

Например, можно задать PORT переменную в .env файле на сервере, чтобы указать порт, в котором должен прослушивать сервер. Затем можно обновить место, в котором наборы :port кода будут выполняться таким образом, чтобы сервер прослушивал порт развертывания:

Ruby
set :port, ENV['PORT']

Защита учетных данных приложения

Вы никогда не должны публиковать закрытый ключ приложения или секрет веб-перехватчика. В этом руководстве хранятся учетные данные приложения в gitignored-файле .env . При развертывании приложения следует выбрать безопасный способ хранения учетных данных и обновления кода, чтобы получить соответствующее значение. Например, вы можете хранить учетные данные в секретной службе, такой как Azure Key Vault. При запуске приложения он может получить учетные данные и сохранить их в переменных среды на сервере, на котором развернуто приложение.

Дополнительные сведения см. в разделе Лучшие практики создания приложения на GitHub.

Общий доступ к приложению

Если вы хотите предоставить общий доступ к приложению другим пользователям и организациям, сделайте приложение общедоступным. Дополнительные сведения см. в разделе Как сделать приложение GitHub публичным или частным.

Применение рекомендаций

Вам следует следовать рекомендациям по использованию данных GitHub App. Дополнительные сведения см. в разделе Лучшие практики создания приложения на GitHub.