Skip to main content

Génération de vérifications CI avec un application GitHub

Générez un serveur d’intégration continue pour exécuter des tests en utilisant une GitHub App et des vérifications.

Introduction

Ce tutoriel montre comment générer un serveur d’intégration continue (CI) qui exécute des tests sur un nouveau code poussé (push) vers un dépôt. Le tutoriel montre comment générer et configurer une GitHub App afin qu’elle fasse office de serveur qui reçoit les événements de webhook check_run et check_suite et y répond avec l’API REST de GitHub.

Dans ce tutoriel, vous allez utiliser votre ordinateur ou codespace comme serveur pendant que vous développez votre application. Une fois que l’application est prête pour être utilisée en production, vous devez déployer votre application sur un serveur dédié.

Ce tutoriel utilise Ruby, mais vous pouvez utiliser n’importe quel langage de programmation que vous pouvez exécuter sur votre serveur.

Ce tutoriel est divisé en deux parties :

  • Dans la première partie, vous allez apprendre à configurer le framework d’un serveur CI en utilisant l’API REST de GitHub, à créer des exécutions de vérification pour les tests de CI quand un dépôt reçoit des commits nouvellement poussés et à réexécuter les exécutions de vérification quand un utilisateur demande cette action sur GitHub.
  • Dans la deuxième partie, vous allez ajouter des fonctionnalités à votre test de CI, en ajoutant un test de linter à votre serveur CI. Vous allez également créer des annotations qui s’affichent sous l’onglet Vérifications et Fichiers modifiés d’une demande de tirage (pull request) et corriger automatiquement les recommandations de linter en exposant un bouton « Corriger cela » sous l’onglet Vérifications de la demande de tirage.

À propos de l’intégration continue (CI)

L’intégration continue (CI) est une pratique logicielle qui nécessite un validation fréquente de code dans un dépôt partagé. Le fait de valider le code plus souvent permet de détecter les erreurs plus tôt, et réduit la quantité de code dont un développeur a besoin pour le débogage quand il trouve la source d’une erreur. Les mises à jour fréquentes du code facilitent également la fusion des modifications apportées par différents membres d’une équipe de développement logiciel. Ceci est idéal pour les développeurs, qui peuvent alors passer plus de temps à écrire du code et moins de temps à déboguer des erreurs ou à résoudre les conflits de fusion.

Un serveur de CI héberge du code qui exécute des tests de CI, comme des linters de code (qui vérifient la mise en forme du style), des vérifications de sécurité, la couverture du code et d’autres vérifications sur de nouvelles validations de code dans un dépôt. Des serveurs de CI peuvent même générer et déployer du code sur des serveurs intermédiaires ou de production. Pour obtenir des exemples des types de tests de CI que vous pouvez créer avec une GitHub App, consultez les applications d’intégration continue disponibles dans GitHub Marketplace.

À propos des vérifications

L’API REST de GitHub vous permet de configurer des tests de CI (vérifications) qui s’exécutent automatiquement sur chaque validation de code dans un dépôt. L’API fournit des informations détaillées sur chaque vérification sous l’onglet Vérifications de la demande de tirage sur GitHub. Vous pouvez utiliser des vérifications dans un dépôt pour déterminer quand un commit de code introduit des erreurs.

Les vérifications incluent les exécutions de vérification, les suites de vérifications et les états de commit.

  • Une exécution de vérification est un test de CI individuel qui s’exécute sur un commit.
  • Une suite de vérifications est un groupe d’exécutions de vérification.
  • Un état de commit marque l’état d’un commit, par exemple error, failure, pending ou success, et est visible dans une demande de tirage sur GitHub. Les suites de vérifications et les exécutions de vérification contiennent des états de commit.

GitHub crée automatiquement des événements check_suite pour les nouveaux commits de code dans un dépôt en utilisant le flux par défaut, même si vous pouvez changer les paramètres par défaut. Pour plus d’informations, consultez « Suites de vérifications ». Voici comment fonctionne le flux par défaut :

  1. Quand quelqu’un pousse du code vers le dépôt, GitHub envoie automatiquement l’événement check_suite avec une action de requestedà toutes les GitHub Apps installées sur le dépôt qui disposent de l’autorisation checks:write. Cet événement permet aux applications de savoir que du code a été poussé vers le dépôt et que GitHub a créé automatiquement une suite de vérifications.
  2. Lorsque votre application reçoit cet événement, elle peut ajouter des exécutions de vérification à cette suite.
  3. Vos exécutions de vérification peuvent inclure des annotations qui sont affichées sur des lignes de code spécifiques. Des annotations sont visibles sous l’onglet Vérifications. Quand vous créez une annotation pour un fichier qui fait partie de la demande de tirage, les annotations sont également affichées sous l’onglet Fichiers modifiés. Pour plus d’informations, consultez l’objet annotations dans la documentation « Exécutions de vérifications ».

Pour plus d’informations sur les vérifications, consultez « Vérifications » et « Utilisation de l’API REST pour interagir avec des vérifications ».

Prérequis

Ce tutoriel part du principe que vous avez une compréhension de base du langage de programmation Ruby.

Avant de commencer, vous pouvez vous familiariser avec les concepts suivants :

Les vérifications peuvent également être utilisées avec l’API GraphQL, mais ce tutoriel se concentre sur l’API REST. Pour plus d’informations sur les objets GraphQL, consultez Suite de vérifications et Exécution de vérification dans la documentation GraphQL.

Programme d’installation

Les sections suivantes vous guident tout au long de la configuration des composants suivants :

  • Un dépôt pour stocker le code de votre application.
  • Un moyen de recevoir des webhooks localement.
  • Une GitHub App qui est abonnée aux événements de webhook « Suite de vérifications » et « Exécution de vérification », a l’autorisation d’écriture pour les vérifications et utilise une URL de webhook que vous pouvez recevoir localement.

Créer un dépôt pour stocker le code de votre GitHub App

  1. Créez un référentiel pour stocker le code de votre application. Pour plus d’informations, consultez « Création d’un dépôt ».
  2. Clonez votre référentiel à partir de l’étape précédente. Pour plus d’informations, consultez « Clonage d’un dépôt ». Vous pouvez utiliser un clone local ou GitHub Codespaces.
  3. Dans un terminal, accédez au répertoire dans lequel votre clone est stocké.
  4. Créez un fichier Ruby nommé server.rb. Ce fichier contient tout le code de votre application. Vous ajouterez du contenu à ce fichier ultérieurement.
  5. Si le répertoire n’inclut pas encore de fichier .gitignore, ajoutez un fichier .gitignore. Vous ajouterez du contenu à ce fichier ultérieurement. Pour plus d’informations sur les fichiers .gitignore, consultez « Ignorer des fichiers ».
  6. Créez un fichier appelé Gemfile. Ce fichier décrit les dépendances de gemme dont votre code Ruby a besoin. Ajoutez le contenu suivant à votre 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. Créez un fichier appelé config.ru. Ce fichier configure l’exécution de votre serveur Sinatra. Ajoutez le contenu suivant à votre fichier config.ru :
    Ruby
    require './server'
    run GHAapp
    

Obtenir une URL de proxy webhook

Pour développer votre application localement, vous pouvez utiliser une URL de proxy webhook pour transférer des événements de webhook à partir de GitHub vers votre ordinateur ou codespace. Ce tutoriel utilise Smee.io pour fournir une URL de proxy webhook et transférer des événements.

  1. Dans un terminal, exécutez la commande suivante pour installer le client Smee :
    Shell
    npm install --global smee-client
    
  2. Dans votre navigateur, accédez à https://smee.io/.
  3. Cliquez sur Démarrer un nouveau canal.
  4. Copiez l’URL complète sous « URL du proxy webhook ».
  5. Dans le terminal, exécutez la commande suivante pour démarrer le client Smee. Remplacez YOUR_DOMAIN par l’URL du proxy webhook que vous avez copiée à l’étape précédente.
    Shell
    smee --url YOUR_DOMAIN --path /event_handler --port 3000
    
    Un résultat similaire à ce qui suit s’affiche normalement :
    Forwarding https://smee.io/YOUR_DOMAIN to http://127.0.0.1:3000/event_handler
    Connected https://smee.io/YOUR_DOMAIN
    

La commande smee --url https://smee.io/YOUR_DOMAIN indique à Smee de transférer tous les événements de webhook reçus par le canal Smee vers le client Smee s’exécutant sur votre ordinateur. L’option --path /event_handler transfère les événements à la route /event_handler. L’option --port 3000 spécifie le port 3000, qui est le port que vous indiquerez à votre serveur d’écouter, quand vous ajouterez du code plus loin dans le tutoriel. Si vous utilisez Smee, votre machine n’a pas besoin d’être ouverte à l’Internet public pour recevoir des webhooks provenant de GitHub. Vous pouvez également ouvrir cette URL Smee dans votre navigateur pour inspecter les charges utiles de webhook à mesure qu’elles arrivent.

Nous vous recommandons de laisser cette fenêtre de terminal ouverte et Smee connecté pour le reste des étapes décrites dans ce guide. Bien que vous puissiez déconnecter et reconnecter le client Smee sans perdre votre domaine unique, il est plus facile de rester connecté et d’effectuer les autres tâches de ligne de commande dans une autre fenêtre de terminal.

Inscrire une GitHub App

Pour ce tutoriel, vous devez inscrire une GitHub App qui :

  • a des webhooks actifs
  • utilise une URL de webhook que vous pouvez recevoir localement
  • dispose de l’autorisation de dépôt « Vérifications »
  • s’abonne aux événements webhook « Suite de vérifications » et « Exécution de vérification »

Les étapes suivantes vous guident tout au long de la configuration d’une GitHub App avec ces paramètres. Pour plus d’informations sur les GitHub App, consultez « Inscription d’une application GitHub ».

  1. Dans le coin supérieur droit de n’importe quelle page sur GitHub, cliquez sur votre photo de profil.
  2. Accédez aux paramètres de votre compte.
    • Pour une GitHub App appartenant à un compte personnel, cliquez sur Paramètres.
    • Pour une GitHub App appartenant à une organisation :
      1. Cliquez sur Vos organisations.
      2. À droite de l’organisation, cliquez sur Paramètres.
  3. Dans la barre latérale gauche, cliquez sur Paramètres de développeur.
  4. Dans la barre latérale à gauche, cliquez sur GitHub Apps .
  5. Cliquez sur Nouvelle application GitHub.
  6. Sous « Nom de l’application GitHub », entrez un nom pour votre application. Par exemple, USERNAME-ci-test-appUSERNAME est votre nom d'utilisateur GitHub.
  7. Sous « URL de la page d’accueil », entrez une URL pour votre application. Par exemple, vous pouvez utiliser l’URL du référentiel que vous avez créé pour stocker le code de votre application.
  8. Ignorez les sections « Identification et autorisation des utilisateurs » et « Après installation » pour ce tutoriel.
  9. Assurez-vous que Actif est sélectionné sous « Webhooks ».
  10. Sous « URL de webhook », entrez l’URL de votre proxy webhook à partir d’une version antérieure. Pour plus d’informations, consultez « Obtenir une URL de proxy webhook ».
  11. Sous « Webhook secret », entrez une chaîne aléatoire. Ce secret est utilisé pour vérifier que les webhooks sont envoyés par GitHub. Enregistrez cette chaîne ; vous l’utiliserez plus tard.
  12. Sous « Autorisations du dépôt », en regard de « Vérifications », sélectionnez Lire et écrire.
  13. Sous « S’abonner aux événements », sélectionnez Suite de vérifications et Exécution de vérification.
  14. Sous « Où cette application GitHub peut-elle être installée ? », sélectionnez Uniquement sur ce compte. Vous pouvez le modifier cela ultérieurement si vous souhaitez publier votre application.
  15. Cliquez sur Créer une application GitHub.

Stocker les informations d’identification et les informations de connexion de votre application

Ce tutoriel vous montre comment stocker les informations d’identification de votre application et identifier les informations en tant que variables d’environnement dans un fichier .env. Quand vous déployez votre application, vous devez changer la façon dont vous stockez les informations d’identification. Pour plus d’informations, consultez « Déployer votre application ».

Assurez-vous que vous êtes sur un ordinateur sécurisé avant d’effectuer ces étapes, car vous allez stocker vos informations d’identification localement.

  1. Dans votre terminal, accédez au répertoire dans lequel votre clone est stocké.

  2. Créez un fichier appelé .env au niveau supérieur de ce répertoire.

  3. Ajoutez .env à votre fichier .gitignore. Cette action vous empêche de valider accidentellement les informations d'identification de votre application.

  4. Ajoutez le contenu suivant à votre fichier .env. Vous mettrez à jour les valeurs ultérieurement.

    Shell
    GITHUB_APP_IDENTIFIER="YOUR_APP_ID"
    GITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET"
    GITHUB_PRIVATE_KEY="YOUR_PRIVATE_KEY"
    
  5. Accédez à la page Paramètres de votre application :

    1. Dans le coin supérieur droit de n’importe quelle page sur GitHub, cliquez sur votre photo de profil.

    2. Accédez aux paramètres de votre compte.

      • Pour une GitHub App appartenant à un compte personnel, cliquez sur Paramètres.
      • Pour une GitHub App appartenant à une organisation :
        1. Cliquez sur Vos organisations.
        2. À droite de l’organisation, cliquez sur Paramètres.
    3. Dans la barre latérale gauche, cliquez sur Paramètres de développeur.

    4. Dans la barre latérale à gauche, cliquez sur GitHub Apps .

    5. En regard du nom de votre application, cliquez sur Modifier.

  6. Dans la page des paramètres de votre application, en regard de « ID d’application », recherchez l’ID d’application de votre application.

  7. Dans votre fichier, .env remplacez YOUR_APP_ID par l’ID d’application de votre application.

  8. Dans votre fichier .env, remplacez YOUR_WEBHOOK_SECRET par le secret webhook de votre application. Si vous avez oublié votre secret webhook, sous « Secret webhook (facultatif) », cliquez sur Modifier le secret. Entrez un nouveau secret, puis cliquez sur Enregistrer les modifications.

  9. Dans la page des paramètres de votre application, sous « Clés privées », cliquez sur Générer une clé privée. Un fichier .pem de clé privée est téléchargée sur votre ordinateur.

  10. Ouvrez le fichier .pem avec un éditeur de texte ou utilisez la commande suivante sur la ligne de commande pour afficher le contenu du fichier : cat PATH/TO/YOUR/private-key.pem.

  11. Copiez et collez l’intégralité du contenu du fichier dans votre fichier .env en tant que valeur de GITHUB_PRIVATE_KEY, puis ajoutez des guillemets doubles autour de la valeur entière.

    Voici un exemple de fichier .env :

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

Ajouter du code pour votre GitHub App

Cette section vous montre comment ajouter du code de modèle de base pour votre GitHub App, et explique ce que fait le code. Plus loin dans le tutoriel, vous allez apprendre à modifier et à ajouter à ce code, pour générer les fonctionnalités de votre application.

Ajoutez le code du modèle suivant à votre fichier 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 an not altererd 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

Le reste de cette section explique ce que fait le code du modèle. Vous ne devez effectuer aucune étape dans cette section. Si vous êtes déjà familiarisé avec le code du modèle, vous pouvez passer à la section « Démarrer le serveur ».

Comprendre le code du modèle

Ouvrez le fichier server.rb dans un éditeur de texte. Des commentaires dans le fichier fournissent un contexte supplémentaire pour le code du modèle. Nous vous recommandons de lire ces commentaires attentivement et même d’ajouter vos propres commentaires pour accompagner le nouveau code que vous écrivez.

Sous la liste des fichiers obligatoires, le premier code que vous voyez est la déclaration class GHApp < Sinatra::Application. Vous allez écrire tout le code de votre GitHub App dans cette classe. Les sections suivantes expliquent en détail ce que fait le code à l’intérieur de cette classe.

Définir le port

La première chose que vous voyez à l’intérieur de la déclaration class GHApp < Sinatra::Application est set :port 3000. Cela définit le port utilisé lors du démarrage du serveur web pour qu’il corresponde au port vers lequel vous avez redirigé les charges utiles de votre webhook dans « Obtenir une URL de proxy webhook ».

  # Sets the port that's used when starting the web server.
  set :port, 3000
  set :bind, '0.0.0.0'

Lire les variables d’environnement

Ensuite, cette classe lit les trois variables d’environnement que vous avez définies dans « Stocker les informations d’identification et les informations de connexion de votre application » et les stocke dans des variables à utiliser ultérieurement.

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

Activation de la journalisation

Voici un bloc de code qui active la journalisation pendant le développement. Il s’agit de l’environnement par défaut dans Sinatra. Ce code active la journalisation au niveau DEBUG pour afficher une sortie utile dans le terminal pendant le développement de l’application.

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

Définir un filtre before

Sinatra utilise des filtres before qui vous permettent d’exécuter du code avant le gestionnaire de routage. Le bloc before du modèle appelle quatre méthodes d’assistance : get_payload_request, verify_webhook_signature, authenticate_app et authenticate_installation. Pour plus d’informations, consultez « Filtres » et « Assistances » dans la documentation de 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

Chacune de ces méthodes d’assistance est définie plus loin dans le code, dans le bloc de code qui commence par helpers do. Pour plus d’informations, consultez « Définir les méthodes d’assistance ».

Sous verify_webhook_signature, le code qui commence par unless @payload est une mesure de sécurité. Si un nom de dépôt est fourni avec une charge utile de webhook, ce code vérifie que le nom du dépôt contient uniquement des caractères alphabétiques latins, des traits d’union et des traits de soulignement. Cela permet de s’assurer qu’un acteur malveillant ne tente pas d’exécuter des commandes arbitraires ou d’injecter de faux noms de dépôt. Plus tard, dans le bloc de code qui commence par helpers do, la méthode d’assistance verify_webhook_signature valide également les charges utiles de webhook entrantes en tant que mesure de sécurité supplémentaire.

Définir un gestionnaire de routage

Un itinéraire vide est inclus dans le code du modèle. Ce code gère toutes les demandes POST vers l’itinéraire /event_handler. Vous y ajouterez plus de code ultérieurement.

post '/event_handler' do

end

Définir les méthodes d’assistance

Quatre méthodes d’assistance sont appelées dans le bloc before du code du modèle. Le bloc de code helpers do définit chacune de ces méthodes d’assistance.

Gestion de la charge utile de webhook

La première méthode d’assistance get_payload_request capture la charge utile de webhook et la convertit au format JSON, ce qui facilite beaucoup l’accès aux données de la charge utile.

Vérification de la signature du webhook

La deuxième méthode d’assistance verify_webhook_signature effectue la vérification de la signature webhook pour s’assurer que GitHub a généré l’événement. Pour en savoir plus sur le code de la méthode d’assistance verify_webhook_signature, consultez « Validating webhook deliveries ». Si les webhooks sont sécurisés, cette méthode journalise toutes les charges utiles entrantes sur votre terminal. Le code d’enregistreur d’événements est utile pour vérifier que votre serveur web fonctionne.

Authentification en tant qu’GitHub App

La troisième méthode d’assistance authenticate_app permet à votre GitHub App de s’authentifier, afin qu’elle puisse demander un jeton d’installation.

Pour effectuer des appels d’API, vous allez utiliser la bibliothèque Octokit. Pour faire quelque chose d’intéressant avec cette bibliothèque, votre GitHub App doit s’authentifier. Pour plus d’informations sur la bibliothèque Octokit, consultez la documentation Octokit.

Les GitHub Apps ont trois méthodes d’authentification :

  • Authentification en tant qu’GitHub App avec un jeton JWT (JSON Web Token).
  • Authentification en tant qu’installation spécifique d’une GitHub App avec un jeton d’accès d’installation.
  • Authentification pour le compte d’un utilisateur. Ce tutoriel n’utilise pas cette méthode d’authentification.

Vous découvrirez l’authentification en tant qu’installation dans la section suivante, « Authentification en tant qu’installation ».

L’authentification en tant qu’GitHub App vous permet de faire plusieurs choses :

  • Vous pouvez récupérer des informations de gestion générales concernant votre GitHub App.
  • Vous pouvez demander des jetons d’accès pour une installation de l’application.

Par exemple, vous vous authentifiez en tant qu’GitHub App pour récupérer une liste des comptes (organisation et personnel) qui ont installé votre application. Mais cette méthode d’authentification offre peu de possibilités avec l’API. Pour accéder aux données d’un référentiel et effectuer des opérations pour le compte de l’installation, vous devez vous authentifier en tant qu’installation. Pour ce faire, vous devez d’abord vous authentifier en tant qu’GitHub App pour demander un jeton d’accès d’installation. Pour plus d’informations, consultez « À propos de l’authentification avec une application GitHub ».

Avant de pouvoir utiliser la bibliothèque Octokit.rb pour effectuer des appels d’API, vous devez initialiser un client Octokit authentifié en tant qu’GitHub App, en utilisant la méthode d’assistance 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

Le code ci-dessus génère un jeton JSON Web Token (JWT) et l’utilise (ainsi que la clé privée de votre application) pour initialiser le client Octokit. GitHub vérifie l’authentification d’une demande en vérifiant le jeton avec la clé publique stockée de l’application. Pour en savoir plus sur le fonctionnement de ce code, consultez « Génération d’un jeton web JSON (JWT) pour une application GitHub ».

Authentification en tant qu’installation

La quatrième et dernière méthode d’assistance, authenticate_installation, initialise un client Octokit authentifié en tant qu’installation, que vous pouvez utiliser pour effectuer des appels authentifiés à l’API.

Une installation fait référence à tout compte d’utilisateur ou d’organisation ayant installé l’application. Même si un utilisateur accorde à l’application l’accès à plusieurs dépôts sur ce compte, cela compte comme une seule installation, car il s’agit du même compte.

# 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

La méthode Octokit create_app_installation_access_token crée un jeton d’installation. Pour plus d’informations, consultez « create_installation_access_token » dans la documentation Octokit.

Cette méthode accepte deux arguments :

  • Installation (entier) : ID de l’installation d’une GitHub App
  • Options (hachage, valeur par défaut : {}) : ensemble personnalisable d’options

Chaque fois qu’une GitHub App reçoit un webhook, elle inclut un objet installation avec un id. En utilisant le client authentifié en tant qu’GitHub App, vous transmettez cet ID à la méthode create_app_installation_access_token afin de générer un jeton d’accès pour chaque installation. Étant donné que vous ne transmettez aucune option à la méthode, les options sont par défaut un hachage vide. La réponse pour create_app_installation_access_token comprend deux champs : token et expired_at. Le code de modèle sélectionne le jeton dans la réponse et initialise un client d’installation.

Avec cette méthode en place, chaque fois que votre application reçoit une nouvelle charge utile webhook, elle crée un client pour l’installation qui a déclenché l’événement. Ce processus d’authentification permet à votre GitHub App de fonctionner sur toutes les installations de n’importe quel compte.

Démarrer le serveur

Votre application n’effectue aucune opération pour le moment. Toutefois, vous pouvez l’exécuter sur le serveur à ce stade.

  1. Dans votre terminal, vérifiez que Smee est toujours en cours d’exécution. Pour plus d’informations, consultez « Obtenir une URL de proxy webhook ».

  2. Ouvrez un nouvel onglet dans votre terminal, puis accédez avec cd au répertoire dans lequel vous avez cloné le dépôt que vous avez créé dans le tutoriel. Pour plus d’informations, consultez « Créer un dépôt pour stocker le code de votre application GitHub ». Le code Ruby dans ce référentiel démarre un serveur web Sinatra.

  3. Installez les dépendances en exécutant les deux commandes suivantes l’une après l’autre :

    Shell
    gem install bundler
    
    Shell
    bundle install
    
  4. Après avoir installé les dépendances, démarrez le serveur en exécutant cette commande :

    Shell
    bundle exec ruby server.rb
    

    La réponse doit ressembler à ceci :

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

    En cas d’erreur, vérifiez que vous avez créé le fichier .env dans le répertoire qui contient server.rb.

  5. Pour tester le serveur, accédez dans votre navigateur à http://localhost:3000.

    Si vous voyez une page d’erreur indiquant « Sinatra doesn't know this ditty », l’application fonctionne comme prévu. Il s’agit d’une page d’erreur, mais c’est une page d’erreur Sinatra. Cela signifie que votre application est connectée au serveur comme prévu. Ce message s’affiche parce que vous n’avez fourni à l’application aucun élément à afficher.

Tester que le serveur écoute votre application

Vous pouvez tester que le serveur écoute votre application en déclenchant un événement qu’il doit recevoir. Pour ce faire, installez l’application sur un dépôt de test, qui envoie l’événement installation à votre application. Si l’application le reçoit, une sortie doit s’afficher sous l’onglet de terminal où vous exécutez server.rb.

  1. Créez un dépôt à utiliser pour tester le code de votre tutoriel. Pour plus d’informations, consultez « Création d’un dépôt ».

  2. Installez l’GitHub App sur le dépôt que vous venez de créer. Pour plus d’informations, consultez « Installation de votre propre application GitHub ». Pendant le processus d’installation, choisissez Sélectionner uniquement les dépôts, puis sélectionnez le dépôt que vous avez créé à l’étape précédente.

  3. Après avoir cliqué sur Installer, examinez la sortie sous l’onglet de terminal où vous exécutez server.rb. Un résultat semblable à celui-ci doit s’afficher :

    > 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
    

    Si vous voyez une sortie comme celle-ci, cela signifie que votre application a reçu une notification indiquant qu’elle a été installée sur votre compte GitHub. L’application s’exécute sur le serveur comme prévu.

    Si cette sortie ne s’affiche pas, assurez-vous que Smee s’exécute correctement dans un autre onglet de terminal. Si vous devez redémarrer Smee, notez que vous devez également désinstaller puis réinstaller l’application pour envoyer à nouveau l’événement installation à votre application et voir la sortie dans le terminal.

Si vous vous demandez d’où provient la sortie de terminal ci-dessus, elle est écrite dans le code de modèle d’application que vous avez ajouté à server.rb dans la section « Ajouter du code pour votre GitHub App ».

Partie 1. Création de l’interface de l’API Vérifications

Dans cette partie, vous allez ajouter le code nécessaire pour recevoir des événements de webhook check_suite, puis créer et mettre à jour des exécutions de vérification. Vous allez également apprendre à créer des exécutions de vérification quand une vérification a été redemandée sur GitHub. À la fin de cette section, vous serez en mesure d’afficher l’exécution de vérification que vous avez créée dans une demande de tirage GitHub.

Votre exécution de vérification n’effectue aucune vérification du code dans cette section. Vous allez ajouter cette fonctionnalité dans la « Partie 2 : Création du test de CI Octo RuboCop ».

Vous devriez déjà disposer d’un canal Smee configuré qui transfère des charges utiles de webhook à votre serveur local. Votre serveur devrait être en cours d’exécution et connecté à l’GitHub App que vous avez inscrite et installée sur un dépôt de test.

Voici les étapes que vous allez suivre dans la Partie 1 :

  1. Ajouter la gestion des événements
  2. Créer une exécution de vérifications
  3. Mettre à jour une exécution de vérifications

Étape 1.1. Ajouter la gestion des événements

Votre application étant abonnée aux événements Suite de vérifications et Exécution de vérification, elle va recevoir les webhooks check_suite et check_run. GitHub envoie les charges utiles de webhook sous forme de demandes POST. Étant donné que vous avez transféré vos charges utiles de webhook Smee à http://localhost:3000/event_handler, votre serveur recevra les charges utiles de demande POST dans l’itinéraire post '/event_handler'.

Ouvrez le fichier server.rb que vous avez créé dans « Ajouter du code pour votre GitHub App », puis recherchez le code suivant. Une route post '/event_handler' vide est déjà incluse dans le code du modèle. Voici comment se présente l’itinéraire vide :

  post '/event_handler' do

    # ADD EVENT HANDLING HERE #

    200 # success status
  end

Dans le bloc de code qui commence par post '/event_handler' do, où il est indiqué # ADD EVENT HANDLING HERE #, ajoutez le code suivant. Cette route gère l’événement 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

Chaque événement que GitHub envoie inclut un en-tête de demande appelé HTTP_X_GITHUB_EVENT, qui indique le type d’événement dans la demande POST. En ce moment précis, vous ne vous intéressez qu’aux événements de type check_suite, qui sont émis lors de la création d’une suite de vérifications. Chaque événement a un champ action supplémentaire qui indique le type d’action ayant déclenché les événements. Pour check_suite, le champ action peut être requested, rerequested ou completed.

L’action requested demande une exécution de vérification chaque fois que du code est envoyé (push) au dépôt, tandis que l’action rerequested demande que vous réexécutiez une vérification du code existant déjà dans le dépôt. Étant donné que les actions requested et rerequested nécessitent toutes deux la création d’une exécution de vérification, vous allez appeler une assistance appelée create_check_run. Écrivons cette méthode maintenant.

Étape 1.2. Créer une exécution de vérifications

Vous allez ajouter cette nouvelle méthode en tant qu’assistance Sinatra au cas où vous voudriez que d’autres itinéraires l’utilisent également.

Dans le bloc de code qui commence par helpers do, où il est indiqué # ADD CREATE_CHECK_RUN HELPER METHOD HERE #, ajoutez le code suivant :

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

Ce code appelle le point de terminaison « Vérifications » à l’aide de la méthode create_check_run d’Octokit.

Pour créer une exécution de vérification, seuls deux paramètres d’entrée sont requis : name et head_sha. Dans ce code, nous nommons l’exécution de vérification « Octo RuboCop », car nous utiliserons RuboCop pour implémenter le test de CI plus loin dans le tutoriel. Toutefois, vous pouvez choisir n’importe quel nom pour l’exécution de vérification. Pour plus d’informations sur RuboCop, consultez la documentation RuboCop.

Pour l’instant, vous ne fournissez que les paramètres requis pour que la fonctionnalité de base opère, mais vous allez mettre à jour l’exécution de vérification plus tard, à mesure que vous recueillerez plus d’informations sur celle-ci. Par défaut, GitHub définit status sur queued.

GitHub créant une exécution de vérification pour un SHA de validation spécifique, le paramètre head_sha est requis. Vous pouvez trouver le SHA de validation dans la charge utile du webhook. Même si vous ne créez pour l’instant qu’une exécution de vérification pour l’événement check_suite, il est bon de savoir que le head_sha est inclus dans les objets check_suite et check_run dans les charges utiles de l’événement.

Le code ci-dessus utilise un opérateur ternaire, qui fonctionne comme une instruction if/else, pour vérifier si la charge utile contient un objet check_run. Si c’est le cas, vous lisez le head_sha à partir de l’objet check_run. Sinon, vous l’avez lu à partir de l’objet check_suite.

Tester le code

Les étapes suivantes vous montrent comment tester que le code fonctionne et qu’il crée correctement une exécution de vérification.

  1. Exécutez la commande suivante pour redémarrer le serveur à partir de votre terminal. Si le serveur est déjà en cours d’exécution, entrez Ctrl-C dans votre terminal pour arrêter le serveur, puis exécutez la commande suivante pour redémarrer le serveur.

    Shell
    ruby server.rb
    
  2. Créez une demande de tirage dans le dépôt de test que vous avez créé dans « Tester que le serveur écoute votre application ». Il s’agit du dépôt auquel vous avez accordé l’accès à l’application.

  3. Dans la demande de tirage que vous avez créée, accédez à l’onglet Vérifications. Vous devriez voir une exécution de vérification avec le nom « Octo RuboCop » ou le nom que vous avez éventuellement choisi pour la vérification.

Si vous voyez d’autres applications sous l’onglet Vérifications, cela signifie que vous disposez d’autres applications installées sur votre dépôt, qui disposent d’un accès en lecture et écriture aux vérifications, et sont abonnées aux événements Suite de vérifications et Exécution de vérification. Cela peut également signifier que vous avez des workflows GitHub Actions sur le dépôt qui sont déclenchés par l’événement pull_request ou pull_request_target.

Jusqu’à présent, vous avez demandé à GitHub de créer une exécution de vérification. L’état de l’exécution de vérification dans la demande de tirage est défini sur mise en file d’attente avec une icône jaune. À l’étape suivante, vous attendez que GitHub crée l’exécution de vérification et mette à jour son état.

Étape 1.3. Mettre à jour une exécution de vérifications

Quand votre méthode create_check_run s’exécute, elle demande à GitHub de créer une exécution de vérification. Quand GitHub termine la création de l’exécution de vérification, vous recevez l’événement de webhook check_run avec l’action created. Cet événement est votre signal pour commencer à exécuter la vérification.

Vous allez mettre à jour votre gestionnaire d’événements pour rechercher l’action created. Pendant que vous mettez à jour le gestionnaire d’événements, vous pouvez ajouter une condition pour l’action rerequested. Quand quelqu’un réexécute un seul test sur GitHub en cliquant sur le bouton « Réexécuter », GitHub envoie l’événement d’exécution de vérification rerequested à votre application. Quand une exécution de vérification est rerequested, vous recommencez le processus et créez une exécution de vérification. Pour ce faire, vous incluez une condition pour l’événement check_run dans la route post '/event_handler'.

Dans le bloc de code qui commence par post '/event_handler' do, où il est indiqué # ADD CHECK_RUN METHOD HERE #, ajoutez le code suivant :

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 envoie tous les événements pour les exécutions de vérification created à chaque application installée sur un dépôt disposant des autorisations de vérification nécessaires. Cela signifie que votre application recevra des exécutions de vérification créées par d’autres applications. Une exécution de vérification created diffère légèrement d’une suite de vérifications requested ou rerequested, que GitHub envoie uniquement aux applications qui doivent exécuter une vérification. Le code ci-dessus recherche l’ID d’application de l’exécution de vérification. Cela filtre toutes les exécutions de vérification pour d’autres applications sur le dépôt.

Ensuite, vous allez écrire la méthode initiate_check_run, qui est l’endroit où vous allez mettre à jour l’état de la vérification d’exécution et préparer le lancement de votre test de CI.

Dans cette section, vous n’allez pas encore lancer le test de CI, mais découvrir comment mettre à jour l’état de l’exécution de vérification de queued à pending, puis de pending à completed voir le flux global d’une exécution de vérification. Dans la « Partie 2 : Création du test de CI Octo RuboCop », vous allez ajouter le code qui effectue réellement le test de CI.

Nous allons créer la méthode initiate_check_run et mettre à jour l’état de l’exécution de vérification.

Dans le bloc de code qui commence par helpers do, où il est indiqué # ADD INITIATE_CHECK_RUN HELPER METHOD HERE #, ajoutez le code suivant :

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

Le code ci-dessus appelle le point de terminaison « Mettre à jour une exécution de vérification » à l’aide de la méthode Octokit update_check_run et met à jour l’exécution de vérification que vous avez déjà créée.

Examinons ce que fait ce code. Tout d’abord, il met à jour l’état de l’exécution de vérification en in_progress, et définit implicitement l’heure started_at sur l’heure actuelle. Dans la Partie 2 de ce tutoriel, vous allez ajouter du code qui lance un test de CI réel sous ***** RUN A CI TEST *****. Pour l’instant, vous allez laisser cette section comme un espace réservé, de sorte que le code qui le suit simulera simplement le fait que le processus de CI réussit, ainsi que tous les tests. Enfin, le code remet à jour l’état de l’exécution de vérification sur completed.

Quand vous utilisez l’API REST pour fournir l’état d’exécution de vérification completed, les paramètres conclusion et completed_at sont requis. conclusion récapitule le résultat d’une exécution de vérification et peut être success, failure, neutral, cancelled, timed_out, skipped ou action_required. Vous allez définir la conclusion sur success, l’heure completed_at sur l’heure actuelle, et l’état sur completed.

Vous pouvez également fournir des informations supplémentaires l’action de votre vérification, mais vous y reviendrez dans la section suivante.

Tester le code

Les étapes suivantes vous montrent comment tester le fonctionnement du code et du bouton « Réexécuter tout » que vous avez créé.

  1. Exécutez la commande suivante pour redémarrer le serveur à partir de votre terminal. Si le serveur est déjà en cours d’exécution, entrez Ctrl-C dans votre terminal pour arrêter le serveur, puis exécutez la commande suivante pour redémarrer le serveur.

    Shell
    ruby server.rb
    
  2. Créez une demande de tirage dans le dépôt de test que vous avez créé dans « Tester que le serveur écoute votre application ». Il s’agit du dépôt auquel vous avez accordé l’accès à l’application.

  3. Dans la demande de tirage que vous venez de créer, accédez à l’onglet Vérifications. Vous devriez voir un bouton « Réexécuter tout ».

  4. Cliquez sur le bouton « Réexécuter tout » en haut à droite. Le test doit se réexécuter et se terminer par success.

Partie 2. Création d’un test de CI

Maintenant que vous avez l’interface créée pour recevoir des événements d’API et créer des exécutions de vérification, vous pouvez créer une exécution de vérification qui implémente un test de CI.

RuboCop est un linter et formateur de code Ruby. Il vérifie le code Ruby pour s’assurer qu’il est conforme au Guide de style Ruby. Pour plus d’informations, consultez la documentation RuboCop.

RuboCop à trois fonctions principales :

  • Linting pour vérifier le style de code
  • Mise en forme du code
  • Remplace les fonctionnalités de linting Ruby natives en utilisant ruby -w

Votre application exécute RuboCop sur le serveur de CI et crée des exécutions de vérification (tests de CI dans ce cas) qui rapportent les résultats que RuboCop rapporte à GitHub.

L’API REST vous permet de rapporter des détails riches sur chaque exécution de vérification, dont des états, des images, des résumés, des annotations et des actions demandées.

Les annotations sont des informations sur des lignes de code spécifiques dans un dépôt. Une annotation vous permet d’identifier et de visualiser les parties exactes du code pour lesquelles vous souhaitez afficher des informations supplémentaires. Par exemple, vous pouvez afficher ces informations sous la forme d’un commentaire, d’une erreur ou d’un avertissement sur une ligne de code spécifique. Ce tutoriel utilise des annotations pour visualiser des erreurs de RuboCop.

Pour tirer parti des actions demandées, les développeurs d’applications peuvent créer des boutons sous l’onglet Vérifications des demandes d’extraction. Quand quelqu’un clique sur l’un de ces boutons, le clic envoie un événement requested_action check_run à l’GitHub App. La mesure de l’application prend est entièrement configurable par son développeur. Ce tutoriel vous guidera pour ajouter un bouton permettant aux utilisateurs de demander que RuboCop corrige les erreurs qu’il trouve. RuboCop prend en charge la correction automatique des erreurs à l’aide d’une option de ligne de commande, et vous allez configurer la requested_action pour tirer parti de cette option.

Voici les étapes que vous allez suivre dans cette section :

  1. Ajouter un fichier Ruby
  2. Cloner le dépôt
  3. Exécuter RuboCop
  4. Collecter les erreurs de RuboCop
  5. Mettre à jour l’exécution de vérification avec les résultats de test de CI
  6. Corriger automatiquement les erreurs de RuboCop

Étape 2.1. Ajouter un fichier Ruby

Vous pouvez transmettre des fichiers spécifiques ou des répertoires entiers pour vérification par RuboCop. Dans ce tutoriel, vous allez exécuter RuboCop sur un répertoire entier. RuboCop vérifie uniquement le code Ruby. Pour tester votre GitHub App, vous devez ajouter un fichier Ruby dans votre dépôt qui contient des erreurs que RuboCop doit rechercher. Après avoir ajouté le fichier Ruby suivant à votre dépôt, vous allez mettre à jour votre vérification de CI pour exécuter RuboCop sur le code.

  1. Accédez au dépôt de test que vous avez créé dans « Tester que le serveur écoute votre application ». Il s’agit du dépôt auquel vous avez accordé l’accès à l’application.

  2. Créez un nouveau fichier appelé myfile.rb. Pour plus d’informations, consultez « Création de fichiers ».

  3. Ajoutez le contenu suivant à 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. Si vous avez créé le fichier localement, veillez à le commiter et à le pousser vers votre dépôt sur GitHub.

Étape 2.2. Autoriser RuboCop à cloner le dépôt de test

RuboCop est disponible en tant qu’utilitaire de ligne de commande. Cela signifie que si vous souhaitez exécuter RuboCop sur un dépôt, votre GitHub App doit cloner une copie locale du dépôt sur le serveur de CI afin que RuboCop puisse analyser les fichiers. Pour ce faire, votre code doit être en mesure d’exécuter des opérations Git, et votre GitHub App doit disposer des autorisations appropriées pour cloner un dépôt.

Autoriser les opérations Git

Pour exécuter des opérations Git dans votre application Ruby, vous pouvez utiliser la gemme ruby-git. Le Gemfile que vous avez créé dans « Configuration » inclut déjà la gemme ruby-git, et vous l’avez installé lors de l’exécution de bundle install dans « Démarrer le serveur ».

Maintenant, en haut de votre fichier server.rb, sous les autres éléments require, ajoutez le code suivant :

Ruby
require 'git'

Mettre à jour les autorisations de votre application

Ensuite, vous devez mettre à jour les autorisations de votre GitHub App. Pour cloner un dépôt, votre application a besoin d’une autorisation de lecture pour « Contenu ». Plus loin dans ce tutoriel, elle aura besoin d’une autorisation d’écriture pour pousser du contenu vers GitHub. Pour mettre à jour les autorisations de votre application :

  1. Sélectionnez votre application dans la page des paramètres de l’application, puis cliquez sur Autorisations et événements dans la barre latérale.
  2. Sous « Autorisations du dépôt », en regard de « Contenu », sélectionnez Lire et écrire.
  3. Cliquez sur Enregistrer les changements au bas de la page.
  4. Si vous avez installé l’application sur votre compte, vérifiez votre adresse e-mail et suivez le lien pour accepter les nouvelles autorisations. Chaque fois que vous modifiez les autorisations ou les webhooks de votre application, les utilisateurs qui ont installé l’application doivent accepter les nouvelles autorisations avant que les changements ne prennent effet (y compris vous-même). Vous pouvez aussi accepter les nouvelles autorisations en accédant à la page de vos installations. Un lien apparaît sous le nom de l’application vous informant que celle-ci demande des autorisations différentes. Cliquez sur « Examiner la demande », puis sur « Accepter les nouvelles autorisations ».

Ajouter du code pour cloner un dépôt

Pour cloner un dépôt, le code utilise les autorisations de votre GitHub App et le Kit de développement logiciel (SDK) Octokit pour créer un jeton d’installation pour votre application (x-access-token:TOKEN) et l’utiliser dans la commande clone suivante :

git clone https://x-access-token:TOKEN@github.com/OWNER/REPO.git

La commande ci-dessus clone un dépôt via HTTP. Il requiert le nom complet du dépôt, qui inclut le propriétaire du dépôt (utilisateur ou organisation) et le nom du dépôt. Par exemple, le dépôt octocat Hello-World porte le nom complet octocat/hello-world.

Ouvrez votre fichier server.rb. Dans le bloc de code qui commence par helpers do, où il est indiqué # ADD CLONE_REPOSITORY HELPER METHOD HERE #, ajoutez le code suivant :

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

Le code ci-dessus utilise la gemme ruby-git pour cloner le dépôt à l’aide du jeton d’installation de l’application. Il clone le code dans le même répertoire que server.rb. Pour exécuter des commandes Git dans le dépôt, le code doit changer dans le répertoire du dépôt. Avant de modifier des répertoires, le code stocke le répertoire de travail actif dans une variable (pwd) afin de se rappeler où retourner avant de quitter la méthode clone_repository.

Dans le répertoire du dépôt, ce code extrait et fusionne les dernières modifications (@git.pull) et extrait la référence Git spécifique (@git.checkout(ref)). Le code permettant d’effectuer tout cela s’intègre parfaitement dans sa propre méthode. Pour effectuer ces opérations, la méthode a besoin du nom et du nom complet du dépôt, ainsi que de la référence pour la validation. La référence peut être un SHA, une branche ou une étiquette de validation. Quand l’opération est terminée, le code bascule vers le répertoire de travail d’origine (pwd).

Vous disposez maintenant d’une méthode qui clone un dépôt et valide une référence. Ensuite, vous devez ajouter du code pour obtenir les paramètres d’entrée requis et appeler la nouvelle méthode clone_repository.

Dans le bloc de code qui commence par helpers do, dans la méthode d’assistance initiate_check_run où il est indiqué # ***** RUN A CI TEST *****, ajoutez le code suivant :

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 #

Le code ci-dessus obtient le nom complet du dépôt et le SHA principal de la validation à partir de la charge utile du webhook check_run.

Étape 2.3. Exécuter RuboCop

Jusqu’à présent, votre code clone le dépôt et crée des exécutions de vérification avec votre serveur de CI. Vous allez maintenant entrer dans les détails du linter RuboCop et des annotations de vérifications.

Tout d’abord, vous allez ajouter du code pour exécuter RuboCop et enregistrer les erreurs de code de style au format JSON.

Dans le bloc de code qui commence par helpers do, recherchez la méthode d’assistance initiate_check_run. À l’intérieur de cette méthode d’assistance, sous clone_repository(full_repo_name, repository, head_sha), où il est indiqué # ADD CODE HERE TO RUN RUBOCOP #, ajoutez le code suivant :

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 #

Le code au-dessus exécute RuboCop sur tous les fichiers du répertoire du dépôt. L’option --format json enregistre une copie des résultats de linting dans un format analysable par une machine. Pour plus d’informations et un exemple de format JSON, consultez « Formateur JSON » dans la documentation RuboCop. Ce code analyse également le code JSON afin que vous puissiez facilement accéder aux clés et aux valeurs de votre GitHub App en utilisant la variable @output.

Après avoir exécuté RuboCop et enregistré les résultats de linting, ce code exécute la commande rm -rf pour supprimer l’extraction du dépôt. Comme le code stocke les résultats de RuboCop dans une variable @report, il peut supprimer la validation du dépôt de façon sécurisée.

La commande rm -rf ne peut pas être annulée. Pour assurer la sécurité de votre application, le code de ce tutoriel vérifie les webhooks entrants à la recherche de commandes malveillantes injectées qui pourraient être utilisées pour supprimer un répertoire différent de celui prévu par votre application. Par exemple, si un acteur mal intentionné envoyait un webhook avec le nom de dépôt ./, votre application supprimerait le répertoire racine. La méthode verify_webhook_signature valide l’expéditeur du webhook. Le gestionnaire d’événements verify_webhook_signature vérifie également que le nom du dépôt est valide. Pour plus d’informations, consultez « Définir un filtre before ».

Tester le code

Les étapes suivantes vous montrent comment tester le fonctionnement du code et afficher les erreurs signalées par RuboCop.

  1. Exécutez la commande suivante pour redémarrer le serveur à partir de votre terminal. Si le serveur est déjà en cours d’exécution, entrez Ctrl-C dans votre terminal pour arrêter le serveur, puis exécutez la commande suivante pour redémarrer le serveur.

    Shell
    ruby server.rb
    
  2. Dans le dépôt dans lequel vous avez ajouté le fichier myfile.rb, créez une demande de tirage.

  3. Dans l’onglet de votre terminal où le serveur est en cours d’exécution, vous devez voir la sortie de débogage qui contient des erreurs de linting. Les erreurs de linting sont affichées sans aucune mise en forme. Vous pouvez copier et coller votre sortie de débogage dans un outil web tel que le formateur JSON, pour mettre en forme votre sortie JSON comme dans l’exemple suivant :

    {
      "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
      }
    }
    

Étape 2.4. Collecter les erreurs de RuboCop

La variable @output contient les résultats JSON analysés du rapport de RuboCop. Comme indiqué dans l’exemple de sortie à l’étape précédente, les résultats contiennent une section summary que votre code peut utiliser pour déterminer rapidement s’il y a des erreurs. Le code suivant définira la conclusion de l’exécution de vérification sur success si aucune erreur n’est signalée. RuboCop signale des erreurs pour chaque fichier figurant dans le tableau files. Par conséquent, s’il y a des erreurs, vous devez extraire des données de l’objet fichier.

Les points de terminaison de l’API REST disponibles pour gérer les exécutions de vérification vous permettent de créer des annotations pour des lignes de code spécifiques. Lorsque vous créez ou mettez à jour une exécution de vérification, vous pouvez ajouter des annotations. Dans ce tutoriel, vous allez mettre à jour l’exécution de vérification avec des annotations, en utilisant le point de terminaison « Mettre à jour une exécution de vérification ».

L’API limite le nombre d’annotations à un maximum de 50 par demande. Pour créer plus de 50 annotations, vous devez adresser plusieurs demandes au point de terminaison « Mettre à jour une exécution de vérification ». Par exemple, pour créer 105 annotations, vous devez adresser trois demandes distinctes à l’API. Les deux premières demandes auraient chacune 50 annotations, et la troisième inclurait les cinq annotations restantes. Chaque fois que vous mettez à jour l’exécution de vérification, des annotations sont ajoutées à la liste des annotations existantes pour l’exécution de vérification.

Une exécution de vérification attend des annotations sous la forme d’un tableau d’objets. Chaque objet d’annotation doit inclure les éléments path, start_line, end_line, annotation_level et message. RuboCop fournissant également les paramètres start_column et end_column, vous pouvez inclure ces paramètres facultatifs dans l’annotation. Les annotations ne prennent en charge les paramètres start_column et end_column que sur la même ligne. Pour plus d’informations, consultez l’objet annotations dans « Exécutions de vérifications ».

Vous allez maintenant ajouter du code pour extraire les informations requises de RuboCop qui sont nécessaires pour créer chaque annotation.

Sous le code que vous avez ajouté à l’étape précédente, où il est indiqué # ADD ANNOTATIONS CODE HERE #, ajoutez le code suivant :

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 #

Ce code limite le nombre total d’annotations à 50. Toutefois, vous pouvez le modifier pour mettre à jour l’exécution de vérification pour chaque lot de 50 annotations. Le code ci-dessus inclut la variable max_annotations qui définit la limite de 50 utilisée dans la boucle qui itère dans les attaques.

Lorsque la valeur offense_count est zéro, le résultat du test de CI est success. S’il y a des erreurs, ce code définit la conclusion sur neutral afin d’empêcher l’application stricte d’erreurs à partir de linters de code. Toutefois, vous pouvez modifier la conclusion en failure si vous souhaitez vous assurer que la suite de vérifications échoue en cas d’erreurs de linting.

Lorsque des erreurs sont signalées, le code ci-dessus itère sur le tableau files dans le rapport de RuboCop. Pour chaque fichier, il extrait le chemin d’accès du fichier et définit le niveau d’annotation sur notice. Vous pourriez aller encore plus loin et définir des niveaux d’avertissement spécifiques pour chaque type de RuboCop Cop mais, pour simplifier les choses dans ce tutoriel, toutes les erreurs sont définies à un niveau de notice.

Ce code itère également sur chaque erreur dans le tableau offenses et collecte l’emplacement de l’attaque et du message d’erreur. Après avoir extrait les informations nécessaires, le code crée une annotation pour chaque erreur et la stocke dans le tableau annotations. En effet, les annotations ne prennent en charge les colonnes de début et de fin que sur la même ligne, et les paramètres start_column et end_column ne sont ajoutés à l’objet annotation que si les valeurs de ligne de début et de fin sont identiques.

Ce code ne crée pas encore d’annotation pour l’exécution de vérification. Vous allez ajouter ce code dans la section suivante.

Étape 2.5. Mettre à jour l’exécution de vérification avec les résultats de test de CI

Chaque exécution de vérification de GitHub contient un objet output qui inclut des éléments title, summary, text, annotations et images. Étant donné que les paramètres summary et title sont les seuls requis pour le output, mais qu’ils ne fournissent pas beaucoup de détails, ce tutoriel ajoute également text et annotations.

Pour le summary, cet exemple utilise les informations résumées de RuboCop, et ajoute de nouvelles lignes (\n) pour mettre en forme la sortie. Vous pouvez personnaliser ce que vous ajoutez au text paramètre, mais cet exemple définit le text paramètre sur la version RuboCop. Le code suivant définit les summary et text.

Sous le code que vous avez ajouté à l’étape précédente, où il est indiqué # ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #, ajoutez le code suivant :

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

Votre code doit maintenant contenir toutes les informations nécessaires pour mettre à jour votre exécution de vérification. Dans « Étape 1.3. Mettre à jour une exécution de vérification », vous avez ajouté du code pour définir l’état de l’exécution de vérification sur success. Vous devez mettre à jour ce code pour utiliser la variable conclusion que vous définissez en fonction des résultats de RuboCop (sur success ou neutral). Voici le code que vous avez ajouté à votre fichier 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'
)

Remplacez ce code par le code suivant :

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

Maintenant que votre code définit une conclusion basée sur l’état du test de CI et ajoute la sortie des résultats de RuboCop, vous avez créé un test de CI.

Le code ci-dessus ajoute également une fonctionnalité appelée « actions demandées » à votre serveur de CI, via l’objet actions. (Notez que ceci n’a pas trait à GitHub Actions.) Pour plus d’informations, consultez « Demander d’autres actions à partir d’une exécution de vérification ». Les actions demandées ajoutent un bouton sous l’onglet Vérifications de GitHub, qui permet à une personne de demander l’exécution de vérification pour prendre une mesure supplémentaire. La mesure supplémentaire est entièrement configurable par votre application. Par exemple, RuboCop disposant d’une fonctionnalité permettant de corriger automatiquement les erreurs détectées dans le code Ruby, votre serveur de CI peut utiliser un bouton Actions demandées pour permettre aux à des personnes de demander des corrections automatiques d’erreurs. Lorsque quelqu’un clique sur le bouton, l’application reçoit l’événement check_run avec une action requested_action. Chaque action demandée a un identifier que l’application utilise pour déterminer le bouton qui a été cliqué.

Dans le code ci-dessus, RuboCop ne corrige pas encore automatiquement les erreurs. Vous l’ajouterez plus loin dans le tutoriel.

Tester le code

Les étapes suivantes vous montrent comment tester le fonctionnement du code et afficher le test de CI que vous venez de créer.

  1. Exécutez la commande suivante pour redémarrer le serveur à partir de votre terminal. Si le serveur est déjà en cours d’exécution, entrez Ctrl-C dans votre terminal pour arrêter le serveur, puis exécutez la commande suivante pour redémarrer le serveur.

    Shell
    ruby server.rb
    
  2. Dans le dépôt dans lequel vous avez ajouté le fichier myfile.rb, créez une demande de tirage.

  3. Dans la demande de tirage que vous venez de créer, accédez à l’onglet Vérifications. Vous devriez voir des annotations pour chacune des erreurs détectées par RuboCop. Notez également le bouton « Corriger cela » que vous avez créé en ajoutant une action demandée.

Étape 2.6. Corriger automatiquement les erreurs de RuboCop

Jusqu’à présent, vous avez créé un test de CI. Dans cette section, vous allez ajouter une fonctionnalité qui utilise RuboCop pour corriger automatiquement les erreurs détectées. Vous avez déjà ajouté le bouton « Corriger cela » à l’Étape 2.5. Mettre à jour l’exécution de vérification avec les résultats de test de CI ». Vous allez maintenant ajouter le code pour gérer l’événement d’exécution de vérification requested_action qui est déclenché quand quelqu’un clique sur le bouton « Corriger cela ».

L’outil RuboCop offre l’option de ligne de commande --auto-correct pour corriger automatiquement les erreurs détectées. Pour plus d’informations, consultez « Correction automatique des infractions » dans la documentation RuboCop. Lorsque vous utilisez la fonctionnalité --auto-correct, les mises à jour sont appliquées aux fichiers locaux sur le serveur. Vous devez pousser les modifications vers GitHub après que RuboCop a apporté les correctifs.

Pour effectuer une poussée vers à un dépôt, votre application doit disposer d’autorisations d’écriture pour « Contenu » dans un dépôt. Vous avez déjà défini cette autorisation sur Lire et écrire à l’Étape 2.2. Clonage du dépôt ».

Pour commiter des fichiers, Git doit connaître le nom d’utilisateur et l’adresse e-mail à associer au commit. Ensuite, vous allez ajouter des variables d’environnement pour stocker le nom et l’adresse e-mail que votre application utilisera quand elle effectuera des commits Git.

  1. Ouvrez le fichier .env que vous avez créé dans ce tutoriel.

  2. Ajoutez les variables d’environnement suivantes à votre fichier .env. Remplacez APP_NAME par le nom de votre application et EMAIL_ADDRESS par un e-mail que vous souhaitez utiliser pour cet exemple.

    Shell
    GITHUB_APP_USER_NAME="APP_NAME"
    GITHUB_APP_USER_EMAIL="EMAIL_ADDRESS"
    

Ensuite, vous devez ajouter du code pour lire les variables d’environnement et définir la configuration Git. Vous allez bientôt ajouter ce code.

Lorsque quelqu’un clique sur le bouton « Corriger cela », votre application reçoit le webhook d’exécution de vérification avec le type d’action requested_action.

Dans « Étape 1.3. Mise à jour d’une exécution de vérification », vous avez mis à jour le event_handler dans votre fichier server.rb pour rechercher les actions dans l’événement check_run. Vous disposez déjà d’une instruction case pour gérer les types d’actions created et 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

Après le cas rerequested, où il est indiqué # ADD REQUESTED_ACTION METHOD HERE #, ajoutez le code suivant :

Ruby
    when 'requested_action'
      take_requested_action

Ce code appelle une nouvelle méthode qui gère tous les événements requested_action pour votre application.

Dans le bloc de code qui commence par helpers do, où il est indiqué # ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #, ajoutez la méthode d’assistance suivante :

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

Le code ci-dessus clone un dépôt comme le code que vous avez ajouté dans « Étape 2.2. Cloner le dépôt ». Une instruction if vérifie que l’identificateur de l’action demandée correspond à l’identificateur du bouton RuboCop (fix_rubocop_notices). Quand ils correspondent, le code clone le dépôt, définit le nom d’utilisateur et l’e-mail Git, puis exécute RuboCop avec l’option --auto-correct. L’option --auto-correct applique automatiquement les modifications aux fichiers du serveur de CI local.

Les fichiers sont changés localement, mais vous devez toujours les pousser vers GitHub. Vous allez utiliser la gemme ruby-git pour commiter tous les fichiers. Git dispose d’une commande qui effectue une copie intermédiaire de tous les fichiers modifiés ou supprimés et les valide : git commit -a. Pour faire la même chose à l’aide de ruby-git, le code ci-dessus utilise la méthode commit_all. Ensuite, le code pousse les fichiers commités vers GitHub avec le jeton d’installation, en utilisant la même méthode d’authentification que la commande Git clone. Enfin, il supprime le répertoire du dépôt pour s’assurer que le répertoire de travail est prêt pour l’événement suivant.

Le code que vous avez écrit termine maintenant votre serveur d’intégration continue que vous avez généré avec une GitHub App et des vérifications. Pour afficher le code final complet de votre application, consultez « Exemple de code complet ».

Tester le code

Les étapes suivantes vous montrent comment vérifier que le code fonctionne et que RuboCop peut corriger automatiquement les erreurs qu’il trouve.

  1. Exécutez la commande suivante pour redémarrer le serveur à partir de votre terminal. Si le serveur est déjà en cours d’exécution, entrez Ctrl-C dans votre terminal pour arrêter le serveur, puis exécutez la commande suivante pour redémarrer le serveur.

    Shell
    ruby server.rb
    
  2. Dans le dépôt dans lequel vous avez ajouté le fichier myfile.rb, créez une demande de tirage.

  3. Dans la nouvelle demande de tirage que vous avez créée, accédez à l’onglet Vérifications, puis cliquez sur le bouton « Corriger cela » pour corriger automatiquement les erreurs détectées par RuboCop.

  4. Accédez à l’onglet Commits. Vous devriez voir un nouveau commit par le nom d’utilisateur que vous avez défini dans votre configuration Git. Il se peut que vous deviez actualiser votre navigateur pour voir la mise à jour.

  5. Accédez à l’onglet Vérifications. Vous devriez voir une nouvelle suite de vérifications pour Octo RuboCop. Mais cette fois, il ne devrait y avoir aucune erreur, car RuboCop les a toutes corrigées.

Exemple de code complet

Voici à quoi doit ressembler le code final dans server.rb, une fois que vous avez suivi toutes les étapes de ce tutoriel. Le code comporte également des commentaires qui fournissent un contexte supplémentaire.

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 an not altererd 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

Étapes suivantes

Vous devez maintenant disposer d’une application qui reçoit des événements d’API, crée des exécutions de vérification, utilise RuboCop pour rechercher les erreurs Ruby, crée des annotations dans une demande de tirage et corrige automatiquement les erreurs de linter. Vous pouvez à présent développer le code de votre application, déployer celle-ci et la rendre publique.

Si vous avez des questions, vous pouvez demander de l’aide ou des conseils auprès de la communauté GitHub, dans les « discussions sur l’API et les webhooks ».

Modifier le code de l’application

Ce tutoriel a montré comment créer un bouton « Corriger cela » qui est toujours affiché dans les demandes de tirage dans le dépôt. Essayez de mettre à jour le code pour afficher le bouton « Corriger cela » uniquement quand RuboCop détecte des erreurs.

Si vous préférez que RuboCop ne commite pas les fichiers directement dans la branche principale, mettez à jour le code pour, à la place, créer une demande de tirage avec une nouvelle branche qui est basée sur la branche principale.

Déployer votre application

Ce tutoriel a montré comment développer votre application localement. Lorsque vous êtes prêt à déployer votre application, vous devez apporter des modifications pour servir votre application et sécuriser les informations d’identification de votre application. Les étapes que vous effectuez dépendent du serveur que vous utilisez, mais les sections suivantes offrent des conseils généraux.

Héberger votre application sur un serveur

Ce tutoriel a utilisé votre ordinateur ou codespace comme serveur. Une fois que l’application est prête pour être utilisée en production, vous devez déployer votre application sur un serveur dédié. Par exemple, vous pouvez utiliser Azure App Service.

Mettre à jour l’URL du webhook

Une fois que vous disposez d’un serveur configuré pour recevoir le trafic webhook à partir de GitHub, mettez à jour l’URL du webhook dans les paramètres de votre application. Vous ne devez pas utiliser Smee.io pour transférer vos webhooks en production.

Mettre à jour le paramètre :port

Quand vous déployez votre application, vous souhaitez changer le port d’écoute de votre serveur. Le code indique déjà à votre serveur d’écouter toutes les interfaces réseau disponibles en définissant :bind sur 0.0.0.0.

Par exemple, vous pouvez définir une variable PORT dans votre fichier .env sur votre serveur pour indiquer le port d’écoute probable de votre serveur. Ensuite, vous pouvez mettre à jour l’emplacement où votre code définit :port afin que votre serveur écoute sur votre port de déploiement :

Ruby
set :port, ENV['PORT']

Sécuriser les informations d’identification de votre application

Vous ne devez jamais rendre public la clé privée ou le secret du webhook de votre application. Ce tutoriel a stocké les informations d’identification de votre application dans un fichier gitignored .env. Quand vous déployez votre application, choisissez un moyen sécurisé de stocker les informations d'identification et de mettre à jour votre code pour obtenir la valeur en conséquence. Par exemple, vous pouvez stocker les informations d’identification avec un service de gestion des secrets comme Azure Key Vault. Lorsque votre application s’exécute, elle peut récupérer les informations d’identification et les stocker dans des variables d’environnement sur le serveur sur lequel votre application est déployée.

Pour plus d’informations, consultez « Meilleures pratiques pour la création d’une application GitHub ».

Partager vos données

Si vous souhaitez partager votre application avec d’autres utilisateurs et organisations, rendez-la publique. Pour plus d’informations, consultez « Rendre une application GitHub publique ou privée ».

Suivre les bonnes pratiques

Vous devez vous efforcer de suivre les bonnes pratiques avec votre GitHub App. Pour plus d’informations, consultez « Meilleures pratiques pour la création d’une application GitHub ».