From aca02b6cfc109aa9abf26bfdff4607a3240cae98 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:29:33 -0600 Subject: [PATCH 01/48] Pull in content from dev env tutorial and repo --- .../creating-ci-tests-with-the-checks-api.md | 495 +++++++++++++++++- 1 file changed, 483 insertions(+), 12 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md index 73d634e36e87..432623902d6e 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md @@ -17,15 +17,19 @@ shortTitle: CI tests using Checks API --- ## Introduction -This guide will introduce you to [GitHub Apps](/apps) and the [Checks API](/rest/checks), which you'll use to build a continuous integration (CI) server that runs tests. +This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. You'll build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to Checks webhook events using {% data variables.product.prodname_dotcom %}'s REST API. + +In this tutorial, you will use your computer or codespace as a server while you develop your app. Once the app is ready for production use, you should deploy your app to a dedicated server. + +This tutorial uses Ruby, but you can use any programming language that you can run on your server. + +### About continuous integration (CI) CI is a software practice that requires frequently committing code to a shared repository. Committing code more often raises errors sooner and reduces the amount of code a developer needs to debug when finding the source of an error. Frequent code updates also make it easier to merge changes from different members of a software development team. This is great for developers, who can spend more time writing code and less time debugging errors or resolving merge conflicts. 🙌 A CI server hosts code that runs CI tests such as code linters (which check style formatting), security checks, code coverage, and other checks against new code commits in a repository. CI servers can even build and deploy code to staging or production servers. For some examples of the types of CI tests you can create with a GitHub App, check out the [continuous integration apps](https://github.com/marketplace/category/continuous-integration) available in GitHub Marketplace. -{% data reusables.apps.app-ruby-guides %} - -### Checks API overview +### About checks The [Checks API](/rest/checks) allows you to set up CI tests that are automatically run against each code commit in a repository. The Checks API reports detailed information about each check on GitHub in the pull request's **Checks** tab. With the Checks API, you can create annotations with additional details for specific lines of code. Annotations are visible in the **Checks** tab. When you create an annotation for a file that is part of the pull request, the annotations are also shown in the **Files changed** tab. @@ -50,24 +54,491 @@ The Checks API sends the [`check_suite` webhook event](/webhooks-and-events/webh ## Prerequisites -Before you get started, you may want to familiarize yourself with [GitHub Apps](/apps), [Webhooks](/webhooks-and-events/webhooks/about-webhooks), and the [Checks API](/rest/checks), if you're not already. You'll find more APIs in the [REST API docs](/rest). The Checks API is also available to use in [GraphQL](/graphql), but this quickstart focuses on REST. See the GraphQL [Checks Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) objects for more details. +Before you get started, you may want to familiarize yourself with [GitHub Apps](/apps), [Webhooks](/webhooks-and-events/webhooks/about-webhooks), and the [REST API checks endpoints](/rest/checks). The Checks endpoints are also available to use in [GraphQL](/graphql), but this quickstart focuses on REST. See the GraphQL [Checks Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) objects for more details. You'll use the [Ruby programming language](https://www.ruby-lang.org/en/), the [Smee](https://smee.io/) webhook payload delivery service, the [Octokit.rb Ruby library](https://octokit.github.io/octokit.rb/) for the GitHub REST API, and the [Sinatra web framework](https://sinatrarb.com/) to create your Checks API CI server app. -You don't need to be an expert in any of these tools or concepts to complete this project. This guide will walk you through all the required steps. Before you begin creating CI tests with the Checks API, you'll need to do the following: +## Setup + +The following sections will lead you through setting up the following components: + +- A repository to store the code for your app +- A way to receive webhooks locally +- A {% data variables.product.prodname_github_app %} that is subscribed to "Check suite" and "Check run" webhook events, has write permission for checks, and uses a webhook URL that you can receive locally. -1. Clone the [Creating CI tests with the Checks API](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) repository. +### Create a repository to store code for your app + +1. Create a repository to store the code for your app. For more information, see "[AUTOTITLE](/repositories/creating-and-managing-repositories/creating-a-new-repository)." +1. Clone your repository from the previous step. For more information, see "[AUTOTITLE](/repositories/creating-and-managing-repositories/cloning-a-repository)." You may use a local clone or {% data variables.product.prodname_github_codespaces %}. +1. In a terminal, navigate to the directory where your clone is stored. +1. Create a Ruby file named `server.rb`. This file will contain all the code for your app. You will add content to this file later. +1. If the directory doesn't already include a `.gitignore` file, add a `.gitignore` file. You will add content to this file later. For more information about `.gitignore` files, see "[AUTOTITLE](/get-started/getting-started-with-git/ignoring-files)." + +### Get a webhook proxy URL + +In order to develop your app locally, you can use a webhook proxy URL to forward webhook events from {% data variables.product.company_short %} to your computer or codespace. This tutorial uses Smee.io to provide a webhook proxy URL and forward events. + +1. In your browser, navigate to https://smee.io/. +1. Click **Start a new channel**. +1. Copy the full URL under "Webhook Proxy URL". You will use this URL in a following step, and during the app registration steps later in the tutorial. +1. In a terminal, run the following command to install the Smee client: + ```shell + npm install --global smee-client + ``` +1. In the terminal, run the following command to start the Smee client. Replace `https://smee.io/YOUR_DOMAIN` with the Webhook Proxy URL you copied in the previous step. + ```shell + smee --url https://smee.io/YOUR_DOMAIN --path /event_handler --port 3000 + ``` + You should see output like the following: ```shell - $ git clone https://github.com/github-developer/creating-ci-tests-with-the-checks-api.git + Forwarding https://smee.io/YOUR_DOMAIN to http://127.0.0.1:3000/event_handler + Connected https://smee.io/YOUR_DOMAIN ``` - Inside the directory, you'll find a `template_server.rb` file with the template code you'll use in this quickstart and a `server.rb` file with the completed project code. +The `smee --url ` command tells Smee to forward all webhook events received by the Smee channel to the Smee client running on your computer. The `--path /event_handler` option forwards events to the `/event_handler` route, which we'll cover in a [later section](#step-5-review-the-github-app-template-code). The `--port 3000` option specifies port 3000, which is the port your server will be listening to. Using Smee, your machine does not need to be open to the public internet to receive webhooks from GitHub. You can also open that Smee URL in your browser to inspect webhook payloads as they come in. + +We recommend leaving this terminal window open and keeping Smee connected while you complete the rest of the steps in this guide. Although you _can_ disconnect and reconnect the Smee client without losing your unique domain (unlike `ngrok`), you may find it easier to leave it connected and do other command-line tasks in a different terminal window. + +### Register a {% data variables.product.prodname_github_app %} + +For this tutorial, you must register a {% data variables.product.prodname_github_app %} that: + +- Has webhooks active +- Uses a webhook URL that you can receive locally +- Has the "Checks" repository permission +- Subscribes to the "Checks" webhook event + +The following steps will guide you through configuring a {% data variables.product.prodname_github_app %} with these settings. For more information about {% data variables.product.prodname_github_app %} settings, see "[AUTOTITLE](/apps/creating-github-apps/creating-github-apps/creating-a-github-app)." + +{% data reusables.apps.settings-step %} +{% data reusables.user-settings.developer_settings %} +{% data reusables.user-settings.github_apps %} +1. Click **New GitHub App**. +1. Under "GitHub App name," enter a name for your app. For example, `USERNAME-ci-test-app` where `USERNAME` is your {% data variables.product.company_short %} username. +1. Under "Homepage URL," enter a URL for your app. For example, you can use the URL of the repository that you created to store the code for your app. +1. Skip the "Identifying and authorizing users" and "Post installation" sections for this tutorial. For more information about these settings, see "[AUTOTITLE](/apps/creating-github-apps/creating-github-apps/creating-a-github-app)." +1. Make sure that **Active** is selected under "Webhooks." +1. Under "Webhook URL", enter your webhook proxy URL from earlier. For more information, see "[Get a webhook proxy URL](#get-a-webhook-proxy-url)." +1. Under "Webhook secret," enter a random string. This secret is used to verify that webhooks are sent by {% data variables.product.prodname_dotcom %}. You will use this string later. +1. Under "Repository permissions," next to "Checks," select **Read & write**. +1. Under "Subscribe to events," select **Check suite** and **Check run**. +1. Under "Where can this GitHub App be installed?", select **Only on this account**. You can change this later if you want to publish your app. +1. Click **Create GitHub App**. + +### Store your app's identifying information and credentials + +This tutorial will show you how to store your app's credentials and identifying information as environment variables in a `.env` file. When you deploy your app, you will want to change how you store the credentials. For more information, see "[Deploy your app](#deploy-your-app)." + +Make sure that you are on a secure machine before performing these steps since you will store your credentials locally. + +1. In your terminal, navigate to the directory where your clone is stored. +1. Create a file called `.env` at the top level of this directory. +1. Add `.env` to your `.gitignore` file. This will prevent you from accidentally committing your app's credentials. +1. Add the following contents to your `.env` file. {% ifversion ghes or ghae %}Replace `YOUR_HOSTNAME` with the name of {% data variables.location.product_location %}. You will update the other values in a later step.{% else %}You will update the values in a later step.{% endif %} + + ```{:copy} + APP_IDENTIFIER="YOUR_APP_ID" + WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET" + PRIVATE_KEY="YOUR_PRIVATE_KEY"{% ifversion ghes or ghae %} + ENTERPRISE_HOSTNAME="YOUR_HOSTNAME"{% endif %} + ``` + +1. {% data reusables.apps.navigate-to-app-settings-page %} +1. On your app's settings page, next to "App ID," find the app ID for your app. +1. In your `.env` file, replace `YOUR_APP_ID` with the app ID of your app. +1. In your `.env` file, replace `YOUR_WEBHOOK_SECRET` with the webhook secret for your app. If you have forgotten your webhook secret, under "Webhook secret (optional)," click **Change secret**. Enter a new secret, then click **Save changes**. +1. On your app's settings page, under "Private keys," click **Generate a private key**. You will see a private key `.pem` file downloaded to your computer. +1. Open the `.pem` file with a text editor, or use the following command on the command line to display the contents of the file: `cat PATH/TO/YOUR/private-key.pem`. +1. Copy and paste the entire contents of the file into your `.env` file as the value of `GITHUB_PRIVATE_KEY`, and add double quotes around the entire value. + + Here is an example .env file: + + ``` + GITHUB_APP_IDENTIFIER=12345 + GITHUB_WEBHOOK_SECRET=your webhook secret + GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- + ... + HkVN9... + ... + -----END DSA PRIVATE KEY-----" + ``` + +## Add code for your app + +This section will show you how to add some basic template code for your GitHub App, and it will explain what the code does. Later in the tutorial, you will learn how to modify and add to this code, to build out your app's functionality. + +Add the following template code to your `server.rb` file: + +```ruby{:copy} +require 'sinatra' +require 'octokit' +require 'dotenv/load' # Manages environment variables +require 'json' +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 + +set :port, 3000 +set :bind, '0.0.0.0' + + +# This is template code to create a GitHub App server. +# You can read more about GitHub Apps here: # https://developer.github.com/apps/ +# +# On its own, this app does absolutely nothing, except that it can be installed. +# It's up to you to add functionality! +# You can check out one example in advanced_server.rb. +# +# 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. +# +# Of course, not all apps need to receive and process events! +# Feel free to rip out the event handling code if you don't need it. +# +# Have fun! +# + +class GHAapp < Sinatra::Application + + # Expects that 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 secret set. 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) set when registering an app. + APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] + + # Turn on Sinatra's verbose logging during development + configure :development do + set :logging, Logger::DEBUG + end + + + # Before each request to the `/event_handler` route + before '/event_handler' do + get_payload_request(request) + verify_webhook_signature + authenticate_app + # Authenticate the app installation in order to run API operations + authenticate_installation(@payload) + end + + + post '/event_handler' do + + # # # # # # # # # # # # + # ADD YOUR CODE HERE # + # # # # # # # # # # # # + + 200 # success status + end + + + helpers do + + # # # # # # # # # # # # # # # # # + # ADD YOUR HELPER METHODS 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'. + # See https://developer.github.com/webhooks/securing/ for details. + 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 +``` + +The rest of this section will explain what the template code does. There aren't any steps that you need to complete in this section. If you're already familiar with the template code, you can skip ahead to "[Start the server](#start-the-server)." + +### Review the template code + +Open up the `server.rb` file in a text editor. You'll see comments throughout the file that provide additional context for the template code. We recommend reading those comments carefully and even adding your own comments to accompany new code you write. + +At the top of the file you'll see `set :port 3000`, which sets the port used when starting the web server to match the port you redirected your webhook payloads to in "[Step 1. Start a new Smee channel](#step-1-start-a-new-smee-channel)." + +The next code you'll see is the `class GHApp < Sinatra::Application` declaration. You'll write all of the code for your GitHub App inside this class. + +Out of the box, the class in the template does the following things: +* [Read the environment variables](#read-the-environment-variables) +* [Turn on logging](#turn-on-logging) +* [Define a before filter](#define-a-before-filter) +* [Define the route handler](#define-a-route-handler) +* [Define the helper methods](#define-the-helper-methods) + +#### Read the environment variables + +The first thing that this class does is read the three environment variables you set in "[Step 4. Prepare the runtime environment](#step-4-prepare-the-runtime-environment)" and store them in variables to use later: + +``` ruby +# Expects that 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 secret set. 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) set when registering an app. +APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] +``` + +#### Turn on logging + +Next is a code block that enables logging during development, which is the default environment in Sinatra. This code turns on logging at the `DEBUG` level to show useful output in the Terminal while you are developing the app: + +``` ruby +# Turn on Sinatra's verbose logging during development +configure :development do + set :logging, Logger::DEBUG +end +``` + +#### Define a before filter + +Sinatra uses [before filters](https://github.com/sinatra/sinatra#filters) that allow you to execute code before the route handler. The `before` block in the template calls four [helper methods](https://github.com/sinatra/sinatra#helpers). The template app defines those helper methods in a [later section](#define-the-helper-methods). + +``` ruby +# Before each request to the `/event_handler` route +before '/event_handler' do + get_payload_request(request) + verify_webhook_signature + authenticate_app + # Authenticate the app installation in order to run API operations + authenticate_installation(@payload) +end +``` + +#### Define a route handler + +An empty route is included in the template code. This code handles all `POST` requests to the `/event_handler` route. You won't write this event handler in this quickstart, but see the other [quickstart guides](/apps/creating-github-apps/guides) for examples of how to extend this template app. + +``` ruby +post '/event_handler' do + +end +``` + +#### Define the helper methods + +The helper methods in this template do most of the heavy lifting. Four helper methods are defined in this section of the code. + +##### Handling the webhook payload + +The first method `get_payload_request` captures the webhook payload and converts it to JSON format, which makes accessing the payload's data much easier. + +##### Verifying the webhook signature + +The second method `verify_webhook_signature` performs verification of the webhook signature to ensure that GitHub generated the event. To learn more about the code in the `verify_webhook_signature` helper method, see "[AUTOTITLE](/webhooks-and-events/webhooks/securing-your-webhooks)." If the webhooks are secure, this method will log all incoming payloads to your Terminal. The logger code is helpful in verifying your web server is working but you can always remove it later. + +##### Authenticating as a GitHub App + +To make API calls, you'll be using the [Octokit library](https://octokit.github.io/octokit.rb/). Doing anything interesting with this library will require you, or rather your app, to authenticate. GitHub Apps have two methods of authentication: + +- Authenticating as a GitHub App using a [JSON Web Token (JWT)](https://jwt.io/introduction). +- Authenticating as a specific installation of a GitHub App using an installation access token. + +You'll learn about authenticating as an installation in the [next section](#authenticating-as-an-installation). + +Authenticating as a GitHub App lets you do a couple of things: + + * You can retrieve high-level management information about your GitHub App. + * You can request access tokens for an installation of the app. + +For example, you would authenticate as a GitHub App to retrieve a list of the accounts (organization and personal) that have installed your app. But this authentication method doesn't allow you to do much with the API. To access a repository's data and perform operations on behalf of the installation, you need to authenticate as an installation. To do that, you'll need to authenticate as a GitHub App first to request an installation access token. For more information, see "[AUTOTITLE](/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app)." + +Before you can use the Octokit.rb library to make API calls, you'll need to initialize an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as a GitHub App. The `authenticate_app` helper method does just that! + +``` ruby +# 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 +``` + +The code above generates a [JSON Web Token (JWT)](https://jwt.io/introduction) and uses it (along with your app's private key) to initialize the Octokit client. GitHub checks a request's authentication by verifying the token with the app's stored public key. To learn more about how this code works, see "[AUTOTITLE](/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app)." + +##### Authenticating as an installation + +An _installation_ refers to any user or organization account that has installed the app. Even if someone installs the app on more than one repository, it only counts as one installation because it's within the same account. The last helper method `authenticate_installation` initializes an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as an installation. This Octokit client is what you'd use to make authenticated API calls. + +``` ruby +# 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 +``` + +The [`create_app_installation_access_token`](https://octokit.github.io/octokit.rb/Octokit/Client/Apps.html#create_app_installation_access_token-instance_method) Octokit method creates an installation token. This method accepts two arguments: + +* Installation (integer): The ID of a GitHub App installation +* Options (hash, defaults to `{}`): A customizable set of options + +Any time a GitHub App receives a webhook, it includes an `installation` object with an `id`. Using the client authenticated as a GitHub App, you pass this ID to the `create_app_installation_access_token` method to generate an access token for each installation. Since you're not passing any options to the method, the options default to an empty hash. The response for `create_app_installation_access_token` includes two fields: `token` and `expired_at`. The template code selects the token in the response and initializes an installation client. + +With this method in place, each time your app receives a new webhook payload, it creates a client for the installation that triggered the event. This authentication process enables your GitHub App to work for all installations on any account. + +## Start the server + +Your app doesn't do anything yet, but at this point, you can get it running on the server. + +Keep Smee running in the current tab in your terminal. Open a new tab and `cd` into the directory where you [cloned the template app code](#prerequisites). The Ruby code in this repository will start up a [Sinatra](https://sinatrarb.com/) web server. This code has a few dependencies. You can install these by running: + +```shell{:copy} +gem install bundler +``` + +Followed by: + +```shell{:copy} +bundle install +``` + +With the dependencies installed, you can start the server: + +```shell{:copy} +bundle exec ruby server.rb +``` + +You should see a response like: + +```shell +> == Sinatra (v2.0.3) has taken the stage on 3000 for development with backup from Puma +> Puma starting in single mode... +> * Version 3.11.2 (ruby 2.4.0-p0), codename: Love Song +> * Min threads: 0, max threads: 16 +> * Environment: development +> * Listening on tcp://localhost:3000 +> Use Ctrl-C to stop +``` + +If you see an error, make sure you've created the `.env` file in the directory that contains `server.rb`. + +Once the server is running, you can test it by going to `http://localhost:3000` in your browser. If the app works as expected, you'll see a helpful error page that says, "Sinatra doesn't know this ditty." + +This is good! Even though it's an error page, it's a Sinatra error page, which means your app is connected to the server as expected. You're seeing this message because you haven't given the app anything else to show. + +## Install the app on your account + +You can test that the server is listening to your app by triggering an event for it to receive. A simple event you can test is installing the app on your GitHub account, which should send the [`installation`](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) event. If the app receives it, you should see some output in the Terminal tab where you started `template_server.rb`. + +To install the app, visit the [app settings page](https://github.com/settings/apps), choose your app, and click **Install App** in the sidebar. Next to your username, click **Install**. + +You'll be asked whether to install the app on all repositories or selected repositories. If you don't want to install the app on _all_ of your repositories, that's okay! You may want to create a sandbox repository for testing purposes and install your app there. + +After you click **Install**, look at the output in your Terminal. You should see something like this: + +```shell +> D, [2018-06-29T15:45:43.773077 #30488] DEBUG -- : ---- received event integration_installation +> D, [2018-06-29T15:45:43.773141 #30488] DEBUG -- : ---- action created +> 192.30.252.44 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0067 +> D, [2018-06-29T15:45:43.833016 #30488] DEBUG -- : ---- received event installation +> D, [2018-06-29T15:45:43.833062 #30488] DEBUG -- : ---- action created +> 192.30.252.39 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0019 +``` -1. Follow the steps in the "[AUTOTITLE](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app)" quickstart to configure and run the app server. **Note:** Instead of [cloning the GitHub App template repository](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app#prerequisites), use the `template_server.rb` file in the repository you cloned in the previous step in this quickstart. +This is good news! It means your app received a notification that it was installed on your GitHub account. If you see something like this, your app is running on the server as expected. - If you've completed a GitHub App quickstart before, make sure to register a _new_ GitHub App and start a new Smee channel to use with this quickstart. +If you don't see the output, make sure Smee is running correctly in another Terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in Terminal. If Smee isn't the problem, see the "[Troubleshooting](#troubleshooting)" section for other ideas. - See the [troubleshooting](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app#troubleshooting) section if you are running into problems setting up your template GitHub App. +If you're wondering where the terminal output above is coming from, it's written in the [app template code](#add-code-for-your-app) in `server.rb`. ## Part 1. Creating the Checks API interface From 301ecbe4425c31137a88d483c940c8985788d13d Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:31:48 -0600 Subject: [PATCH 02/48] renamed 1 files --- ...-the-checks-api.md => building-ci-checks-with-a-github-app.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename content/apps/creating-github-apps/writing-code-for-a-github-app/{creating-ci-tests-with-the-checks-api.md => building-ci-checks-with-a-github-app.md} (100%) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md similarity index 100% rename from content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md rename to content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md From 3ad86a29bdad6c46fae671b35b78b36851357f78 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:31:48 -0600 Subject: [PATCH 03/48] set redirect_from on 1 files --- .../building-ci-checks-with-a-github-app.md | 1 + .../creating-github-apps/writing-code-for-a-github-app/index.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 432623902d6e..a18de0e8405e 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -6,6 +6,7 @@ redirect_from: - /developers/apps/creating-ci-tests-with-the-checks-api - /developers/apps/guides/creating-ci-tests-with-the-checks-api - /apps/creating-github-apps/guides/creating-ci-tests-with-the-checks-api + - /apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api versions: fpt: '*' ghes: '*' diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/index.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/index.md index 118d553dabb4..767e2d6375cc 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/index.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/index.md @@ -20,6 +20,6 @@ children: - /building-a-cli-with-a-github-app - /setting-up-your-development-environment-to-create-a-github-app - /using-the-github-api-in-your-app - - /creating-ci-tests-with-the-checks-api + - /building-ci-checks-with-a-github-app --- From bfe85b6b69bfb8e3d3eca2fbe4e2d353334ce5b9 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:41:41 -0600 Subject: [PATCH 04/48] Rename article --- .../building-ci-checks-with-a-github-app.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index a18de0e8405e..131ab9b6c9de 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -1,6 +1,7 @@ --- -title: Creating CI tests with the Checks API -intro: 'Build a continuous integration server to run tests using a {% data variables.product.prodname_github_app %} and the Checks API.' +title: Building CI checks with a GitHub App +shortTitle: Build CI checks +intro: 'Build a continuous integration server to run tests using a {% data variables.product.prodname_github_app %} and checks.' redirect_from: - /apps/quickstart-guides/creating-ci-tests-with-the-checks-api - /developers/apps/creating-ci-tests-with-the-checks-api @@ -14,7 +15,6 @@ versions: ghec: '*' topics: - GitHub Apps -shortTitle: CI tests using Checks API --- ## Introduction @@ -164,7 +164,7 @@ Make sure that you are on a secure machine before performing these steps since y -----END DSA PRIVATE KEY-----" ``` -## Add code for your app +## Add code for your {% data variables.product.prodname_github_app %} This section will show you how to add some basic template code for your GitHub App, and it will explain what the code does. Later in the tutorial, you will learn how to modify and add to this code, to build out your app's functionality. From de58dd663e65fe22c9faacd6a486bf68ddb231ca Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Mon, 5 Jun 2023 19:08:20 -0600 Subject: [PATCH 05/48] Move protected branches info to rest guide --- .../rest/guides/using-the-rest-api-to-interact-with-checks.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/rest/guides/using-the-rest-api-to-interact-with-checks.md b/content/rest/guides/using-the-rest-api-to-interact-with-checks.md index 5398bec212df..9f6b9cf6fa0b 100644 --- a/content/rest/guides/using-the-rest-api-to-interact-with-checks.md +++ b/content/rest/guides/using-the-rest-api-to-interact-with-checks.md @@ -19,6 +19,8 @@ Rather than binary pass/fail build statuses, {% data variables.product.prodname_ For an example of how to use the REST API with a {% data variables.product.prodname_github_app %}, see "[AUTOTITLE](/apps/creating-github-apps/guides/creating-ci-tests-with-the-checks-api)." +You can use statuses with [protected branches](/rest/repos#branches) to prevent people from merging pull requests prematurely. For more information, see "[AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging)." + ## About check suites When someone pushes code to a repository, GitHub creates a check suite for the last commit. A check suite is a collection of the [check runs](/rest/checks#check-runs) created by a single GitHub App for a specific commit. Check suites summarize the status and conclusion of the check runs that a suite includes. From 191033d455a98b3f31c9a317e57403fefdb5d480 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Tue, 6 Jun 2023 13:59:28 -0600 Subject: [PATCH 06/48] More content updates --- .../building-ci-checks-with-a-github-app.md | 528 +++++++++++++++--- 1 file changed, 457 insertions(+), 71 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 131ab9b6c9de..056b3748d671 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -18,7 +18,7 @@ topics: --- ## Introduction -This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. You'll build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to Checks webhook events using {% data variables.product.prodname_dotcom %}'s REST API. +This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to Checks webhook events using {% data variables.product.prodname_dotcom %}'s REST API. In this tutorial, you will use your computer or codespace as a server while you develop your app. Once the app is ready for production use, you should deploy your app to a dedicated server. @@ -26,54 +26,89 @@ This tutorial uses Ruby, but you can use any programming language that you can r ### About continuous integration (CI) -CI is a software practice that requires frequently committing code to a shared repository. Committing code more often raises errors sooner and reduces the amount of code a developer needs to debug when finding the source of an error. Frequent code updates also make it easier to merge changes from different members of a software development team. This is great for developers, who can spend more time writing code and less time debugging errors or resolving merge conflicts. 🙌 +CI is a software practice that requires frequently committing code to a shared repository. Committing code more often raises errors sooner and reduces the amount of code a developer needs to debug when finding the source of an error. Frequent code updates also make it easier to merge changes from different members of a software development team. This is great for developers, who can spend more time writing code and less time debugging errors or resolving merge conflicts. -A CI server hosts code that runs CI tests such as code linters (which check style formatting), security checks, code coverage, and other checks against new code commits in a repository. CI servers can even build and deploy code to staging or production servers. For some examples of the types of CI tests you can create with a GitHub App, check out the [continuous integration apps](https://github.com/marketplace/category/continuous-integration) available in GitHub Marketplace. +A CI server hosts code that runs CI tests such as code linters (which check style formatting), security checks, code coverage, and other checks against new code commits in a repository. CI servers can even build and deploy code to staging or production servers. For some examples of the types of CI tests you can create with a {% data variables.product.prodname_github_app %}, check out the [continuous integration apps](https://github.com/marketplace/category/continuous-integration) available in {% data variables.product.prodname_marketplace %}. ### About checks -The [Checks API](/rest/checks) allows you to set up CI tests that are automatically run against each code commit in a repository. The Checks API reports detailed information about each check on GitHub in the pull request's **Checks** tab. With the Checks API, you can create annotations with additional details for specific lines of code. Annotations are visible in the **Checks** tab. When you create an annotation for a file that is part of the pull request, the annotations are also shown in the **Files changed** tab. +{% data variables.product.prodname_dotcom %}'s REST API allows you to set up CI tests (checks) that are automatically run against each code commit in a repository. The API reports detailed information about each check in the pull request's **Checks** tab on {% data variables.product.prodname_dotcom %}. You can use checks in a repository to determine when a code commit introduces errors. -A _check suite_ is a group of _check runs_ (individual CI tests). Both the suite and the runs contain _statuses_ that are visible in a pull request on GitHub. You can use statuses to determine when a code commit introduces errors. Using these statuses with [protected branches](/rest/repos#branches) can prevent people from merging pull requests prematurely. See "[AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging)" for more details. +Checks include check runs, check suites, and commit statuses. -The Checks API sends the [`check_suite` webhook event](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) to all GitHub Apps installed on a repository each time new code is pushed to the repository. To receive all Checks API event actions, the app must have the `checks:write` permission. GitHub automatically creates `check_suite` events for new code commits in a repository using the default flow, although [Update repository preferences for check suites](/rest/checks#update-repository-preferences-for-check-suites) if you'd like. Here's how the default flow works: +- A _check run_ is an individual CI test that runs on a commit. +- A _check suite_ is a group of check runs. +- A _commit status_ marks the state of a commit, for example `error`, `failure`, `pending`, or `success`, and is visible in a pull request on {% data variables.product.prodname_dotcom %}. -1. Whenever someone pushes code to the repository, GitHub sends the `check_suite` event with an action of `requested` to all GitHub Apps installed on the repository that have the `checks:write` permission. This event lets the apps know that code was pushed and that GitHub has automatically created a new check suite. +Both check suites and check runs contain commit statuses. For more information about checks, see "[AUTOTITLE](/rest/checks)" and "[AUTOTITLE](/rest/guides/using-the-rest-api-to-interact-with-checks)." + +You can also create annotations with additional details for specific lines of code. Annotations are visible in the **Checks** tab. When you create an annotation for a file that is part of the pull request, the annotations are also shown in the **Files changed** tab. + +Each time new code is pushed to a repository, {% data variables.product.prodname_dotcom %} sends the [`check_suite` webhook event](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) to all {% data variables.product.prodname_github_apps %} installed on the repository. To receive all checks event actions, the app must have the `checks:write` permission. {% data variables.product.prodname_dotcom %} automatically creates `check_suite` events for new code commits in a repository using the default flow, although you can change the default settings. For more information, see "[AUTOTITLE](/rest/checks#update-repository-preferences-for-check-suites)." Here's how the default flow works: + +1. Whenever someone pushes code to the repository, {% data variables.product.prodname_dotcom %} sends the `check_suite` event with an action of `requested` to all {% data variables.product.prodname_github_apps %} installed on the repository that have the `checks:write` permission. This event lets the apps know that code was pushed and that {% data variables.product.prodname_dotcom %} has automatically created a new check suite. 1. When your app receives this event, it can [add check runs](/rest/checks#create-a-check-run) to that suite. 1. Your check runs can include [annotations](/rest/checks#annotations-object) that are displayed on specific lines of code. **In this guide, you’ll learn how to:** * Part 1: Set up the framework for a CI server using the Checks API. - * Configure a GitHub App as a server that receives Checks API events. + * Configure a {% data variables.product.prodname_github_app %} as a server that receives Checks API events. * Create new check runs for CI tests when a repository receives newly pushed commits. - * Re-run check runs when a user requests that action on GitHub. + * Re-run check runs when a user requests that action on {% data variables.product.prodname_dotcom %}. * Part 2: Build on the CI server framework you created by adding a linter CI test. * Update a check run with a `status`, `conclusion`, and `output` details. - * Create annotations on lines of code that GitHub displays in the **Checks** and **Files Changed** tab of a pull request. + * Create annotations on lines of code that {% data variables.product.prodname_dotcom %} displays in the **Checks** and **Files Changed** tab of a pull request. * Automatically fix linter recommendations by exposing a "Fix this" button in the **Checks** tab of the pull request. ## Prerequisites -Before you get started, you may want to familiarize yourself with [GitHub Apps](/apps), [Webhooks](/webhooks-and-events/webhooks/about-webhooks), and the [REST API checks endpoints](/rest/checks). The Checks endpoints are also available to use in [GraphQL](/graphql), but this quickstart focuses on REST. See the GraphQL [Checks Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) objects for more details. +Before you get started, you may want to familiarize yourself with the following concepts: + +- [{% data variables.product.prodname_github_apps %}](/apps) +- [Webhooks](/webhooks-and-events/webhooks/about-webhooks) +- [REST API checks endpoints](/rest/checks) + +The Checks endpoints are also available to use in GraphQL, but this tutorial focuses on REST. For more information about the GraphQL objects, see [Checks Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. + +You'll use the [Ruby programming language](https://www.ruby-lang.org/en/), the [Smee](https://smee.io/) webhook payload delivery service, the [Octokit.rb Ruby library](https://octokit.github.io/octokit.rb/) for the {% data variables.product.prodname_dotcom %} REST API, and the [Sinatra web framework](https://sinatrarb.com/) to create your Checks API CI server app. -You'll use the [Ruby programming language](https://www.ruby-lang.org/en/), the [Smee](https://smee.io/) webhook payload delivery service, the [Octokit.rb Ruby library](https://octokit.github.io/octokit.rb/) for the GitHub REST API, and the [Sinatra web framework](https://sinatrarb.com/) to create your Checks API CI server app. +TODOCS: Add blurb about node.js? ([Example](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/building-a-github-app-that-responds-to-webhook-events#prerequisites)) + +TODOCS: Incorporate any additional prerequisites from the [dev environment tutorial](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/setting-up-your-development-environment-to-create-a-github-app#prerequisites)? ## Setup The following sections will lead you through setting up the following components: -- A repository to store the code for your app -- A way to receive webhooks locally +- A repository to store the code for your app. +- A way to receive webhooks locally. - A {% data variables.product.prodname_github_app %} that is subscribed to "Check suite" and "Check run" webhook events, has write permission for checks, and uses a webhook URL that you can receive locally. -### Create a repository to store code for your app +### Create a repository to store code for your {% data variables.product.prodname_github_app %} 1. Create a repository to store the code for your app. For more information, see "[AUTOTITLE](/repositories/creating-and-managing-repositories/creating-a-new-repository)." 1. Clone your repository from the previous step. For more information, see "[AUTOTITLE](/repositories/creating-and-managing-repositories/cloning-a-repository)." You may use a local clone or {% data variables.product.prodname_github_codespaces %}. 1. In a terminal, navigate to the directory where your clone is stored. 1. Create a Ruby file named `server.rb`. This file will contain all the code for your app. You will add content to this file later. 1. If the directory doesn't already include a `.gitignore` file, add a `.gitignore` file. You will add content to this file later. For more information about `.gitignore` files, see "[AUTOTITLE](/get-started/getting-started-with-git/ignoring-files)." +1. Create a file named `Gemfile`. This file will describe the gem dependencies that your Ruby code needs. Add the following contents to your `Gemfile`: + ```ruby{:copy} + source 'http://rubygems.org' + + gem 'sinatra', '~> 2.0' + gem 'jwt', '~> 2.1' + gem 'octokit', '~> 4.0' + gem 'webrick' + gem 'rubocop' + gem 'dotenv' + gem 'git' + ``` +1. Create a file named `config.ru`. This file will configure your Sinatra server to run. Add the following contents to your `config.ru` file: + ```ruby{:copy} + require './server' + run GHAapp + ``` ### Get a webhook proxy URL @@ -96,9 +131,9 @@ In order to develop your app locally, you can use a webhook proxy URL to forward Connected https://smee.io/YOUR_DOMAIN ``` -The `smee --url ` command tells Smee to forward all webhook events received by the Smee channel to the Smee client running on your computer. The `--path /event_handler` option forwards events to the `/event_handler` route, which we'll cover in a [later section](#step-5-review-the-github-app-template-code). The `--port 3000` option specifies port 3000, which is the port your server will be listening to. Using Smee, your machine does not need to be open to the public internet to receive webhooks from GitHub. You can also open that Smee URL in your browser to inspect webhook payloads as they come in. +The `smee --url https://smee.io/YOUR_DOMAIN` command tells Smee to forward all webhook events received by the Smee channel to the Smee client running on your computer. The `--path /event_handler` option forwards events to the `/event_handler` route, which we'll cover in a [later section](#review-the-template-code). The `--port 3000` option specifies port 3000, which is the port your server will be listening to. Using Smee, your machine does not need to be open to the public internet to receive webhooks from {% data variables.product.prodname_dotcom %}. You can also open that Smee URL in your browser to inspect webhook payloads as they come in. -We recommend leaving this terminal window open and keeping Smee connected while you complete the rest of the steps in this guide. Although you _can_ disconnect and reconnect the Smee client without losing your unique domain (unlike `ngrok`), you may find it easier to leave it connected and do other command-line tasks in a different terminal window. +We recommend leaving this terminal window open and keeping Smee connected while you complete the rest of the steps in this guide. Although you _can_ disconnect and reconnect the Smee client without losing your unique domain, you may find it easier to leave it connected and do other command-line tasks in a different terminal window. ### Register a {% data variables.product.prodname_github_app %} @@ -138,10 +173,9 @@ Make sure that you are on a secure machine before performing these steps since y 1. Add the following contents to your `.env` file. {% ifversion ghes or ghae %}Replace `YOUR_HOSTNAME` with the name of {% data variables.location.product_location %}. You will update the other values in a later step.{% else %}You will update the values in a later step.{% endif %} ```{:copy} - APP_IDENTIFIER="YOUR_APP_ID" - WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET" - PRIVATE_KEY="YOUR_PRIVATE_KEY"{% ifversion ghes or ghae %} - ENTERPRISE_HOSTNAME="YOUR_HOSTNAME"{% endif %} + GITHUB_APP_IDENTIFIER="YOUR_APP_ID" + GITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET" + GITHUB_PRIVATE_KEY="YOUR_PRIVATE_KEY" ``` 1. {% data reusables.apps.navigate-to-app-settings-page %} @@ -166,7 +200,7 @@ Make sure that you are on a secure machine before performing these steps since y ## Add code for your {% data variables.product.prodname_github_app %} -This section will show you how to add some basic template code for your GitHub App, and it will explain what the code does. Later in the tutorial, you will learn how to modify and add to this code, to build out your app's functionality. +This section will show you how to add some basic template code for your {% data variables.product.prodname_github_app %}, and it will explain what the code does. Later in the tutorial, you will learn how to modify and add to this code, to build out your app's functionality. Add the following template code to your `server.rb` file: @@ -332,11 +366,11 @@ The rest of this section will explain what the template code does. There aren't Open up the `server.rb` file in a text editor. You'll see comments throughout the file that provide additional context for the template code. We recommend reading those comments carefully and even adding your own comments to accompany new code you write. -At the top of the file you'll see `set :port 3000`, which sets the port used when starting the web server to match the port you redirected your webhook payloads to in "[Step 1. Start a new Smee channel](#step-1-start-a-new-smee-channel)." +At the top of the file you'll see `set :port 3000`, which sets the port used when starting the web server to match the port you redirected your webhook payloads to in "[Get a Webhook Proxy URL](#get-a-webhook-proxy-url)." -The next code you'll see is the `class GHApp < Sinatra::Application` declaration. You'll write all of the code for your GitHub App inside this class. +The next code you'll see is the `class GHApp < Sinatra::Application` declaration. You'll write all of the code for your {% data variables.product.prodname_github_app %} inside this class. -Out of the box, the class in the template does the following things: +The class in the template does the following things: * [Read the environment variables](#read-the-environment-variables) * [Turn on logging](#turn-on-logging) * [Define a before filter](#define-a-before-filter) @@ -345,7 +379,7 @@ Out of the box, the class in the template does the following things: #### Read the environment variables -The first thing that this class does is read the three environment variables you set in "[Step 4. Prepare the runtime environment](#step-4-prepare-the-runtime-environment)" and store them in variables to use later: +First, this class reads the three environment variables you set in "[Store your app's identifying information and credentials](#store-your-apps-identifying-information-and-credentials)," and stores them in variables to use later: ``` ruby # Expects that the private key in PEM format. Converts the newlines @@ -361,7 +395,7 @@ APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] #### Turn on logging -Next is a code block that enables logging during development, which is the default environment in Sinatra. This code turns on logging at the `DEBUG` level to show useful output in the Terminal while you are developing the app: +Next is a code block that enables logging during development, which is the default environment in Sinatra. This code turns on logging at the `DEBUG` level to show useful output in the terminal while you are developing the app. ``` ruby # Turn on Sinatra's verbose logging during development @@ -387,7 +421,9 @@ end #### Define a route handler -An empty route is included in the template code. This code handles all `POST` requests to the `/event_handler` route. You won't write this event handler in this quickstart, but see the other [quickstart guides](/apps/creating-github-apps/guides) for examples of how to extend this template app. +An empty route is included in the template code. This code handles all `POST` requests to the `/event_handler` route. You will add more code to this later. + +TODOCS: Make sure we talk about adding more code to this later in the tutorial. ``` ruby post '/event_handler' do @@ -397,7 +433,7 @@ end #### Define the helper methods -The helper methods in this template do most of the heavy lifting. Four helper methods are defined in this section of the code. +Four helper methods are called in the `before` block of the template code. The `helpers do` block in the template defines each of these helper methods. ##### Handling the webhook payload @@ -405,25 +441,25 @@ The first method `get_payload_request` captures the webhook payload and converts ##### Verifying the webhook signature -The second method `verify_webhook_signature` performs verification of the webhook signature to ensure that GitHub generated the event. To learn more about the code in the `verify_webhook_signature` helper method, see "[AUTOTITLE](/webhooks-and-events/webhooks/securing-your-webhooks)." If the webhooks are secure, this method will log all incoming payloads to your Terminal. The logger code is helpful in verifying your web server is working but you can always remove it later. +The second method `verify_webhook_signature` performs verification of the webhook signature to ensure that {% data variables.product.prodname_dotcom %} generated the event. To learn more about the code in the `verify_webhook_signature` helper method, see "[AUTOTITLE](/webhooks-and-events/webhooks/securing-your-webhooks)." If the webhooks are secure, this method will log all incoming payloads to your terminal. The logger code is helpful in verifying your web server is working but you can always remove it later. -##### Authenticating as a GitHub App +##### Authenticating as a {% data variables.product.prodname_github_app %} -To make API calls, you'll be using the [Octokit library](https://octokit.github.io/octokit.rb/). Doing anything interesting with this library will require you, or rather your app, to authenticate. GitHub Apps have two methods of authentication: +To make API calls, you'll be using the [Octokit library](https://octokit.github.io/octokit.rb/). Doing anything interesting with this library will require your {% data variables.product.prodname_github_app %} to authenticate. {% data variables.product.prodname_github_apps %} have two methods of authentication: -- Authenticating as a GitHub App using a [JSON Web Token (JWT)](https://jwt.io/introduction). -- Authenticating as a specific installation of a GitHub App using an installation access token. +- Authenticating as a {% data variables.product.prodname_github_app %} using a [JSON Web Token (JWT)](https://jwt.io/introduction). +- Authenticating as a specific installation of a {% data variables.product.prodname_github_app %} using an installation access token. You'll learn about authenticating as an installation in the [next section](#authenticating-as-an-installation). -Authenticating as a GitHub App lets you do a couple of things: +Authenticating as a {% data variables.product.prodname_github_app %} lets you do a couple of things: - * You can retrieve high-level management information about your GitHub App. + * You can retrieve high-level management information about your {% data variables.product.prodname_github_app %}. * You can request access tokens for an installation of the app. -For example, you would authenticate as a GitHub App to retrieve a list of the accounts (organization and personal) that have installed your app. But this authentication method doesn't allow you to do much with the API. To access a repository's data and perform operations on behalf of the installation, you need to authenticate as an installation. To do that, you'll need to authenticate as a GitHub App first to request an installation access token. For more information, see "[AUTOTITLE](/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app)." +For example, you would authenticate as a {% data variables.product.prodname_github_app %} to retrieve a list of the accounts (organization and personal) that have installed your app. But this authentication method doesn't allow you to do much with the API. To access a repository's data and perform operations on behalf of the installation, you need to authenticate as an installation. To do that, you'll need to authenticate as a {% data variables.product.prodname_github_app %} first to request an installation access token. For more information, see "[AUTOTITLE](/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app)." -Before you can use the Octokit.rb library to make API calls, you'll need to initialize an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as a GitHub App. The `authenticate_app` helper method does just that! +Before you can use the Octokit.rb library to make API calls, you'll need to initialize an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as a GitHub App, using the `authenticate_app` helper method. ``` ruby # Instantiate an Octokit client authenticated as a GitHub App. @@ -451,7 +487,7 @@ def authenticate_app end ``` -The code above generates a [JSON Web Token (JWT)](https://jwt.io/introduction) and uses it (along with your app's private key) to initialize the Octokit client. GitHub checks a request's authentication by verifying the token with the app's stored public key. To learn more about how this code works, see "[AUTOTITLE](/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app)." +The code above generates a JSON Web Token (JWT) and uses it (along with your app's private key) to initialize the Octokit client. GitHub checks a request's authentication by verifying the token with the app's stored public key. To learn more about how this code works, see "[AUTOTITLE](/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app)." ##### Authenticating as an installation @@ -469,18 +505,18 @@ end The [`create_app_installation_access_token`](https://octokit.github.io/octokit.rb/Octokit/Client/Apps.html#create_app_installation_access_token-instance_method) Octokit method creates an installation token. This method accepts two arguments: -* Installation (integer): The ID of a GitHub App installation +* Installation (integer): The ID of a {% data variables.product.prodname_github_app %} installation * Options (hash, defaults to `{}`): A customizable set of options -Any time a GitHub App receives a webhook, it includes an `installation` object with an `id`. Using the client authenticated as a GitHub App, you pass this ID to the `create_app_installation_access_token` method to generate an access token for each installation. Since you're not passing any options to the method, the options default to an empty hash. The response for `create_app_installation_access_token` includes two fields: `token` and `expired_at`. The template code selects the token in the response and initializes an installation client. +Any time a {% data variables.product.prodname_github_app %} receives a webhook, it includes an `installation` object with an `id`. Using the client authenticated as a {% data variables.product.prodname_github_app %}, you pass this ID to the `create_app_installation_access_token` method to generate an access token for each installation. Since you're not passing any options to the method, the options default to an empty hash. The response for `create_app_installation_access_token` includes two fields: `token` and `expired_at`. The template code selects the token in the response and initializes an installation client. -With this method in place, each time your app receives a new webhook payload, it creates a client for the installation that triggered the event. This authentication process enables your GitHub App to work for all installations on any account. +With this method in place, each time your app receives a new webhook payload, it creates a client for the installation that triggered the event. This authentication process enables your {% data variables.product.prodname_github_app %} to work for all installations on any account. ## Start the server Your app doesn't do anything yet, but at this point, you can get it running on the server. -Keep Smee running in the current tab in your terminal. Open a new tab and `cd` into the directory where you [cloned the template app code](#prerequisites). The Ruby code in this repository will start up a [Sinatra](https://sinatrarb.com/) web server. This code has a few dependencies. You can install these by running: +Keep Smee running in the current tab in your terminal. Open a new tab and `cd` into the directory where you [cloned the template app code](#create-a-repository-to-store-code-for-your-app). The Ruby code in this repository will start up a [Sinatra](https://sinatrarb.com/) web server. This code has a few dependencies. You can install these by running: ```shell{:copy} gem install bundler @@ -518,7 +554,7 @@ This is good! Even though it's an error page, it's a Sinatra error page, which m ## Install the app on your account -You can test that the server is listening to your app by triggering an event for it to receive. A simple event you can test is installing the app on your GitHub account, which should send the [`installation`](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) event. If the app receives it, you should see some output in the Terminal tab where you started `template_server.rb`. +You can test that the server is listening to your app by triggering an event for it to receive. A simple event you can test is installing the app on your {% data variables.product.prodname_dotcom %} account, which should send the [`installation`](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) event. If the app receives it, you should see some output in the Terminal tab where you started `template_server.rb`. To install the app, visit the [app settings page](https://github.com/settings/apps), choose your app, and click **Install App** in the sidebar. Next to your username, click **Install**. @@ -535,7 +571,7 @@ After you click **Install**, look at the output in your Terminal. You should see > 192.30.252.39 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0019 ``` -This is good news! It means your app received a notification that it was installed on your GitHub account. If you see something like this, your app is running on the server as expected. +This is good news! It means your app received a notification that it was installed on your {% data variables.product.prodname_dotcom %} account. If you see something like this, your app is running on the server as expected. If you don't see the output, make sure Smee is running correctly in another Terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in Terminal. If Smee isn't the problem, see the "[Troubleshooting](#troubleshooting)" section for other ideas. @@ -543,11 +579,11 @@ If you're wondering where the terminal output above is coming from, it's written ## Part 1. Creating the Checks API interface -In this part, you will add the code necessary to receive `check_suite` webhook events and create and update check runs. You'll also learn how to create check runs when a check was re-requested on GitHub. At the end of this section, you'll be able to view the check run you created in a GitHub pull request. +In this part, you will add the code necessary to receive `check_suite` webhook events and create and update check runs. You'll also learn how to create check runs when a check was re-requested on {% data variables.product.prodname_dotcom %}. At the end of this section, you'll be able to view the check run you created in a {% data variables.product.prodname_dotcom %} pull request. Your check run will not be performing any checks on the code in this section. You'll add that functionality in [Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test). -You should already have a Smee channel configured that is forwarding webhook payloads to your local server. Your server should be running and connected to the GitHub App you registered and installed on a test repository. If you haven't completed the steps in "[AUTOTITLE](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app)," you'll need to do that before you can continue. +You should already have a Smee channel configured that is forwarding webhook payloads to your local server. Your server should be running and connected to the {% data variables.product.prodname_github_app %} you registered and installed on a test repository. If you haven't completed the steps in "[AUTOTITLE](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app)," you'll need to do that before you can continue. Let's get started! These are the steps you'll complete in Part 1: @@ -571,7 +607,7 @@ Great! Your app has permission to do the tasks you want it to do. Now you can ad ## Step 1.2. Adding event handling -Now that your app is subscribed to the **Check suite** and **Check run** events, it will start receiving the [`check_suite`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) and [`check_run`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) webhooks. GitHub sends webhook payloads as `POST` requests. Because you forwarded your Smee webhook payloads to `http://localhost:3000/event_handler`, your server will receive the `POST` request payloads at the `post '/event_handler'` route. +Now that your app is subscribed to the **Check suite** and **Check run** events, it will start receiving the [`check_suite`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) and [`check_run`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) webhooks. {% data variables.product.prodname_dotcom %} sends webhook payloads as `POST` requests. Because you forwarded your Smee webhook payloads to `http://localhost:3000/event_handler`, your server will receive the `POST` request payloads at the `post '/event_handler'` route. An empty `post '/event_handler'` route is already included in the `template_server.rb` file, which you downloaded in the [prerequisites](#prerequisites) section. The empty route looks like this: @@ -599,7 +635,7 @@ when 'check_suite' end ``` -Every event that GitHub sends includes a request header called `HTTP_X_GITHUB_EVENT`, which indicates the type of event in the `POST` request. Right now, you're only interested in events of type `check_suite`, which are emitted when a new check suite is created. Each event has an additional `action` field that indicates the type of action that triggered the events. For `check_suite`, the `action` field can be `requested`, `rerequested`, or `completed`. +Every event that {% data variables.product.prodname_dotcom %} sends includes a request header called `HTTP_X_GITHUB_EVENT`, which indicates the type of event in the `POST` request. Right now, you're only interested in events of type `check_suite`, which are emitted when a new check suite is created. Each event has an additional `action` field that indicates the type of action that triggered the events. For `check_suite`, the `action` field can be `requested`, `rerequested`, or `completed`. The `requested` action requests a check run each time code is pushed to the repository, while the `rerequested` action requests that you re-run a check for code that already exists in the repository. Because both the `requested` and `rerequested` actions require creating a check run, you'll call a helper called `create_check_run`. Let's write that method now. @@ -628,9 +664,9 @@ This code calls the "[AUTOTITLE](/rest/checks#create-a-check-run)" endpoint usin To create a check run, only two input parameters are required: `name` and `head_sha`. We will use [RuboCop](https://rubocop.readthedocs.io/en/latest/) to implement the CI test later in this quickstart, which is why the name "Octo RuboCop" is used here, but you can choose any name you'd like for the check run. -You're only supplying the required parameters now to get the basic functionality working, but you'll update the check run later as you collect more information about the check run. By default, GitHub sets the `status` to `queued`. +You're only supplying the required parameters now to get the basic functionality working, but you'll update the check run later as you collect more information about the check run. By default, {% data variables.product.prodname_dotcom %} sets the `status` to `queued`. -GitHub creates a check run for a specific commit SHA, which is why `head_sha` is a required parameter. You can find the commit SHA in the webhook payload. Although you're only creating a check run for the `check_suite` event right now, it's good to know that the `head_sha` is included in both the `check_suite` and `check_run` objects in the event payloads. +{% data variables.product.prodname_dotcom %} creates a check run for a specific commit SHA, which is why `head_sha` is a required parameter. You can find the commit SHA in the webhook payload. Although you're only creating a check run for the `check_suite` event right now, it's good to know that the `head_sha` is included in both the `check_suite` and `check_run` objects in the event payloads. In the code above, you're using the [ternary operator](https://ruby-doc.org/core-2.3.0/doc/syntax/control_expressions_rdoc.html#label-Ternary+if), which works like an `if/else` statement, to check if the payload contains a `check_run` object. If it does, you read the `head_sha` from the `check_run` object, otherwise you read it from the `check_suite` object. @@ -646,13 +682,13 @@ Now open a pull request in the repository where you installed your app. Your app If you see other apps in the Checks tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. -Great! You've told GitHub to create a check run. You can see the check run status is set to `queued` next to a yellow icon. Next, you'll want to wait for GitHub to create the check run and update its status. +Great! You've told {% data variables.product.prodname_dotcom %} to create a check run. You can see the check run status is set to `queued` next to a yellow icon. Next, you'll want to wait for {% data variables.product.prodname_dotcom %} to create the check run and update its status. ## Step 1.4. Updating a check run -When your `create_check_run` method runs, it asks GitHub to create a new check run. When GitHub finishes creating the check run, you'll receive the `check_run` webhook event with the `created` action. That event is your signal to begin running the check. +When your `create_check_run` method runs, it asks {% data variables.product.prodname_dotcom %} to create a new check run. When {% data variables.product.prodname_dotcom %} finishes creating the check run, you'll receive the `check_run` webhook event with the `created` action. That event is your signal to begin running the check. -You'll want to update your event handler to look for the `created` action. While you're updating the event handler, you can add a conditional for the `rerequested` action. When someone re-runs a single test on GitHub by clicking the "Re-run" button, GitHub sends the `rerequested` check run event to your app. When a check run is `rerequested`, you'll want to start the process all over and create a new check run. +You'll want to update your event handler to look for the `created` action. While you're updating the event handler, you can add a conditional for the `rerequested` action. When someone re-runs a single test on {% data variables.product.prodname_dotcom %} by clicking the "Re-run" button, {% data variables.product.prodname_dotcom %} sends the `rerequested` check run event to your app. When a check run is `rerequested`, you'll want to start the process all over and create a new check run. To include a condition for the `check_run` event in the `post '/event_handler'` route, add the following code under `case request.env['HTTP_X_GITHUB_EVENT']`: @@ -669,7 +705,7 @@ when 'check_run' end ``` -GitHub sends all events for `created` check runs to every app installed on a repository that has the necessary checks permissions. That means that your app will receive check runs created by other apps. A `created` check run is a little different from a `requested` or `rerequested` check suite, which GitHub sends only to apps that are being requested to run a check. The code above looks for the check run's application ID. This filters out all check runs for other apps on the repository. +{% data variables.product.prodname_dotcom %} sends all events for `created` check runs to every app installed on a repository that has the necessary checks permissions. That means that your app will receive check runs created by other apps. A `created` check run is a little different from a `requested` or `rerequested` check suite, which {% data variables.product.prodname_dotcom %} sends only to apps that are being requested to run a check. The code above looks for the check run's application ID. This filters out all check runs for other apps on the repository. Next you'll write the `initiate_check_run` method, which is where you'll update the check run status and prepare to kick off your CI test. @@ -728,13 +764,13 @@ Head over to your open pull request and click the **Checks** tab. Click the "Re- Now that you've got the interface created to receive Checks API events and create check runs, you can create a check run that implements a CI test. -Your app will run RuboCop on the CI server and create check runs (CI tests in this case) that report the results that RuboCop reports to GitHub. +Your app will run RuboCop on the CI server and create check runs (CI tests in this case) that report the results that RuboCop reports to {% data variables.product.prodname_dotcom %}. The Checks API allows you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. That information can be anything: for example, a comment, an error, or a warning. This quickstart uses annotations to visualize RuboCop errors. -To take advantage of requested actions, app developers can create buttons in the **Checks** tab of pull requests. When someone clicks one of these buttons, the click sends a `requested_action` `check_run` event to the GitHub App. The action that the app takes is completely configurable by the app developer. This quickstart will walk you through adding a button that allows users to request that RuboCop fix the errors it finds. RuboCop supports automatically fixing errors using a command-line option, and you'll configure the `requested_action` to take advantage of this option. +To take advantage of requested actions, app developers can create buttons in the **Checks** tab of pull requests. When someone clicks one of these buttons, the click sends a `requested_action` `check_run` event to the {% data variables.product.prodname_github_app %}. The action that the app takes is completely configurable by the app developer. This quickstart will walk you through adding a button that allows users to request that RuboCop fix the errors it finds. RuboCop supports automatically fixing errors using a command-line option, and you'll configure the `requested_action` to take advantage of this option. Let's get started! These are the steps you'll complete in this section: @@ -772,7 +808,7 @@ m.display ## Step 2.2. Cloning the repository -RuboCop is available as a command-line utility. That means your GitHub App will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. +RuboCop is available as a command-line utility. That means your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. The `Gemfile` in the `building-a-checks-api-ci-server` repository already includes the ruby-git gem, and you installed it when you ran `bundle install` in the [prerequisite steps](#prerequisites). To use the gem, add this code to the top of your `template_server.rb` file: @@ -780,13 +816,13 @@ The `Gemfile` in the `building-a-checks-api-ci-server` repository already includ require 'git' ``` -Your app needs read permission for "Repository contents" to clone a repository. Later in this quickstart, you'll need to push contents to GitHub, which requires write permission. Go ahead and set your app's "Repository contents" permission to **Read & write** now so you don't need to update it again later. To update your app's permissions: +Your app needs read permission for "Repository contents" to clone a repository. Later in this quickstart, you'll need to push contents to {% data variables.product.prodname_dotcom %}, which requires write permission. Go ahead and set your app's "Repository contents" permission to **Read & write** now so you don't need to update it again later. To update your app's permissions: 1. Select your app from the [app settings page](https://github.com/settings/apps) and click **Permissions & Webhooks** in the sidebar. 1. In the "Permissions" section, find "Repository contents", and select **Read & write** in the "Access" dropdown next to it. {% data reusables.apps.accept_new_permissions_steps %} -To clone a repository using your GitHub App's permissions, you can use the app's installation token (`x-access-token:`) shown in the example below: +To clone a repository using your {% data variables.product.prodname_github_app %}'s permissions, you can use the app's installation token (`x-access-token:`) shown in the example below: ```shell git clone https://x-access-token:@github.com//.git @@ -846,7 +882,7 @@ logger.debug @report The code above runs RuboCop on all files in the repository's directory. The option `--format json` is a handy way to save a copy of the linting results in a machine-parsable format. See the [RuboCop docs](https://docs.rubocop.org/rubocop/formatters.html#json-formatter) for details and an example of the JSON format. -Because this code stores the RuboCop results in a `@report` variable, it can safely remove the checkout of the repository. This code also parses the JSON so you can easily access the keys and values in your GitHub App using the `@output` variable. +Because this code stores the RuboCop results in a `@report` variable, it can safely remove the checkout of the repository. This code also parses the JSON so you can easily access the keys and values in your {% data variables.product.prodname_github_app %} using the `@output` variable. {% note %} @@ -993,7 +1029,7 @@ This code doesn't yet create an annotation for the check run. You'll add that co ## Step 2.5. Updating the check run with CI test results -Each check run from GitHub contains an `output` object that includes a `title`, `summary`, `text`, `annotations`, and `images`. The `summary` and `title` are the only required parameters for the `output`, but those alone don't offer much detail, so this quickstart adds `text` and `annotations` too. The code here doesn't add an image, but feel free to add one if you'd like! +Each check run from {% data variables.product.prodname_dotcom %} contains an `output` object that includes a `title`, `summary`, `text`, `annotations`, and `images`. The `summary` and `title` are the only required parameters for the `output`, but those alone don't offer much detail, so this quickstart adds `text` and `annotations` too. The code here doesn't add an image, but feel free to add one if you'd like! For the `summary`, this example uses the summary information from RuboCop and adds some newlines (`\n`) to format the output. You can customize what you add to the `text` parameter, but this example sets the `text` parameter to the RuboCop version. To set the `summary` and `text`, append this code to the code you added in the [previous section](#step-24-collecting-rubocop-errors): @@ -1042,7 +1078,7 @@ You'll need to update that code to use the `conclusion` variable you set based o Now that you're setting a conclusion based on the status of the CI test and you've added the output from the RuboCop results, you've created a CI test! Congratulations. 🙌 -The code above also adds a feature to your CI server called [requested actions](https://developer.github.com/changes/2018-05-23-request-actions-on-checks/) via the `actions` object. {% ifversion fpt or ghec %}(Note this is not related to [GitHub Actions](/actions).) {% endif %}Requested actions add a button in the **Checks** tab on GitHub that allows someone to request the check run to take additional action. The additional action is completely configurable by your app. For example, because RuboCop has a feature to automatically fix the errors it finds in Ruby code, your CI server can use a requested actions button to allow people to request automatic error fixes. When someone clicks the button, the app receives the `check_run` event with a `requested_action` action. Each requested action has an `identifier` that the app uses to determine which button was clicked. +The code above also adds a feature to your CI server called [requested actions](https://developer.github.com/changes/2018-05-23-request-actions-on-checks/) via the `actions` object. {% ifversion fpt or ghec %}(Note this is not related to [GitHub Actions](/actions).) {% endif %}Requested actions add a button in the **Checks** tab on {% data variables.product.prodname_dotcom %} that allows someone to request the check run to take additional action. The additional action is completely configurable by your app. For example, because RuboCop has a feature to automatically fix the errors it finds in Ruby code, your CI server can use a requested actions button to allow people to request automatic error fixes. When someone clicks the button, the app receives the `check_run` event with a `requested_action` action. Each requested action has an `identifier` that the app uses to determine which button was clicked. The code above doesn't have RuboCop automatically fix errors yet. You'll add that in the next section. But first, take a look at the CI test that you just created by starting up the `template_server.rb` server again and creating a new pull request: @@ -1058,7 +1094,7 @@ If the annotations are related to a file already included in the PR, the annotat If you've made it this far, kudos! 👏 You've already created a CI test. In this section, you'll add one more feature that uses RuboCop to automatically fix the errors it finds. You already added the "Fix this" button in the [previous section](#step-25-updating-the-check-run-with-ci-test-results). Now you'll add the code to handle the `requested_action` check run event triggered when someone clicks the "Fix this" button. -The RuboCop tool [offers](https://docs.rubocop.org/rubocop/usage/basic_usage.html#auto-correcting-offenses) the `--auto-correct` command-line option to automatically fix errors it finds. When you use the `--auto-correct` feature, the updates are applied to the local files on the server. You'll need to push the changes to GitHub after RuboCop does its magic. +The RuboCop tool [offers](https://docs.rubocop.org/rubocop/usage/basic_usage.html#auto-correcting-offenses) the `--auto-correct` command-line option to automatically fix errors it finds. When you use the `--auto-correct` feature, the updates are applied to the local files on the server. You'll need to push the changes to {% data variables.product.prodname_dotcom %} after RuboCop does its magic. To push to a repository, your app must have write permissions for "Repository contents." You set that permission back in [Step 2.2. Cloning the repository](#step-22-cloning-the-repository) to **Read & write**, so you're all set. @@ -1132,7 +1168,7 @@ end The code above clones a repository just like the code you added in [Step 2.2. Cloning the repository](#step-22-cloning-the-repository). An `if` statement checks that the requested action's identifier matches the RuboCop button identifier (`fix_rubocop_notices`). When they match, the code clones the repository, sets the Git username and email, and runs RuboCop with the option `--auto-correct`. The `--auto-correct` option applies the changes to the local CI server files automatically. -The files are changed locally, but you'll still need to push them to GitHub. You'll use the handy `ruby-git` gem again to commit all of the files. Git has a single command that stages all modified or deleted files and commits them: `git commit -a`. To do the same thing using `ruby-git`, the code above uses the `commit_all` method. Then the code pushes the committed files to GitHub using the installation token, using the same authentication method as the Git `clone` command. Finally, it removes the repository directory to ensure the working directory is prepared for the next event. +The files are changed locally, but you'll still need to push them to {% data variables.product.prodname_dotcom %}. You'll use the handy `ruby-git` gem again to commit all of the files. Git has a single command that stages all modified or deleted files and commits them: `git commit -a`. To do the same thing using `ruby-git`, the code above uses the `commit_all` method. Then the code pushes the committed files to {% data variables.product.prodname_dotcom %} using the installation token, using the same authentication method as the Git `clone` command. Finally, it removes the repository directory to ensure the working directory is prepared for the next event. That's it! The code you have written now completes your Checks API CI server. 💪 Restart your `template_server.rb` server again and create a new pull request: @@ -1146,13 +1182,13 @@ This time, click the "Fix this" button to automatically fix the errors RuboCop f In the **Commits** tab, you'll see a brand new commit by the username you set in your Git configuration. You may need to refresh your browser to see the update. -Because a new commit was pushed to the repo, you'll see a new check suite for Octo RuboCop in the **Checks** tab. But this time there are no errors because RuboCop fixed them all. 🎉 +Because a new commit was pushed to the repo, you'll see a new check suite for Octo RuboCop in the **Checks** tab. But this time there are no errors because RuboCop fixed them all. -You can find the completed code for the app you just built in the `server.rb` file in the [Creating CI tests with the Checks API](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) repository. +To see the full final code for the app you just built, see "[Full code example](#full-code-example)." ## Step 2.7. Security tips -The template GitHub App code already has a method to verify incoming webhook payloads to ensure they are from a trusted source. If you are not validating webhook payloads, you'll need to ensure that when repository names are included in the webhook payload, the webhook does not contain arbitrary commands that could be used maliciously. The code below validates that the repository name only contains Latin alphabetic characters, hyphens, and underscores. To provide you with a complete example, the complete `server.rb` code available in the [companion repository](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) for this quickstart includes both the method of validating incoming webhook payloads and this check to verify the repository name. +The template {% data variables.product.prodname_github_app %} code already has a method to verify incoming webhook payloads to ensure they are from a trusted source. If you are not validating webhook payloads, you'll need to ensure that when repository names are included in the webhook payload, the webhook does not contain arbitrary commands that could be used maliciously. The code below validates that the repository name only contains Latin alphabetic characters, hyphens, and underscores. To provide you with a complete example, the complete `server.rb` code available in the [companion repository](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) for this quickstart includes both the method of validating incoming webhook payloads and this check to verify the repository name. ``` ruby # This quickstart example uses the repository name in the webhook with @@ -1166,11 +1202,361 @@ unless @payload['repository'].nil? end ``` +## Full code example + +This is what the final code in `server.rb` should look like, after you've followed all of the steps in this tutorial. There are also comments throughout the code that provide additional context. + +```ruby{:copy} +require 'sinatra' +require 'octokit' +require 'dotenv/load' # Manages environment variables +require 'json' +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 +require 'git' + +set :port, 3000 +set :bind, '0.0.0.0' + +class GHAapp < Sinatra::Application + + # Converts the newlines. Expects that the private key has been set as an + # environment variable in PEM format. + PRIVATE_KEY = OpenSSL::PKey::RSA.new(ENV['GITHUB_PRIVATE_KEY'].gsub('\n', "\n")) + + # Your registered app must have a secret set. 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) set when registering an app. + 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 + + # This Quickstart example uses the repository name in the webhook with + # command line utilities. For security reasons, you should validate the + # repository name to ensure that a bad actor isn't attempting to execute + # arbitrary commands or inject false repository names. 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 the status queued + def create_check_run + # At the time of writing, Octokit does not support the Checks API, but + # it does provide generic HTTP methods you can use: + # https://developer.github.com/v3/checks/runs/#create-a-check-run + check_run = @installation_client.post( + "repos/#{@payload['repository']['full_name']}/check-runs", + { + # This header allows for beta access to Checks API + accept: 'application/vnd.github.antiope-preview+json', + # The name of your check run. + name: 'Octo RuboCop', + # The payload structure differs depending on whether a check run or a check suite event occurred. + head_sha: @payload['check_run'].nil? ? @payload['check_suite']['head_sha'] : @payload['check_run']['head_sha'] + } + ) + + # You requested the creation of a check run from GitHub. Now, you'll wait + # to get confirmation from GitHub, in the form of a webhook, that it was + # created before starting CI. Equivalently, a 201 response from + # POST /repos/:owner/:repo/check-runs could also be used as confirmation. + 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. + + # At the time of writing, Octokit doesn't support the Checks API, but + # it does provide generic HTTP methods you can use: + # https://developer.github.com/v3/checks/runs/#update-a-check-run + updated_check_run = @installation_client.patch( + "repos/#{@payload['repository']['full_name']}/check-runs/#{@payload['check_run']['id']}", + { + accept: 'application/vnd.github.antiope-preview+json', + name: 'Octo RuboCop', + status: 'in_progress', + started_at: Time.now.utc.iso8601 + } + ) + + # ***** RUN A CI TEST ***** + # Ideally this would be performed async, so you could return immediately. + # But for now you'll do a simulated CI process syncronously, and update + # the check run right here. + 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 https://developer.github.com/v3/checks/runs/#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, + 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. + updated_check_run = @installation_client.patch( + "repos/#{@payload['repository']['full_name']}/check-runs/#{@payload['check_run']['id']}", + { + accept: 'application/vnd.github.antiope-preview+json', + name: 'Octo RuboCop', + status: 'completed', + conclusion: conclusion, + completed_at: Time.now.utc.iso8601, + output: { + title: 'Octo RuboCop', + summary: summary, + text: text, + annotations: annotations + }, + actions: [{ + label: 'Fix this', + description: 'Automatically fix all linter notices.', + identifier: 'fix_rubocop_notices' + }] + } + ) + + end + + # Handles the check run `requested_action` event + # See https://developer.github.com/v3/activity/events/types/#checkrunevent + 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 + + # 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 + + # 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'. + # See https://developer.github.com/webhooks/securing/ for details. + 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 +``` + ## Troubleshooting Here are a few common problems and some suggested solutions. If you run into any other trouble, you can ask for help or advice in the {% data reusables.support.prodname_support_forum_with_url %}. -* **Q:** My app isn't pushing code to GitHub. I don't see the fixes that RuboCop automatically makes! +* **Q:** My app isn't pushing code to {% data variables.product.prodname_dotcom %}. I don't see the fixes that RuboCop automatically makes! **A:** Make sure you have **Read & write** permissions for "Repository contents," and that you are cloning the repository with your installation token. See [Step 2.2. Cloning the repository](#step-22-cloning-the-repository) for details. @@ -1184,11 +1570,11 @@ Here are a few common problems and some suggested solutions. If you run into any Compare your code to the `server.rb` file to ensure you have the same code in your `initiate_check_run` and `take_requested_action` methods. -* **Q:** New check runs are not showing up in the "Checks" tab on GitHub. +* **Q:** New check runs are not showing up in the "Checks" tab on {% data variables.product.prodname_dotcom %}. **A:** Restart Smee and re-run your `template_server.rb` server. -* **Q:** I do not see the "Re-run all" button in the "Checks" tab on GitHub. +* **Q:** I do not see the "Re-run all" button in the "Checks" tab on {% data variables.product.prodname_dotcom %}. **A:** Restart Smee and re-run your `template_server.rb` server. From 88a3d170fe282b0e0fe1f9511582805debc99586 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Tue, 6 Jun 2023 14:59:46 -0600 Subject: [PATCH 07/48] More content updates --- .../building-ci-checks-with-a-github-app.md | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 056b3748d671..fd6edee4b360 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -537,13 +537,8 @@ bundle exec ruby server.rb You should see a response like: ```shell -> == Sinatra (v2.0.3) has taken the stage on 3000 for development with backup from Puma -> Puma starting in single mode... -> * Version 3.11.2 (ruby 2.4.0-p0), codename: Love Song -> * Min threads: 0, max threads: 16 -> * Environment: development -> * Listening on tcp://localhost:3000 -> Use Ctrl-C to stop +> == Sinatra (v2.2.3) has taken the stage on 3000 for development with backup from WEBrick +> [2023-06-06 10:55:25] INFO WEBrick::HTTPServer#start: pid=93165 port=3000 ``` If you see an error, make sure you've created the `.env` file in the directory that contains `server.rb`. @@ -552,28 +547,25 @@ Once the server is running, you can test it by going to `http://localhost:3000` This is good! Even though it's an error page, it's a Sinatra error page, which means your app is connected to the server as expected. You're seeing this message because you haven't given the app anything else to show. -## Install the app on your account +## Test that the server is listening to your app -You can test that the server is listening to your app by triggering an event for it to receive. A simple event you can test is installing the app on your {% data variables.product.prodname_dotcom %} account, which should send the [`installation`](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) event. If the app receives it, you should see some output in the Terminal tab where you started `template_server.rb`. +You can test that the server is listening to your app by triggering an event for it to receive. A simple event you can test is installing the app on your {% data variables.product.prodname_dotcom %} account, which should send the [`installation`](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) event. If the app receives it, you should see some output in the terminal tab where you started `server.rb`. -To install the app, visit the [app settings page](https://github.com/settings/apps), choose your app, and click **Install App** in the sidebar. Next to your username, click **Install**. +1. Install the {% data variables.product.prodname_github_app %} on your account. For more information, see "[AUTOTITLE](/apps/using-github-apps/installing-your-own-github-app#installing-your-own-github-app)." You can choose to install it on all of your repositories, or just one. For example, you could install it on the repository you created for this tutorial. +2. After you click **Install**, look at the output in the terminal tab where you started `server.rb`. You should see something like this: -You'll be asked whether to install the app on all repositories or selected repositories. If you don't want to install the app on _all_ of your repositories, that's okay! You may want to create a sandbox repository for testing purposes and install your app there. - -After you click **Install**, look at the output in your Terminal. You should see something like this: - -```shell -> D, [2018-06-29T15:45:43.773077 #30488] DEBUG -- : ---- received event integration_installation -> D, [2018-06-29T15:45:43.773141 #30488] DEBUG -- : ---- action created -> 192.30.252.44 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0067 -> D, [2018-06-29T15:45:43.833016 #30488] DEBUG -- : ---- received event installation -> D, [2018-06-29T15:45:43.833062 #30488] DEBUG -- : ---- action created -> 192.30.252.39 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0019 -``` + ```shell + > D, [2018-06-29T15:45:43.773077 #30488] DEBUG -- : ---- received event integration_installation + > D, [2018-06-29T15:45:43.773141 #30488] DEBUG -- : ---- action created + > 192.30.252.44 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0067 + > D, [2018-06-29T15:45:43.833016 #30488] DEBUG -- : ---- received event installation + > D, [2018-06-29T15:45:43.833062 #30488] DEBUG -- : ---- action created + > 192.30.252.39 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0019 + ``` -This is good news! It means your app received a notification that it was installed on your {% data variables.product.prodname_dotcom %} account. If you see something like this, your app is running on the server as expected. + If you see output like this, it means your app received a notification that it was installed on your {% data variables.product.prodname_dotcom %} account. The app is running on the server as expected. -If you don't see the output, make sure Smee is running correctly in another Terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in Terminal. If Smee isn't the problem, see the "[Troubleshooting](#troubleshooting)" section for other ideas. +If you don't see this output, make sure Smee is running correctly in another terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in terminal. If Smee isn't the problem, see the "[Troubleshooting](#troubleshooting)" section for other ideas. [TODOCS: Remove troubleshooting link if we remove troubleshooting section.] If you're wondering where the terminal output above is coming from, it's written in the [app template code](#add-code-for-your-app) in `server.rb`. From 619dfad8bf937bb73fe4b8eabf56d0881603fe9e Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 8 Jun 2023 12:57:57 -0600 Subject: [PATCH 08/48] More content updates --- .../building-ci-checks-with-a-github-app.md | 128 ++++++++---------- .../apps/sinatra_restart_instructions.md | 2 +- 2 files changed, 56 insertions(+), 74 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index fd6edee4b360..7b278b010904 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -99,7 +99,6 @@ The following sections will lead you through setting up the following components gem 'sinatra', '~> 2.0' gem 'jwt', '~> 2.1' gem 'octokit', '~> 4.0' - gem 'webrick' gem 'rubocop' gem 'dotenv' gem 'git' @@ -118,11 +117,11 @@ In order to develop your app locally, you can use a webhook proxy URL to forward 1. Click **Start a new channel**. 1. Copy the full URL under "Webhook Proxy URL". You will use this URL in a following step, and during the app registration steps later in the tutorial. 1. In a terminal, run the following command to install the Smee client: - ```shell + ```shell{:copy} npm install --global smee-client ``` 1. In the terminal, run the following command to start the Smee client. Replace `https://smee.io/YOUR_DOMAIN` with the Webhook Proxy URL you copied in the previous step. - ```shell + ```shell{:copy} smee --url https://smee.io/YOUR_DOMAIN --path /event_handler --port 3000 ``` You should see output like the following: @@ -219,21 +218,15 @@ set :bind, '0.0.0.0' # This is template code to create a GitHub App server. -# You can read more about GitHub Apps here: # https://developer.github.com/apps/ +# You can read more about GitHub Apps here: https://docs.github.com/en/apps # # On its own, this app does absolutely nothing, except that it can be installed. -# It's up to you to add functionality! -# You can check out one example in advanced_server.rb. +# This tutorial will show you how to add more functionality. # # 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. # -# Of course, not all apps need to receive and process events! -# Feel free to rip out the event handling code if you don't need it. -# -# Have fun! -# class GHAapp < Sinatra::Application @@ -265,9 +258,7 @@ class GHAapp < Sinatra::Application post '/event_handler' do - # # # # # # # # # # # # - # ADD YOUR CODE HERE # - # # # # # # # # # # # # + # ADD YOUR CODE HERE # 200 # success status end @@ -275,9 +266,7 @@ class GHAapp < Sinatra::Application helpers do - # # # # # # # # # # # # # # # # # - # ADD YOUR HELPER METHODS HERE # - # # # # # # # # # # # # # # # # # + # ADD YOUR HELPER METHODS HERE # # Saves the raw payload and converts the payload to JSON format def get_payload_request(request) @@ -537,15 +526,22 @@ bundle exec ruby server.rb You should see a response like: ```shell -> == Sinatra (v2.2.3) has taken the stage on 3000 for development with backup from WEBrick -> [2023-06-06 10:55:25] INFO WEBrick::HTTPServer#start: pid=93165 port=3000 +> == 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 ``` If you see an error, make sure you've created the `.env` file in the directory that contains `server.rb`. -Once the server is running, you can test it by going to `http://localhost:3000` in your browser. If the app works as expected, you'll see a helpful error page that says, "Sinatra doesn't know this ditty." +Once the server is running, test it by going to `http://localhost:3000` in your browser. If you see an error page that says "Sinatra doesn't know this ditty," the app is working as expected. -This is good! Even though it's an error page, it's a Sinatra error page, which means your app is connected to the server as expected. You're seeing this message because you haven't given the app anything else to show. +Even though it's an error page, it's a Sinatra error page, which means your app is connected to the server as expected. You're seeing this message because you haven't given the app anything else to show. ## Test that the server is listening to your app @@ -555,19 +551,19 @@ You can test that the server is listening to your app by triggering an event for 2. After you click **Install**, look at the output in the terminal tab where you started `server.rb`. You should see something like this: ```shell - > D, [2018-06-29T15:45:43.773077 #30488] DEBUG -- : ---- received event integration_installation - > D, [2018-06-29T15:45:43.773141 #30488] DEBUG -- : ---- action created - > 192.30.252.44 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0067 - > D, [2018-06-29T15:45:43.833016 #30488] DEBUG -- : ---- received event installation - > D, [2018-06-29T15:45:43.833062 #30488] DEBUG -- : ---- action created - > 192.30.252.39 - - [29/Jun/2018:15:45:43 -0400] "POST / HTTP/2" 200 2 0.0019 + > D, [2023-06-08T15:45:43.773077 #30488] DEBUG -- : ---- received event integration_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 + > D, [2023-06-08T15:45:43.833016 #30488] DEBUG -- : ---- received event installation + > D, [2023-06-08T15:45:43.833062 #30488] DEBUG -- : ---- action created + > 192.30.252.39 - - [08/Jun/2023:15:45:43 -0400] "POST /event_handler HTTP/1.1" 200 - 0.5390 ``` If you see output like this, it means your app received a notification that it was installed on your {% data variables.product.prodname_dotcom %} account. The app is running on the server as expected. If you don't see this output, make sure Smee is running correctly in another terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in terminal. If Smee isn't the problem, see the "[Troubleshooting](#troubleshooting)" section for other ideas. [TODOCS: Remove troubleshooting link if we remove troubleshooting section.] -If you're wondering where the terminal output above is coming from, it's written in the [app template code](#add-code-for-your-app) in `server.rb`. +If you're wondering where the terminal output above is coming from, it's written in the app template code you added to `server.rb` in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your-github-app)". ## Part 1. Creating the Checks API interface @@ -575,48 +571,32 @@ In this part, you will add the code necessary to receive `check_suite` webhook e Your check run will not be performing any checks on the code in this section. You'll add that functionality in [Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test). -You should already have a Smee channel configured that is forwarding webhook payloads to your local server. Your server should be running and connected to the {% data variables.product.prodname_github_app %} you registered and installed on a test repository. If you haven't completed the steps in "[AUTOTITLE](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app)," you'll need to do that before you can continue. +You should already have a Smee channel configured that is forwarding webhook payloads to your local server. Your server should be running and connected to the {% data variables.product.prodname_github_app %} you registered and installed on a test repository. Let's get started! These are the steps you'll complete in Part 1: -1. [Updating app permissions](#step-11-updating-app-permissions) -1. [Adding event handling](#step-12-adding-event-handling) -1. [Creating a check run](#step-13-creating-a-check-run) -1. [Updating a check run](#step-14-updating-a-check-run) - -## Step 1.1. Updating app permissions - -When you [first registered your app](#prerequisites), you accepted the default permissions, which means your app doesn't have access to most resources. For this example, your app will need permission to read and write checks. - -To update your app's permissions: - -1. Select your app from the [app settings page](https://github.com/settings/apps) and click **Permissions & Webhooks** in the sidebar. -1. In the "Permissions" section, find "Checks", and select **Read & write** in the Access dropdown next to it. -1. In the "Subscribe to events" section, select **Check suite** and **Check run** to subscribe to these events. -{% data reusables.apps.accept_new_permissions_steps %} - -Great! Your app has permission to do the tasks you want it to do. Now you can add the code to handle the events. +1. [Add event handling](#step-11-add-event-handling) +1. [Create a check run](#step-12-create-a-check-run) +1. [Update a check run](#step-13-update-a-check-run) -## Step 1.2. Adding event handling +## Step 1.1. Add event handling -Now that your app is subscribed to the **Check suite** and **Check run** events, it will start receiving the [`check_suite`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) and [`check_run`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) webhooks. {% data variables.product.prodname_dotcom %} sends webhook payloads as `POST` requests. Because you forwarded your Smee webhook payloads to `http://localhost:3000/event_handler`, your server will receive the `POST` request payloads at the `post '/event_handler'` route. +Because your app is subscribed to the **Check suite** and **Check run** events, it will receive the [`check_suite`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) and [`check_run`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) webhooks. {% data variables.product.prodname_dotcom %} sends webhook payloads as `POST` requests. Because you forwarded your Smee webhook payloads to `http://localhost:3000/event_handler`, your server will receive the `POST` request payloads at the `post '/event_handler'` route. -An empty `post '/event_handler'` route is already included in the `template_server.rb` file, which you downloaded in the [prerequisites](#prerequisites) section. The empty route looks like this: +An empty `post '/event_handler'` route is already included in the template code in the `server.rb` file that you created in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your--data-variablesproductprodname_github_app)." The empty route looks like this: ``` ruby post '/event_handler' do - # # # # # # # # # # # # # ADD YOUR CODE HERE # - # # # # # # # # # # # # 200 # success status end ``` -Use this route to handle the `check_suite` event by adding the following code: +Use this route to handle the `check_suite` event by adding the following code. Under `post '/event_handler' do`, where it says `# ADD YOUR CODE HERE #`, add the following code: -``` ruby +``` ruby{:copy} # Get the event type from the HTTP_X_GITHUB_EVENT header case request.env['HTTP_X_GITHUB_EVENT'] when 'check_suite' @@ -631,11 +611,13 @@ Every event that {% data variables.product.prodname_dotcom %} sends includes a r The `requested` action requests a check run each time code is pushed to the repository, while the `rerequested` action requests that you re-run a check for code that already exists in the repository. Because both the `requested` and `rerequested` actions require creating a check run, you'll call a helper called `create_check_run`. Let's write that method now. -## Step 1.3. Creating a check run +## Step 1.2. Create a check run -You'll add this new method as a [Sinatra helper](https://github.com/sinatra/sinatra#helpers) in case you want other routes to use it too. Under `helpers do`, add this `create_check_run` method: +You'll add this new method as a [Sinatra helper](https://github.com/sinatra/sinatra#helpers) in case you want other routes to use it too. -``` ruby +Under `helpers do`, where it says `# ADD YOUR HELPER METHODS HERE #`, add this `create_check_run` method: + +``` ruby{:copy} # Create a new check run with status "queued" def create_check_run @installation_client.create_check_run( @@ -652,9 +634,9 @@ def create_check_run end ``` -This code calls the "[AUTOTITLE](/rest/checks#create-a-check-run)" endpoint using the [create_check_run method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#create_check_run-instance_method). +This code calls the "[AUTOTITLE](/rest/checks#create-a-check-run)" endpoint using the Octokit [create_check_run method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#create_check_run-instance_method). -To create a check run, only two input parameters are required: `name` and `head_sha`. We will use [RuboCop](https://rubocop.readthedocs.io/en/latest/) to implement the CI test later in this quickstart, which is why the name "Octo RuboCop" is used here, but you can choose any name you'd like for the check run. +To create a check run, only two input parameters are required: `name` and `head_sha`. We will use [RuboCop](https://rubocop.readthedocs.io/en/latest/) to implement the CI test later in this tutorial, which is why the name "Octo RuboCop" is used here, but you can choose any name you'd like for the check run. You're only supplying the required parameters now to get the basic functionality working, but you'll update the check run later as you collect more information about the check run. By default, {% data variables.product.prodname_dotcom %} sets the `status` to `queued`. @@ -664,8 +646,8 @@ In the code above, you're using the [ternary operator](https://ruby-doc.org/core To test this code, restart the server from your terminal: -```shell -$ ruby template_server.rb +```shell{:copy} +ruby server.rb ``` {% data reusables.apps.sinatra_restart_instructions %} @@ -676,7 +658,7 @@ If you see other apps in the Checks tab, it means you have other apps installed Great! You've told {% data variables.product.prodname_dotcom %} to create a check run. You can see the check run status is set to `queued` next to a yellow icon. Next, you'll want to wait for {% data variables.product.prodname_dotcom %} to create the check run and update its status. -## Step 1.4. Updating a check run +## Step 1.3. Updating a check run When your `create_check_run` method runs, it asks {% data variables.product.prodname_dotcom %} to create a new check run. When {% data variables.product.prodname_dotcom %} finishes creating the check run, you'll receive the `check_run` webhook event with the `created` action. That event is your signal to begin running the check. @@ -734,7 +716,7 @@ end The code above calls the "[AUTOTITLE](/rest/checks#update-a-check-run)" API endpoint using the [`update_check_run` Octokit method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#update_check_run-instance_method) to update the check run that you already created. -Here's what this code is doing. First, it updates the check run's status to `in_progress` and implicitly sets the `started_at` time to the current time. In [Part 2](#part-2-creating-the-octo-rubocop-ci-test) of this quickstart, you'll add code that kicks off a real CI test under `***** RUN A CI TEST *****`. For now, you'll leave that section as a placeholder, so the code that follows it will just simulate that the CI process succeeds and all tests pass. Finally, the code updates the status of the check run again to `completed`. +Here's what this code is doing. First, it updates the check run's status to `in_progress` and implicitly sets the `started_at` time to the current time. In [Part 2](#part-2-creating-the-octo-rubocop-ci-test) of this tutorial, you'll add code that kicks off a real CI test under `***** RUN A CI TEST *****`. For now, you'll leave that section as a placeholder, so the code that follows it will just simulate that the CI process succeeds and all tests pass. Finally, the code updates the status of the check run again to `completed`. You'll notice in the "[AUTOTITLE](/rest/checks#update-a-check-run)" docs that when you provide a status of `completed`, the `conclusion` and `completed_at` parameters are required. The `conclusion` summarizes the outcome of a check run and can be `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `skipped`, or `action_required`. You'll set the conclusion to `success`, the `completed_at` time to the current time, and the status to `completed`. @@ -760,9 +742,9 @@ Your app will run RuboCop on the CI server and create check runs (CI tests in th The Checks API allows you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. -Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. That information can be anything: for example, a comment, an error, or a warning. This quickstart uses annotations to visualize RuboCop errors. +Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. That information can be anything: for example, a comment, an error, or a warning. This tutorial uses annotations to visualize RuboCop errors. -To take advantage of requested actions, app developers can create buttons in the **Checks** tab of pull requests. When someone clicks one of these buttons, the click sends a `requested_action` `check_run` event to the {% data variables.product.prodname_github_app %}. The action that the app takes is completely configurable by the app developer. This quickstart will walk you through adding a button that allows users to request that RuboCop fix the errors it finds. RuboCop supports automatically fixing errors using a command-line option, and you'll configure the `requested_action` to take advantage of this option. +To take advantage of requested actions, app developers can create buttons in the **Checks** tab of pull requests. When someone clicks one of these buttons, the click sends a `requested_action` `check_run` event to the {% data variables.product.prodname_github_app %}. The action that the app takes is completely configurable by the app developer. This tutorial will walk you through adding a button that allows users to request that RuboCop fix the errors it finds. RuboCop supports automatically fixing errors using a command-line option, and you'll configure the `requested_action` to take advantage of this option. Let's get started! These are the steps you'll complete in this section: @@ -776,7 +758,7 @@ Let's get started! These are the steps you'll complete in this section: ## Step 2.1. Adding a Ruby file -You can pass specific files or entire directories for RuboCop to check. In this quickstart, you'll run RuboCop on an entire directory. Because RuboCop only checks Ruby code, you'll want at least one Ruby file in your repository that contains errors. The example file provided below contains a few errors. Add this example Ruby file to the repository where your app is installed (make sure to name the file with an `.rb` extension, as in `myfile.rb`): +You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. Because RuboCop only checks Ruby code, you'll want at least one Ruby file in your repository that contains errors. The example file provided below contains a few errors. Add this example Ruby file to the repository where your app is installed (make sure to name the file with an `.rb` extension, as in `myfile.rb`): ```ruby # The Octocat class tells you about different breeds of Octocat @@ -808,7 +790,7 @@ The `Gemfile` in the `building-a-checks-api-ci-server` repository already includ require 'git' ``` -Your app needs read permission for "Repository contents" to clone a repository. Later in this quickstart, you'll need to push contents to {% data variables.product.prodname_dotcom %}, which requires write permission. Go ahead and set your app's "Repository contents" permission to **Read & write** now so you don't need to update it again later. To update your app's permissions: +Your app needs read permission for "Repository contents" to clone a repository. Later in this tutorial, you'll need to push contents to {% data variables.product.prodname_dotcom %}, which requires write permission. Go ahead and set your app's "Repository contents" permission to **Read & write** now so you don't need to update it again later. To update your app's permissions: 1. Select your app from the [app settings page](https://github.com/settings/apps) and click **Permissions & Webhooks** in the sidebar. 1. In the "Permissions" section, find "Repository contents", and select **Read & write** in the "Access" dropdown next to it. @@ -948,7 +930,7 @@ You should see the linting errors in the debug output, although they aren't prin The `@output` variable contains the parsed JSON results of the RuboCop report. As shown above, the results contain a `summary` section that your code can use to quickly determine if there are any errors. The following code will set the check run conclusion to `success` when there are no reported errors. RuboCop reports errors for each file in the `files` array, so if there are errors, you'll need to extract some data from the file object. -The Checks API allows you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this quickstart you are [updating the check run](/rest/checks#update-a-check-run) with annotations. +The Checks API allows you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this tutorial you are [updating the check run](/rest/checks#update-a-check-run) with annotations. The Checks API limits the number of annotations to a maximum of 50 per API request. To create more than 50 annotations, you have to make multiple requests to the [AUTOTITLE](/rest/checks#update-a-check-run) endpoint. For example, to create 105 annotations you'd need to call the [AUTOTITLE](/rest/checks#update-a-check-run) endpoint three times. The first two requests would each have 50 annotations, and the third request would include the five remaining annotations. Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. @@ -1013,7 +995,7 @@ This code limits the total number of annotations to 50. But you can modify this When the `offense_count` is zero, the CI test is a `success`. If there are errors, this code sets the conclusion to `neutral` in order to prevent strictly enforcing errors from code linters. But you can change the conclusion to `failure` if you would like to ensure that the check suite fails when there are linting errors. -When errors are reported, the code above iterates through the `files` array in the RuboCop report. For each file, it extracts the file path and sets the annotation level to `notice`. You could go even further and set specific warning levels for each type of [RuboCop Cop](https://docs.rubocop.org/rubocop/cops.html), but to keep things simpler in this quickstart, all errors are set to a level of `notice`. +When errors are reported, the code above iterates through the `files` array in the RuboCop report. For each file, it extracts the file path and sets the annotation level to `notice`. You could go even further and set specific warning levels for each type of [RuboCop Cop](https://docs.rubocop.org/rubocop/cops.html), but to keep things simpler in this tutorial, all errors are set to a level of `notice`. This code also iterates through each error in the `offenses` array and collects the location of the offense and error message. After extracting the information needed, the code creates an annotation for each error and stores it in the `annotations` array. Because annotations only support start and end columns on the same line, `start_column` and `end_column` are only added to the `annotation` object if the start and end line values are the same. @@ -1021,7 +1003,7 @@ This code doesn't yet create an annotation for the check run. You'll add that co ## Step 2.5. Updating the check run with CI test results -Each check run from {% data variables.product.prodname_dotcom %} contains an `output` object that includes a `title`, `summary`, `text`, `annotations`, and `images`. The `summary` and `title` are the only required parameters for the `output`, but those alone don't offer much detail, so this quickstart adds `text` and `annotations` too. The code here doesn't add an image, but feel free to add one if you'd like! +Each check run from {% data variables.product.prodname_dotcom %} contains an `output` object that includes a `title`, `summary`, `text`, `annotations`, and `images`. The `summary` and `title` are the only required parameters for the `output`, but those alone don't offer much detail, so this tutorial adds `text` and `annotations` too. The code here doesn't add an image, but feel free to add one if you'd like! For the `summary`, this example uses the summary information from RuboCop and adds some newlines (`\n`) to format the output. You can customize what you add to the `text` parameter, but this example sets the `text` parameter to the RuboCop version. To set the `summary` and `text`, append this code to the code you added in the [previous section](#step-24-collecting-rubocop-errors): @@ -1031,7 +1013,7 @@ summary = "Octo RuboCop summary\n-Offense count: #{@output['summary']['offense_c text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}" ``` -Now you've got all the information you need to update your check run. In the [first half of this quickstart](#step-14-updating-a-check-run), you added this code to set the status of the check run to `success`: +Now you've got all the information you need to update your check run. In the [first half of this tutorial](#step-14-updating-a-check-run), you added this code to set the status of the check run to `success`: ``` ruby # Mark the check run as complete! @@ -1180,10 +1162,10 @@ To see the full final code for the app you just built, see "[Full code example]( ## Step 2.7. Security tips -The template {% data variables.product.prodname_github_app %} code already has a method to verify incoming webhook payloads to ensure they are from a trusted source. If you are not validating webhook payloads, you'll need to ensure that when repository names are included in the webhook payload, the webhook does not contain arbitrary commands that could be used maliciously. The code below validates that the repository name only contains Latin alphabetic characters, hyphens, and underscores. To provide you with a complete example, the complete `server.rb` code available in the [companion repository](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) for this quickstart includes both the method of validating incoming webhook payloads and this check to verify the repository name. +The template {% data variables.product.prodname_github_app %} code already has a method to verify incoming webhook payloads to ensure they are from a trusted source. If you are not validating webhook payloads, you'll need to ensure that when repository names are included in the webhook payload, the webhook does not contain arbitrary commands that could be used maliciously. The code below validates that the repository name only contains Latin alphabetic characters, hyphens, and underscores. To provide you with a complete example, the complete `server.rb` code available in the [companion repository](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) for this tutorial includes both the method of validating incoming webhook payloads and this check to verify the repository name. ``` ruby -# This quickstart example uses the repository name in the webhook with +# This tutorial example uses the repository name in the webhook with # command-line utilities. For security reasons, you should validate the # repository name to ensure that a bad actor isn't attempting to execute # arbitrary commands or inject false repository names. If a repository name @@ -1236,7 +1218,7 @@ class GHAapp < Sinatra::Application get_payload_request(request) verify_webhook_signature - # This Quickstart example uses the repository name in the webhook with + # This tutorial example uses the repository name in the webhook with # command line utilities. For security reasons, you should validate the # repository name to ensure that a bad actor isn't attempting to execute # arbitrary commands or inject false repository names. If a repository name diff --git a/data/reusables/apps/sinatra_restart_instructions.md b/data/reusables/apps/sinatra_restart_instructions.md index 837d16d08ff0..bd71223936a8 100644 --- a/data/reusables/apps/sinatra_restart_instructions.md +++ b/data/reusables/apps/sinatra_restart_instructions.md @@ -1,5 +1,5 @@ {% note %} -**Note:** You'll need to restart the Sinatra server before you can test changes. Enter `Ctrl-C` to stop the server, and then run `ruby template_server.rb` again. If you don't want to do this every time you change your app code, you can look into [reloading](http://sinatrarb.com/faq.html#reloading). +**Note:** You'll need to restart the Sinatra server before you can test changes. Enter `Ctrl-C` to stop the server, and then run `ruby server.rb` again. If you don't want to do this every time you change your app code, you can look into [reloading](http://sinatrarb.com/faq.html#reloading). {% endnote %} From c3f3269a8adc71d54afcca34eaf65b2a4f8f6d55 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 8 Jun 2023 13:04:37 -0600 Subject: [PATCH 09/48] Move lone reusable into main text --- .../building-ci-checks-with-a-github-app.md | 6 +++++- data/reusables/apps/sinatra_restart_instructions.md | 5 ----- 2 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 data/reusables/apps/sinatra_restart_instructions.md diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 7b278b010904..bbeacb813b87 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -650,7 +650,11 @@ To test this code, restart the server from your terminal: ruby server.rb ``` -{% data reusables.apps.sinatra_restart_instructions %} +{% note %} + +**Note:** You'll need to restart the Sinatra server before you can test changes. Enter `Ctrl-C` to stop the server, and then run `ruby server.rb` again. If you don't want to do this every time you change your app code, you can look into [reloading](http://sinatrarb.com/faq.html#reloading). + +{% endnote %} Now open a pull request in the repository where you installed your app. Your app should respond by creating a check run on your pull request. Click on the **Checks** tab, and you should see a check run with the name "Octo RuboCop", or whichever name you chose earlier for the check run. diff --git a/data/reusables/apps/sinatra_restart_instructions.md b/data/reusables/apps/sinatra_restart_instructions.md deleted file mode 100644 index bd71223936a8..000000000000 --- a/data/reusables/apps/sinatra_restart_instructions.md +++ /dev/null @@ -1,5 +0,0 @@ -{% note %} - -**Note:** You'll need to restart the Sinatra server before you can test changes. Enter `Ctrl-C` to stop the server, and then run `ruby server.rb` again. If you don't want to do this every time you change your app code, you can look into [reloading](http://sinatrarb.com/faq.html#reloading). - -{% endnote %} From b3ada70890c9626b90968e770511fbaf831cd2c2 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:10:55 -0600 Subject: [PATCH 10/48] More content updates --- .../building-ci-checks-with-a-github-app.md | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index bbeacb813b87..960852b8dab0 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -99,6 +99,7 @@ The following sections will lead you through setting up the following components gem 'sinatra', '~> 2.0' gem 'jwt', '~> 2.1' gem 'octokit', '~> 4.0' + gem 'puma' gem 'rubocop' gem 'dotenv' gem 'git' @@ -551,12 +552,9 @@ You can test that the server is listening to your app by triggering an event for 2. After you click **Install**, look at the output in the terminal tab where you started `server.rb`. You should see something like this: ```shell - > D, [2023-06-08T15:45:43.773077 #30488] DEBUG -- : ---- received event integration_installation - > D, [2023-06-08T15:45:43.773141 #30488] DEBUG -- : ---- action created + > 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 - > D, [2023-06-08T15:45:43.833016 #30488] DEBUG -- : ---- received event installation - > D, [2023-06-08T15:45:43.833062 #30488] DEBUG -- : ---- action created - > 192.30.252.39 - - [08/Jun/2023:15:45:43 -0400] "POST /event_handler HTTP/1.1" 200 - 0.5390 ``` If you see output like this, it means your app received a notification that it was installed on your {% data variables.product.prodname_dotcom %} account. The app is running on the server as expected. @@ -594,7 +592,7 @@ An empty `post '/event_handler'` route is already included in the template code end ``` -Use this route to handle the `check_suite` event by adding the following code. Under `post '/event_handler' do`, where it says `# ADD YOUR CODE HERE #`, add the following code: +This route will handle the `check_suite` event. Under `post '/event_handler' do`, where it says `# ADD YOUR CODE HERE #`, add the following code: ``` ruby{:copy} # Get the event type from the HTTP_X_GITHUB_EVENT header @@ -604,9 +602,10 @@ when 'check_suite' if @payload['action'] == 'requested' || @payload['action'] == 'rerequested' create_check_run end -end ``` +[TODOCS: Deleted an extra `end` at the end of the code block above. According to the final code in the repo, it shouldn't be there. But it was in original tutorial code.] + Every event that {% data variables.product.prodname_dotcom %} sends includes a request header called `HTTP_X_GITHUB_EVENT`, which indicates the type of event in the `POST` request. Right now, you're only interested in events of type `check_suite`, which are emitted when a new check suite is created. Each event has an additional `action` field that indicates the type of action that triggered the events. For `check_suite`, the `action` field can be `requested`, `rerequested`, or `completed`. The `requested` action requests a check run each time code is pushed to the repository, while the `rerequested` action requests that you re-run a check for code that already exists in the repository. Because both the `requested` and `rerequested` actions require creating a check run, you'll call a helper called `create_check_run`. Let's write that method now. @@ -644,23 +643,19 @@ You're only supplying the required parameters now to get the basic functionality In the code above, you're using the [ternary operator](https://ruby-doc.org/core-2.3.0/doc/syntax/control_expressions_rdoc.html#label-Ternary+if), which works like an `if/else` statement, to check if the payload contains a `check_run` object. If it does, you read the `head_sha` from the `check_run` object, otherwise you read it from the `check_suite` object. -To test this code, restart the server from your terminal: +### Test that a check run is created + +To test the code you just added, use the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. ```shell{:copy} ruby server.rb ``` -{% note %} - -**Note:** You'll need to restart the Sinatra server before you can test changes. Enter `Ctrl-C` to stop the server, and then run `ruby server.rb` again. If you don't want to do this every time you change your app code, you can look into [reloading](http://sinatrarb.com/faq.html#reloading). - -{% endnote %} - -Now open a pull request in the repository where you installed your app. Your app should respond by creating a check run on your pull request. Click on the **Checks** tab, and you should see a check run with the name "Octo RuboCop", or whichever name you chose earlier for the check run. +Now open a pull request in the repository where you installed your app. Your app should respond by creating a check run on your pull request. Click on the **Checks** tab, and you should see a check run with the name "Octo RuboCop," or whichever name you chose earlier for the check run. If you see other apps in the Checks tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. -Great! You've told {% data variables.product.prodname_dotcom %} to create a check run. You can see the check run status is set to `queued` next to a yellow icon. Next, you'll want to wait for {% data variables.product.prodname_dotcom %} to create the check run and update its status. +So far you've told {% data variables.product.prodname_dotcom %} to create a check run. The check run status in the pull request is set to queued with a yellow icon. Next, you'll want to wait for {% data variables.product.prodname_dotcom %} to create the check run and update its status. ## Step 1.3. Updating a check run @@ -668,9 +663,9 @@ When your `create_check_run` method runs, it asks {% data variables.product.prod You'll want to update your event handler to look for the `created` action. While you're updating the event handler, you can add a conditional for the `rerequested` action. When someone re-runs a single test on {% data variables.product.prodname_dotcom %} by clicking the "Re-run" button, {% data variables.product.prodname_dotcom %} sends the `rerequested` check run event to your app. When a check run is `rerequested`, you'll want to start the process all over and create a new check run. -To include a condition for the `check_run` event in the `post '/event_handler'` route, add the following code under `case request.env['HTTP_X_GITHUB_EVENT']`: +To do that, you'll include a condition for the `check_run` event in the `post '/event_handler'` route. Under `post '/event_handler' do`, add the following code below `case request.env['HTTP_X_GITHUB_EVENT']`: -``` ruby +``` ruby{:copy} when 'check_run' # Check that the event is being sent to this app if @payload['check_run']['app']['id'].to_s === APP_IDENTIFIER @@ -691,7 +686,9 @@ In this section, you're not going to kick off the CI test yet, but you'll walk t Let's create the `initiate_check_run` method and update the status of the check run. Add the following code to the helpers section: -``` ruby +Under `helpers do`, add the following code: + +``` ruby{:copy} # Start the CI process def initiate_check_run # Once the check run is created, you'll update the status of the check run @@ -724,12 +721,14 @@ Here's what this code is doing. First, it updates the check run's status to `in_ You'll notice in the "[AUTOTITLE](/rest/checks#update-a-check-run)" docs that when you provide a status of `completed`, the `conclusion` and `completed_at` parameters are required. The `conclusion` summarizes the outcome of a check run and can be `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `skipped`, or `action_required`. You'll set the conclusion to `success`, the `completed_at` time to the current time, and the status to `completed`. -You could also provide more details about what your check is doing, but you'll get to that in the next section. Let's test this code again by re-running `template_server.rb`: +You could also provide more details about what your check is doing, but you'll get to that in the next section. Let's test this code again. If your server is currently running, enter `Ctrl-C` in your terminal to stop the server. Run the following command to restart the server: -```shell -$ ruby template_server.rb +```shell{:copy} +ruby server.rb ``` +[TODOCS: Do you have to push another commit first, before it will update to show the "Re-run" button?] + Head over to your open pull request and click the **Checks** tab. Click the "Re-run all" button in the upper right corner. You should see the check run move from `pending` to `in_progress` and end with `success`. ## Part 2. Creating the Octo RuboCop CI test @@ -788,7 +787,7 @@ m.display RuboCop is available as a command-line utility. That means your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. -The `Gemfile` in the `building-a-checks-api-ci-server` repository already includes the ruby-git gem, and you installed it when you ran `bundle install` in the [prerequisite steps](#prerequisites). To use the gem, add this code to the top of your `template_server.rb` file: +The `Gemfile` in the `building-a-checks-api-ci-server` repository already includes the ruby-git gem, and you installed it when you ran `bundle install` in the [prerequisite steps](#prerequisites). To use the gem, add this code to the top of your `server.rb` file: ``` ruby require 'git' @@ -808,7 +807,7 @@ git clone https://x-access-token:@github.com//.git The code above clones a repository over HTTP. It requires the full repository name, which includes the repository owner (user or organization) and the repository name. For example, the [octocat Hello-World](https://github.com/octocat/Hello-World) repository has a full name of `octocat/hello-world`. -After your app clones the repository, it needs to pull the latest code changes and check out a specific Git ref. The code to do all of this will fit nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. Add the following new method to the helper method section in `template_server.rb`: +After your app clones the repository, it needs to pull the latest code changes and check out a specific Git ref. The code to do all of this will fit nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. Add the following new method to the helper method section in `server.rb`: ``` ruby # Clones the repository to the current working directory, updates the @@ -827,7 +826,7 @@ def clone_repository(full_repo_name, repository, ref) end ``` -The code above uses the `ruby-git` gem to clone the repository using the app's installation token. This code clones the code in the same directory as `template_server.rb`. To run Git commands in the repository, the code needs to change into the repository directory. Before changing directories, the code stores the current working directory in a variable (`pwd`) to remember where to return before exiting the `clone_repository` method. +The code above uses the `ruby-git` gem to clone the repository using the app's installation token. This code clones the code in the same directory as `server.rb`. To run Git commands in the repository, the code needs to change into the repository directory. Before changing directories, the code stores the current working directory in a variable (`pwd`) to remember where to return before exiting the `clone_repository` method. From the repository directory, this code fetches and merges the latest changes (`@git.pull`), checks out the ref (`@git.checkout(ref)`), then changes the directory back to the original working directory (`pwd`). @@ -864,14 +863,14 @@ Because this code stores the RuboCop results in a `@report` variable, it can saf {% note %} -**Note:** The command used to remove the repository (`rm -rf`) cannot be undone. See [Step 2.7. Security tips](#step-27-security-tips) to learn how to check webhooks for injected malicious commands that could be used to remove a different directory than intended by your app. For example, if a bad actor sent a webhook with the repository name `./`, your app would remove the root directory. 😱 If for some reason you're _not_ using the method `verify_webhook_signature` (which is included in `template_server.rb`) to validate the sender of the webhook, make sure you check that the repository name is valid. +**Note:** The command used to remove the repository (`rm -rf`) cannot be undone. See [Step 2.7. Security tips](#step-27-security-tips) to learn how to check webhooks for injected malicious commands that could be used to remove a different directory than intended by your app. For example, if a bad actor sent a webhook with the repository name `./`, your app would remove the root directory. 😱 If for some reason you're _not_ using the method `verify_webhook_signature` (which is included in `server.rb`) to validate the sender of the webhook, make sure you check that the repository name is valid. {% endnote %} -You can test that this code works and see the errors reported by RuboCop in your server's debug output. Start up the `template_server.rb` server again and create a new pull request in the repository where you're testing your app: +You can test that this code works and see the errors reported by RuboCop in your server's debug output. Start up the `server.rb` server again and create a new pull request in the repository where you're testing your app: ```shell -$ ruby template_server.rb +$ ruby server.rb ``` You should see the linting errors in the debug output, although they aren't printed with formatting. You can use a web tool like [JSON formatter](https://jsonformatter.org/) to format your JSON output like this formatted linting error output: @@ -1058,10 +1057,10 @@ Now that you're setting a conclusion based on the status of the CI test and you' The code above also adds a feature to your CI server called [requested actions](https://developer.github.com/changes/2018-05-23-request-actions-on-checks/) via the `actions` object. {% ifversion fpt or ghec %}(Note this is not related to [GitHub Actions](/actions).) {% endif %}Requested actions add a button in the **Checks** tab on {% data variables.product.prodname_dotcom %} that allows someone to request the check run to take additional action. The additional action is completely configurable by your app. For example, because RuboCop has a feature to automatically fix the errors it finds in Ruby code, your CI server can use a requested actions button to allow people to request automatic error fixes. When someone clicks the button, the app receives the `check_run` event with a `requested_action` action. Each requested action has an `identifier` that the app uses to determine which button was clicked. -The code above doesn't have RuboCop automatically fix errors yet. You'll add that in the next section. But first, take a look at the CI test that you just created by starting up the `template_server.rb` server again and creating a new pull request: +The code above doesn't have RuboCop automatically fix errors yet. You'll add that in the next section. But first, take a look at the CI test that you just created by starting up the `server.rb` server again and creating a new pull request: ```shell -$ ruby template_server.rb +$ ruby server.rb ``` The annotations will show up in the **Checks** tab. Also notice the "Fix this" button that you created by adding a requested action. @@ -1148,13 +1147,17 @@ The code above clones a repository just like the code you added in [Step 2.2. Cl The files are changed locally, but you'll still need to push them to {% data variables.product.prodname_dotcom %}. You'll use the handy `ruby-git` gem again to commit all of the files. Git has a single command that stages all modified or deleted files and commits them: `git commit -a`. To do the same thing using `ruby-git`, the code above uses the `commit_all` method. Then the code pushes the committed files to {% data variables.product.prodname_dotcom %} using the installation token, using the same authentication method as the Git `clone` command. Finally, it removes the repository directory to ensure the working directory is prepared for the next event. -That's it! The code you have written now completes your Checks API CI server. 💪 Restart your `template_server.rb` server again and create a new pull request: +That's it! The code you have written now completes your Checks API CI server. 💪 Restart your `server.rb` server again and create a new pull request: ```shell -$ ruby template_server.rb +$ ruby server.rb ``` -{% data reusables.apps.sinatra_restart_instructions %} +{% note %} + +**Note:** You'll need to restart the Sinatra server before you can test changes. Enter `Ctrl-C` to stop the server, and then run `ruby server.rb` again. If you don't want to do this every time you change your app code, you can look into [reloading](http://sinatrarb.com/faq.html#reloading). + +{% endnote %} This time, click the "Fix this" button to automatically fix the errors RuboCop found from the **Checks** tab. @@ -1538,7 +1541,7 @@ Here are a few common problems and some suggested solutions. If you run into any **A:** Make sure you have **Read & write** permissions for "Repository contents," and that you are cloning the repository with your installation token. See [Step 2.2. Cloning the repository](#step-22-cloning-the-repository) for details. -* **Q:** I see an error in the `template_server.rb` debug output related to cloning my repository. +* **Q:** I see an error in the `server.rb` debug output related to cloning my repository. **A:** If you see the following error, you haven't deleted the checkout of the repository in one or both of the `initiate_check_run` or `take_requested_action` methods: @@ -1550,11 +1553,11 @@ Here are a few common problems and some suggested solutions. If you run into any * **Q:** New check runs are not showing up in the "Checks" tab on {% data variables.product.prodname_dotcom %}. - **A:** Restart Smee and re-run your `template_server.rb` server. + **A:** Restart Smee and re-run your `server.rb` server. * **Q:** I do not see the "Re-run all" button in the "Checks" tab on {% data variables.product.prodname_dotcom %}. - **A:** Restart Smee and re-run your `template_server.rb` server. + **A:** Restart Smee and re-run your `server.rb` server. ## Conclusion From ceb2383f47ee2b18b6dce4304d34ffb7fc9b1dd5 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Mon, 12 Jun 2023 18:33:06 -0600 Subject: [PATCH 11/48] More tutorial updates --- .../building-ci-checks-with-a-github-app.md | 87 +++++++++++-------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 960852b8dab0..48ce88dfa89f 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -581,7 +581,7 @@ Let's get started! These are the steps you'll complete in Part 1: Because your app is subscribed to the **Check suite** and **Check run** events, it will receive the [`check_suite`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) and [`check_run`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) webhooks. {% data variables.product.prodname_dotcom %} sends webhook payloads as `POST` requests. Because you forwarded your Smee webhook payloads to `http://localhost:3000/event_handler`, your server will receive the `POST` request payloads at the `post '/event_handler'` route. -An empty `post '/event_handler'` route is already included in the template code in the `server.rb` file that you created in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your--data-variablesproductprodname_github_app)." The empty route looks like this: +Open the `server.rb` file that you created in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your--data-variablesproductprodname_github_app)", and look for the following code. An empty `post '/event_handler'` route is already included in the template code. The empty route looks like this: ``` ruby post '/event_handler' do @@ -602,10 +602,9 @@ when 'check_suite' if @payload['action'] == 'requested' || @payload['action'] == 'rerequested' create_check_run end +end ``` -[TODOCS: Deleted an extra `end` at the end of the code block above. According to the final code in the repo, it shouldn't be there. But it was in original tutorial code.] - Every event that {% data variables.product.prodname_dotcom %} sends includes a request header called `HTTP_X_GITHUB_EVENT`, which indicates the type of event in the `POST` request. Right now, you're only interested in events of type `check_suite`, which are emitted when a new check suite is created. Each event has an additional `action` field that indicates the type of action that triggered the events. For `check_suite`, the `action` field can be `requested`, `rerequested`, or `completed`. The `requested` action requests a check run each time code is pushed to the repository, while the `rerequested` action requests that you re-run a check for code that already exists in the repository. Because both the `requested` and `rerequested` actions require creating a check run, you'll call a helper called `create_check_run`. Let's write that method now. @@ -727,10 +726,10 @@ You could also provide more details about what your check is doing, but you'll g ruby server.rb ``` -[TODOCS: Do you have to push another commit first, before it will update to show the "Re-run" button?] - Head over to your open pull request and click the **Checks** tab. Click the "Re-run all" button in the upper right corner. You should see the check run move from `pending` to `in_progress` and end with `success`. +[TODOCS: I had to push another commit first, before the PR would update to show the "Re-run" button. But pushing another commit already re-runs the test. So it maybe feels weird to also tell them to click the re-run all button... but maybe not? Is there another way to get the re-run all button to show up?] + ## Part 2. Creating the Octo RuboCop CI test [RuboCop](https://rubocop.readthedocs.io/en/latest/) is a Ruby code linter and formatter. It checks Ruby code to ensure that it complies with the "[Ruby Style Guide](https://github.com/rubocop-hq/ruby-style-guide)." RuboCop has three primary functions: @@ -741,11 +740,11 @@ Head over to your open pull request and click the **Checks** tab. Click the "Re- Now that you've got the interface created to receive Checks API events and create check runs, you can create a check run that implements a CI test. -Your app will run RuboCop on the CI server and create check runs (CI tests in this case) that report the results that RuboCop reports to {% data variables.product.prodname_dotcom %}. +Your app will run RuboCop on the CI server, and create check runs (CI tests in this case) that report the results that RuboCop reports to {% data variables.product.prodname_dotcom %}. The Checks API allows you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. -Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. That information can be anything: for example, a comment, an error, or a warning. This tutorial uses annotations to visualize RuboCop errors. +Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. For example, you could show that information as a comment, error, or warning on a specific line of code. This tutorial uses annotations to visualize RuboCop errors. To take advantage of requested actions, app developers can create buttons in the **Checks** tab of pull requests. When someone clicks one of these buttons, the click sends a `requested_action` `check_run` event to the {% data variables.product.prodname_github_app %}. The action that the app takes is completely configurable by the app developer. This tutorial will walk you through adding a button that allows users to request that RuboCop fix the errors it finds. RuboCop supports automatically fixing errors using a command-line option, and you'll configure the `requested_action` to take advantage of this option. @@ -759,57 +758,67 @@ Let's get started! These are the steps you'll complete in this section: 1. [Automatically fixing RuboCop errors](#step-26-automatically-fixing-rubocop-errors) 1. [Security tips](#step-27-security-tips) -## Step 2.1. Adding a Ruby file - -You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. Because RuboCop only checks Ruby code, you'll want at least one Ruby file in your repository that contains errors. The example file provided below contains a few errors. Add this example Ruby file to the repository where your app is installed (make sure to name the file with an `.rb` extension, as in `myfile.rb`): +## Step 2.1. Add a Ruby file -```ruby -# The Octocat class tells you about different breeds of Octocat -class Octocat - def initialize(name, *breeds) - # Instance variables - @name = name - @breeds = breeds - end +You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. RuboCop only checks Ruby code. You'll need to add a Ruby file in your repository that contains errors for RuboCop to find. - def display - breed = @breeds.join("-") +1. Navigate to the directory where your app is installed. +2. Create a new file named `myfile.rb`. For more information, see "[AUTOTITLE](/repositories/working-with-files/managing-files/creating-new-files)." +3. Add the following content to `myfile.rb`: - puts "I am of #{breed} breed, and my name is #{@name}." - end -end - -m = Octocat.new("Mona", "cat", "octopus") -m.display -``` + ```ruby{:copy} + # 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 + ``` -## Step 2.2. Cloning the repository +## Step 2.2. Clone the repository RuboCop is available as a command-line utility. That means your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. The `Gemfile` in the `building-a-checks-api-ci-server` repository already includes the ruby-git gem, and you installed it when you ran `bundle install` in the [prerequisite steps](#prerequisites). To use the gem, add this code to the top of your `server.rb` file: -``` ruby +``` ruby{:copy} require 'git' ``` -Your app needs read permission for "Repository contents" to clone a repository. Later in this tutorial, you'll need to push contents to {% data variables.product.prodname_dotcom %}, which requires write permission. Go ahead and set your app's "Repository contents" permission to **Read & write** now so you don't need to update it again later. To update your app's permissions: +### Update your app permissions + +Next you'll need to update your {% data variables.product.prodname_github_app %}'s permissions. Your app will need read permission for "Repository contents" to clone a repository. And later in this tutorial, it will need write permission to push contents to {% data variables.product.prodname_dotcom %}. To update your app's permissions: -1. Select your app from the [app settings page](https://github.com/settings/apps) and click **Permissions & Webhooks** in the sidebar. -1. In the "Permissions" section, find "Repository contents", and select **Read & write** in the "Access" dropdown next to it. +1. Select your app from the [app settings page](https://github.com/settings/apps), and click **Permissions & events** in the sidebar. +1. Under "Repository permissions," next to "Contents," select **Read & write**. {% data reusables.apps.accept_new_permissions_steps %} -To clone a repository using your {% data variables.product.prodname_github_app %}'s permissions, you can use the app's installation token (`x-access-token:`) shown in the example below: +### Add code to clone a repository + +To clone a repository using your {% data variables.product.prodname_github_app %}'s permissions, your code will use the app's installation token (`x-access-token:`), as shown in the example below: ```shell -git clone https://x-access-token:@github.com//.git +git clone https://x-access-token:<TOKEN>@github.com/<OWNER>/<REPO>.git ``` -The code above clones a repository over HTTP. It requires the full repository name, which includes the repository owner (user or organization) and the repository name. For example, the [octocat Hello-World](https://github.com/octocat/Hello-World) repository has a full name of `octocat/hello-world`. +The command above clones a repository over HTTP. It requires the full repository name, which includes the repository owner (user or organization) and the repository name. For example, the [octocat Hello-World](https://github.com/octocat/Hello-World) repository has a full name of `octocat/hello-world`. -After your app clones the repository, it needs to pull the latest code changes and check out a specific Git ref. The code to do all of this will fit nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. Add the following new method to the helper method section in `server.rb`: +After your app clones the repository, it needs to pull the latest code changes and check out a specific Git ref. The code to do all of this will fit nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. -``` ruby +Open your `server.rb` file. Under `helpers do`, add the following code: + +``` ruby{:copy} # Clones the repository to the current working directory, updates the # contents using Git pull, and checks out the ref. # @@ -830,7 +839,9 @@ The code above uses the `ruby-git` gem to clone the repository using the app's i From the repository directory, this code fetches and merges the latest changes (`@git.pull`), checks out the ref (`@git.checkout(ref)`), then changes the directory back to the original working directory (`pwd`). -Now you've got a method that clones a repository and checks out a ref. Next, you need to add code to get the required input parameters and call the new `clone_repository` method. Add the following code under the `***** RUN A CI TEST *****` comment in your `initiate_check_run` helper method: +Now you've got a method that clones a repository and checks out a ref. Next, you need to add code to get the required input parameters and call the new `clone_repository` method. + +Under `helpers do`, in the `initiate_check_run` helper method where it says `# ***** RUN A CI TEST *****`, add the following code: ``` ruby # ***** RUN A CI TEST ***** From cdacdb731edbea04badb72d00af3cff276261911 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Tue, 13 Jun 2023 17:56:06 -0600 Subject: [PATCH 12/48] More updates --- .../building-ci-checks-with-a-github-app.md | 148 ++++++++++-------- 1 file changed, 80 insertions(+), 68 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 48ce88dfa89f..ca9703fc6f83 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -788,9 +788,13 @@ You can pass specific files or entire directories for RuboCop to check. In this ## Step 2.2. Clone the repository -RuboCop is available as a command-line utility. That means your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. +RuboCop is available as a command-line utility. That means your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To do that, your code will need to be able to run Git operations, and your {% data variables.product.prodname_github_app %} will need to have the correct permissions to clone a repository. -The `Gemfile` in the `building-a-checks-api-ci-server` repository already includes the ruby-git gem, and you installed it when you ran `bundle install` in the [prerequisite steps](#prerequisites). To use the gem, add this code to the top of your `server.rb` file: +### Allow Git operations + +To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. The `Gemfile` in the `building-a-checks-api-ci-server` repository already includes the ruby-git gem, and you installed it when you ran `bundle install` in the [prerequisite steps](#prerequisites). + +At the top of your `server.rb` file, below the other `require` items, add the following code: ``` ruby{:copy} require 'git' @@ -806,7 +810,7 @@ Next you'll need to update your {% data variables.product.prodname_github_app %} ### Add code to clone a repository -To clone a repository using your {% data variables.product.prodname_github_app %}'s permissions, your code will use the app's installation token (`x-access-token:`), as shown in the example below: +To clone a repository, the code will use your {% data variables.product.prodname_github_app %}'s permissions and the Octokit SDK to create an installation token for your app (`x-access-token:`) and use it in the following clone command: ```shell git clone https://x-access-token:<TOKEN>@github.com/<OWNER>/<REPO>.git @@ -843,8 +847,7 @@ Now you've got a method that clones a repository and checks out a ref. Next, you Under `helpers do`, in the `initiate_check_run` helper method where it says `# ***** RUN A CI TEST *****`, add the following code: -``` ruby -# ***** RUN A CI TEST ***** +``` ruby{:copy} full_repo_name = @payload['repository']['full_name'] repository = @payload['repository']['name'] head_sha = @payload['check_run']['head_sha'] @@ -854,13 +857,13 @@ clone_repository(full_repo_name, repository, head_sha) The code above gets the full repository name and the head SHA of the commit from the `check_run` webhook payload. -## Step 2.3. Running RuboCop +## Step 2.3. Run RuboCop -Great! You're cloning the repository and creating check runs using your CI server. Now you'll get into the nitty gritty details of the [RuboCop linter](https://docs.rubocop.org/rubocop/usage/basic_usage.html#code-style-checker) and [Checks API annotations](/rest/checks#create-a-check-run). +So far your code clones the repository and creates check runs using your CI server. Now you'll get into the details of the [RuboCop linter](https://docs.rubocop.org/rubocop/usage/basic_usage.html#code-style-checker) and [checks annotations](/rest/checks#create-a-check-run). First, add some code to run RuboCop and save the style code errors in JSON format. -The following code runs RuboCop and saves the style code errors in JSON format. Add this code below the call to `clone_repository` you added in the [previous step](#step-22-cloning-the-repository) and above the code that updates the check run to complete. +Under `clone_repository`, which you just added in the [previous step](#step-22-clone-the-repository), add the following code: -``` ruby +``` ruby{:copy} # Run RuboCop on all files in the repository @report = `rubocop '#{repository}' --format json` logger.debug @report @@ -868,7 +871,7 @@ logger.debug @report @output = JSON.parse @report ``` -The code above runs RuboCop on all files in the repository's directory. The option `--format json` is a handy way to save a copy of the linting results in a machine-parsable format. See the [RuboCop docs](https://docs.rubocop.org/rubocop/formatters.html#json-formatter) for details and an example of the JSON format. +The code above runs RuboCop on all files in the repository's directory. The option `--format json` saves a copy of the linting results in a machine-parsable format. For more information, and an example of the JSON format, see "[JSON Formatter](https://docs.rubocop.org/rubocop/formatters.html#json-formatter)" in the RuboCop docs. Because this code stores the RuboCop results in a `@report` variable, it can safely remove the checkout of the repository. This code also parses the JSON so you can easily access the keys and values in your {% data variables.product.prodname_github_app %} using the `@output` variable. @@ -876,69 +879,78 @@ Because this code stores the RuboCop results in a `@report` variable, it can saf **Note:** The command used to remove the repository (`rm -rf`) cannot be undone. See [Step 2.7. Security tips](#step-27-security-tips) to learn how to check webhooks for injected malicious commands that could be used to remove a different directory than intended by your app. For example, if a bad actor sent a webhook with the repository name `./`, your app would remove the root directory. 😱 If for some reason you're _not_ using the method `verify_webhook_signature` (which is included in `server.rb`) to validate the sender of the webhook, make sure you check that the repository name is valid. +TODOCS: If we remove the security tips section, update this note text. + {% endnote %} -You can test that this code works and see the errors reported by RuboCop in your server's debug output. Start up the `server.rb` server again and create a new pull request in the repository where you're testing your app: +### Test the code -```shell -$ ruby server.rb -``` +The following steps will show you how to test that the code works and view the errors reported by RuboCop. -You should see the linting errors in the debug output, although they aren't printed with formatting. You can use a web tool like [JSON formatter](https://jsonformatter.org/) to format your JSON output like this formatted linting error output: +1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. -```json -{ - "metadata": { - "rubocop_version": "0.60.0", - "ruby_engine": "ruby", - "ruby_version": "2.3.7", - "ruby_patchlevel": "456", - "ruby_platform": "universal.x86_64-darwin18" - }, - "files": [ - { - "path": "Octocat-breeds/octocat.rb", - "offenses": [ - { - "severity": "convention", - "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.", - "cop_name": "Style/StringLiterals", - "corrected": false, - "location": { - "start_line": 17, - "start_column": 17, - "last_line": 17, - "last_column": 22, - "length": 6, - "line": 17, - "column": 17 - } - }, - { - "severity": "convention", - "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.", - "cop_name": "Style/StringLiterals", - "corrected": false, - "location": { - "start_line": 17, - "start_column": 25, - "last_line": 17, - "last_column": 29, - "length": 5, - "line": 17, - "column": 25 - } - } - ] - } - ], - "summary": { - "offense_count": 2, - "target_file_count": 1, - "inspected_file_count": 1 - } -} -``` + ```shell{:copy} + ruby server.rb + ``` + +2. In the repository where you installed your app, create a new pull request. +3. In your terminal tab where the server is running, you should see debug output that contains linting errors. The linting errors are printed without any formatting. You can use a web tool like [JSON formatter](https://jsonformatter.org/) to format your JSON output like the following example, so it's easier to read. + + ```json + { + "metadata": { + "rubocop_version": "0.60.0", + "ruby_engine": "ruby", + "ruby_version": "2.3.7", + "ruby_patchlevel": "456", + "ruby_platform": "universal.x86_64-darwin18" + }, + "files": [ + { + "path": "Octocat-breeds/octocat.rb", + "offenses": [ + { + "severity": "convention", + "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.", + "cop_name": "Style/StringLiterals", + "corrected": false, + "location": { + "start_line": 17, + "start_column": 17, + "last_line": 17, + "last_column": 22, + "length": 6, + "line": 17, + "column": 17 + } + }, + { + "severity": "convention", + "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.", + "cop_name": "Style/StringLiterals", + "corrected": false, + "location": { + "start_line": 17, + "start_column": 25, + "last_line": 17, + "last_column": 29, + "length": 5, + "line": 17, + "column": 25 + } + } + ] + } + ], + "summary": { + "offense_count": 2, + "target_file_count": 1, + "inspected_file_count": 1 + } + } + ``` + +TODOCS: This testing failed for me. It was unable to clone the repository ("Repository not found"). ## Step 2.4. Collecting RuboCop errors From 58ac9c1d6f0f0080213501c546409abdb6f9772d Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Tue, 13 Jun 2023 18:06:34 -0600 Subject: [PATCH 13/48] More updates --- .../building-ci-checks-with-a-github-app.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index ca9703fc6f83..8f2a4be13dde 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -950,9 +950,7 @@ The following steps will show you how to test that the code works and view the e } ``` -TODOCS: This testing failed for me. It was unable to clone the repository ("Repository not found"). - -## Step 2.4. Collecting RuboCop errors +## Step 2.4. Collect RuboCop errors The `@output` variable contains the parsed JSON results of the RuboCop report. As shown above, the results contain a `summary` section that your code can use to quickly determine if there are any errors. The following code will set the check run conclusion to `success` when there are no reported errors. RuboCop reports errors for each file in the `files` array, so if there are errors, you'll need to extract some data from the file object. @@ -1027,7 +1025,7 @@ This code also iterates through each error in the `offenses` array and collects This code doesn't yet create an annotation for the check run. You'll add that code in the next section. -## Step 2.5. Updating the check run with CI test results +## Step 2.5. Update the check run with CI test results Each check run from {% data variables.product.prodname_dotcom %} contains an `output` object that includes a `title`, `summary`, `text`, `annotations`, and `images`. The `summary` and `title` are the only required parameters for the `output`, but those alone don't offer much detail, so this tutorial adds `text` and `annotations` too. The code here doesn't add an image, but feel free to add one if you'd like! @@ -1090,7 +1088,7 @@ The annotations will show up in the **Checks** tab. Also notice the "Fix this" b If the annotations are related to a file already included in the PR, the annotations will also show up in the **Files changed** tab. -## Step 2.6. Automatically fixing RuboCop errors +## Step 2.6. Automatically fix RuboCop errors If you've made it this far, kudos! 👏 You've already created a CI test. In this section, you'll add one more feature that uses RuboCop to automatically fix the errors it finds. You already added the "Fix this" button in the [previous section](#step-25-updating-the-check-run-with-ci-test-results). Now you'll add the code to handle the `requested_action` check run event triggered when someone clicks the "Fix this" button. From dfdba48aabea4ddfcb8065e5a3ba70e1ecf91cb2 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:37:20 -0600 Subject: [PATCH 14/48] More updates --- .../building-ci-checks-with-a-github-app.md | 88 +++++++++++++------ 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 8f2a4be13dde..354998120d19 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -172,7 +172,7 @@ Make sure that you are on a secure machine before performing these steps since y 1. Add `.env` to your `.gitignore` file. This will prevent you from accidentally committing your app's credentials. 1. Add the following contents to your `.env` file. {% ifversion ghes or ghae %}Replace `YOUR_HOSTNAME` with the name of {% data variables.location.product_location %}. You will update the other values in a later step.{% else %}You will update the values in a later step.{% endif %} - ```{:copy} + ```ini copy GITHUB_APP_IDENTIFIER="YOUR_APP_ID" GITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET" GITHUB_PRIVATE_KEY="YOUR_PRIVATE_KEY" @@ -188,7 +188,7 @@ Make sure that you are on a secure machine before performing these steps since y Here is an example .env file: - ``` + ```ini GITHUB_APP_IDENTIFIER=12345 GITHUB_WEBHOOK_SECRET=your webhook secret GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- @@ -802,7 +802,7 @@ require 'git' ### Update your app permissions -Next you'll need to update your {% data variables.product.prodname_github_app %}'s permissions. Your app will need read permission for "Repository contents" to clone a repository. And later in this tutorial, it will need write permission to push contents to {% data variables.product.prodname_dotcom %}. To update your app's permissions: +Next you'll need to update your {% data variables.product.prodname_github_app %}'s permissions. Your app will need read permission for "Contents" to clone a repository. And later in this tutorial, it will need write permission to push contents to {% data variables.product.prodname_dotcom %}. To update your app's permissions: 1. Select your app from the [app settings page](https://github.com/settings/apps), and click **Permissions & events** in the sidebar. 1. Under "Repository permissions," next to "Contents," select **Read & write**. @@ -952,17 +952,19 @@ The following steps will show you how to test that the code works and view the e ## Step 2.4. Collect RuboCop errors -The `@output` variable contains the parsed JSON results of the RuboCop report. As shown above, the results contain a `summary` section that your code can use to quickly determine if there are any errors. The following code will set the check run conclusion to `success` when there are no reported errors. RuboCop reports errors for each file in the `files` array, so if there are errors, you'll need to extract some data from the file object. +The `@output` variable contains the parsed JSON results of the RuboCop report. As shown in the example output in the previous step, the results contain a `summary` section that your code can use to quickly determine if there are any errors. The following code will set the check run conclusion to `success` when there are no reported errors. RuboCop reports errors for each file in the `files` array, so if there are errors, you'll need to extract some data from the file object. -The Checks API allows you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this tutorial you are [updating the check run](/rest/checks#update-a-check-run) with annotations. +The REST API checks endpoints allow you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this tutorial you will update the check run with annotations, using the "[Update a check run](/rest/checks#update-a-check-run)" endpoint. -The Checks API limits the number of annotations to a maximum of 50 per API request. To create more than 50 annotations, you have to make multiple requests to the [AUTOTITLE](/rest/checks#update-a-check-run) endpoint. For example, to create 105 annotations you'd need to call the [AUTOTITLE](/rest/checks#update-a-check-run) endpoint three times. The first two requests would each have 50 annotations, and the third request would include the five remaining annotations. Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. +The API limits the number of annotations to a maximum of 50 per request. To create more than 50 annotations, you will have to make multiple requests to the "Update a check run" endpoint. For example, to create 105 annotations you would need to make three separate requests to the API. The first two requests would each have 50 annotations, and the third request would include the five remaining annotations. Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. -A check run expects annotations as an array of objects. Each annotation object must include the `path`, `start_line`, `end_line`, `annotation_level`, and `message`. RuboCop provides the `start_column` and `end_column` too, so you can include those optional parameters in the annotation. Annotations only support `start_column` and `end_column` on the same line. See the [`annotations` object](/rest/checks#annotations-object-1) reference documentation for details. +A check run expects annotations as an array of objects. Each annotation object must include the `path`, `start_line`, `end_line`, `annotation_level`, and `message`. RuboCop provides the `start_column` and `end_column` too, so you can include those optional parameters in the annotation. Annotations only support `start_column` and `end_column` on the same line. For more information, see the `annotations` object in "[AUTOTITLE](/rest/checks/runs#create-a-check-run)." -You'll extract the required information from RuboCop needed to create each annotation. Append the following code to the code you added in the [previous section](#step-23-running-rubocop): +Now you'll add code to extract the required information from RuboCop that's needed to create each annotation. -``` ruby +In the [previous step](#step-23-run-rubocop), you added code under `clone_repository` that runs RuboCop on all files in the repository and outputs the linting results as JSON. Immediately under that code block, below `@output = JSON.parse @report`, add the following code: + +``` ruby{:copy} 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 @@ -1027,17 +1029,33 @@ This code doesn't yet create an annotation for the check run. You'll add that co ## Step 2.5. Update the check run with CI test results -Each check run from {% data variables.product.prodname_dotcom %} contains an `output` object that includes a `title`, `summary`, `text`, `annotations`, and `images`. The `summary` and `title` are the only required parameters for the `output`, but those alone don't offer much detail, so this tutorial adds `text` and `annotations` too. The code here doesn't add an image, but feel free to add one if you'd like! +Each check run from {% data variables.product.prodname_dotcom %} contains an `output` object that includes a `title`, `summary`, `text`, `annotations`, and `images`. The `summary` and `title` are the only required parameters for the `output`, but those alone don't offer much detail, so this tutorial also adds `text` and `annotations`. -For the `summary`, this example uses the summary information from RuboCop and adds some newlines (`\n`) to format the output. You can customize what you add to the `text` parameter, but this example sets the `text` parameter to the RuboCop version. To set the `summary` and `text`, append this code to the code you added in the [previous section](#step-24-collecting-rubocop-errors): +For the `summary`, this example uses the summary information from RuboCop and adds newlines (`\n`) to format the output. You can customize what you add to the `text` parameter, but this example sets the `text` parameter to the RuboCop version. The following code sets the `summary` and `text`. -``` ruby +In the [previous step](#step-24-collect-rubocop-errors), you appended code to an existing code block in your `server.rb` file. The end of that code block should look like this: + +```ruby + # 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 +``` + +Below the final `end`, add the following code: + +``` ruby{:copy} # 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']}" ``` -Now you've got all the information you need to update your check run. In the [first half of this tutorial](#step-14-updating-a-check-run), you added this code to set the status of the check run to `success`: +Now your code should have all the information it needs to update your check run. In [Part 1 of this tutorial](#step-13-updating-a-check-run), you added code to set the status of the check run to `success`. You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). Here's the code you added previously to your `server.rb` file: ``` ruby # Mark the check run as complete! @@ -1050,9 +1068,9 @@ Now you've got all the information you need to update your check run. In the [fi ) ``` -You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). You can update the code with the following: +Replace that code with the following code: -``` ruby +``` ruby{:copy} # Mark the check run as complete! And if there are warnings, share them. @installation_client.update_check_run( @payload['repository']['full_name'], @@ -1074,34 +1092,46 @@ You'll need to update that code to use the `conclusion` variable you set based o ) ``` -Now that you're setting a conclusion based on the status of the CI test and you've added the output from the RuboCop results, you've created a CI test! Congratulations. 🙌 +Now that your code sets a conclusion based on the status of the CI test, and adds the output from the RuboCop results, you've created a CI test. The code above also adds a feature to your CI server called [requested actions](https://developer.github.com/changes/2018-05-23-request-actions-on-checks/) via the `actions` object. {% ifversion fpt or ghec %}(Note this is not related to [GitHub Actions](/actions).) {% endif %}Requested actions add a button in the **Checks** tab on {% data variables.product.prodname_dotcom %} that allows someone to request the check run to take additional action. The additional action is completely configurable by your app. For example, because RuboCop has a feature to automatically fix the errors it finds in Ruby code, your CI server can use a requested actions button to allow people to request automatic error fixes. When someone clicks the button, the app receives the `check_run` event with a `requested_action` action. Each requested action has an `identifier` that the app uses to determine which button was clicked. -The code above doesn't have RuboCop automatically fix errors yet. You'll add that in the next section. But first, take a look at the CI test that you just created by starting up the `server.rb` server again and creating a new pull request: +The code above doesn't have RuboCop automatically fix errors yet. You'll add that later in the tutorial. -```shell -$ ruby server.rb -``` +### Test the code + +The following steps will show you how to test that the code works, and view the CI test that you just created. + +1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. -The annotations will show up in the **Checks** tab. Also notice the "Fix this" button that you created by adding a requested action. + ```shell{:copy} + ruby server.rb + ``` + +2. In the repository where you installed your app, create a new pull request. +3. In the pull request you just created, navigate to the **Checks** tab. + +You should see annotations for each of the errors that RuboCop found. Also notice the "Fix this" button that you created by adding a requested action. If the annotations are related to a file already included in the PR, the annotations will also show up in the **Files changed** tab. ## Step 2.6. Automatically fix RuboCop errors -If you've made it this far, kudos! 👏 You've already created a CI test. In this section, you'll add one more feature that uses RuboCop to automatically fix the errors it finds. You already added the "Fix this" button in the [previous section](#step-25-updating-the-check-run-with-ci-test-results). Now you'll add the code to handle the `requested_action` check run event triggered when someone clicks the "Fix this" button. +So far you've created a CI test. In this section, you'll add one more feature that uses RuboCop to automatically fix the errors it finds. You already added the "Fix this" button in the [previous section](#step-25-update-the-check-run-with-ci-test-results). Now you'll add the code to handle the `requested_action` check run event that's triggered when someone clicks the "Fix this" button. -The RuboCop tool [offers](https://docs.rubocop.org/rubocop/usage/basic_usage.html#auto-correcting-offenses) the `--auto-correct` command-line option to automatically fix errors it finds. When you use the `--auto-correct` feature, the updates are applied to the local files on the server. You'll need to push the changes to {% data variables.product.prodname_dotcom %} after RuboCop does its magic. +The RuboCop tool offers the `--auto-correct` command-line option to automatically fix the errors it finds. For more information, see "[Autocorrecting offenses](https://docs.rubocop.org/rubocop/usage/basic_usage.html#autocorrecting-offenses)" in the RuboCop documentation. When you use the `--auto-correct` feature, the updates are applied to the local files on the server. You'll need to push the changes to {% data variables.product.prodname_dotcom %} after RuboCop makes the fixes. -To push to a repository, your app must have write permissions for "Repository contents." You set that permission back in [Step 2.2. Cloning the repository](#step-22-cloning-the-repository) to **Read & write**, so you're all set. +To push to a repository, your app must have write permissions for "Contents" in a repository. You already set that permission to **Read & write** back in [Step 2.2. Cloning the repository](#step-22-clone-the-repository). -In order to commit files, Git must know which [username](/get-started/getting-started-with-git/setting-your-username-in-git) and [email](/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address) to associate with the commit. Add two more environment variables in your `.env` file to store the name (`GITHUB_APP_USER_NAME`) and email (`GITHUB_APP_USER_EMAIL`) settings. Your name can be the name of your app and the email can be any email you'd like for this example. For example: +To commit files, Git must know which username and email address to associate with the commit. Next you'll add environment variables to store the name (`GITHUB_APP_USER_NAME`) and email (`GITHUB_APP_USER_EMAIL`) that your app will use when it makes Git commits. -```ini -GITHUB_APP_USER_NAME=Octoapp -GITHUB_APP_USER_EMAIL=octoapp@octo-org.com -``` +1. Open the `.env` file you created earlier in this tutorial. +2. Add the following environment variables to your `.env` file. Replace `APP_NAME` with the name of your app, and `EMAIL_ADDRESS` with any email you'd like to use for this example. + + ```ini code + GITHUB_APP_USER_NAME="APP_NAME" + GITHUB_APP_USER_EMAIL="EMAIL_ADDRESS" + ``` Once you've updated your `.env` file with the name and email of the author and committer, you'll be ready to add code to read the environment variables and set the Git configuration. You'll add that code soon. From 2a0f9862dc9708db721f926b750f0d6e1f810cf8 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:47:09 -0600 Subject: [PATCH 15/48] More updates --- .../building-ci-checks-with-a-github-app.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 354998120d19..f014d21c9fda 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -172,7 +172,7 @@ Make sure that you are on a secure machine before performing these steps since y 1. Add `.env` to your `.gitignore` file. This will prevent you from accidentally committing your app's credentials. 1. Add the following contents to your `.env` file. {% ifversion ghes or ghae %}Replace `YOUR_HOSTNAME` with the name of {% data variables.location.product_location %}. You will update the other values in a later step.{% else %}You will update the values in a later step.{% endif %} - ```ini copy + ```{:copy} GITHUB_APP_IDENTIFIER="YOUR_APP_ID" GITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET" GITHUB_PRIVATE_KEY="YOUR_PRIVATE_KEY" @@ -188,7 +188,7 @@ Make sure that you are on a secure machine before performing these steps since y Here is an example .env file: - ```ini + ``` GITHUB_APP_IDENTIFIER=12345 GITHUB_WEBHOOK_SECRET=your webhook secret GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- @@ -1128,12 +1128,12 @@ To commit files, Git must know which username and email address to associate wit 1. Open the `.env` file you created earlier in this tutorial. 2. Add the following environment variables to your `.env` file. Replace `APP_NAME` with the name of your app, and `EMAIL_ADDRESS` with any email you'd like to use for this example. - ```ini code + ```{:copy} GITHUB_APP_USER_NAME="APP_NAME" GITHUB_APP_USER_EMAIL="EMAIL_ADDRESS" ``` -Once you've updated your `.env` file with the name and email of the author and committer, you'll be ready to add code to read the environment variables and set the Git configuration. You'll add that code soon. +Next you'll need to add code to read the environment variables and set the Git configuration. You'll add that code soon. When someone clicks the "Fix this" button, your app receives the [check run webhook](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) with the `requested_action` action type. From 1bf39253b3c639fb4f8d62dc3359836fef1bca36 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 15 Jun 2023 10:36:04 -0600 Subject: [PATCH 16/48] More updates --- .../building-ci-checks-with-a-github-app.md | 303 +++++++++--------- 1 file changed, 149 insertions(+), 154 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index f014d21c9fda..256211fed3ee 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -63,6 +63,10 @@ Each time new code is pushed to a repository, {% data variables.product.prodname ## Prerequisites +This tutorial assumes you have a basic understanding of the [Ruby programming language](https://www.ruby-lang.org/en/). + +This tutorial requires your computer or codespace to run Node.js version 12 or greater and npm version 6.12.0 or greater. For more information, see [Node.js](https://nodejs.org/en). TODOCS: Is this true? Grabbed it from other tutorial. + Before you get started, you may want to familiarize yourself with the following concepts: - [{% data variables.product.prodname_github_apps %}](/apps) @@ -71,11 +75,7 @@ Before you get started, you may want to familiarize yourself with the following The Checks endpoints are also available to use in GraphQL, but this tutorial focuses on REST. For more information about the GraphQL objects, see [Checks Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. -You'll use the [Ruby programming language](https://www.ruby-lang.org/en/), the [Smee](https://smee.io/) webhook payload delivery service, the [Octokit.rb Ruby library](https://octokit.github.io/octokit.rb/) for the {% data variables.product.prodname_dotcom %} REST API, and the [Sinatra web framework](https://sinatrarb.com/) to create your Checks API CI server app. - -TODOCS: Add blurb about node.js? ([Example](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/building-a-github-app-that-responds-to-webhook-events#prerequisites)) - -TODOCS: Incorporate any additional prerequisites from the [dev environment tutorial](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/setting-up-your-development-environment-to-create-a-github-app#prerequisites)? +This tutorial also uses the [Smee](https://smee.io/) webhook payload delivery service, the [Octokit.rb Ruby library](https://octokit.github.io/octokit.rb/) for the {% data variables.product.prodname_dotcom %} REST API, and the [Sinatra web framework](https://sinatrarb.com/). ## Setup @@ -205,10 +205,10 @@ This section will show you how to add some basic template code for your {% data Add the following template code to your `server.rb` file: ```ruby{:copy} -require 'sinatra' -require 'octokit' +require 'sinatra' # Uses the Sinatra web framework +require 'octokit' # Uses the Octokit Ruby library to interact with GitHub's REST API require 'dotenv/load' # Manages environment variables -require 'json' +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 @@ -217,24 +217,17 @@ require 'logger' # Logs debug statements set :port, 3000 set :bind, '0.0.0.0' - -# This is template code to create a GitHub App server. -# You can read more about GitHub Apps here: https://docs.github.com/en/apps -# -# On its own, this app does absolutely nothing, except that it can be installed. -# This tutorial will show you how to add more functionality. -# # 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 - # Expects that the private key in PEM format. Converts the newlines + # Expects that the private key has been set as an environment + # variable 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 secret set. The secret is used to verify + # 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'] @@ -247,10 +240,21 @@ class GHAapp < Sinatra::Application end - # Before each request to the `/event_handler` route + # Executed before each request to the `/event_handler` route before '/event_handler' do get_payload_request(request) verify_webhook_signature + + # This tutorial example uses the repository name in the webhook with + # command line utilities. For security reasons, you should validate the + # repository name to ensure that a bad actor isn't attempting to execute + # arbitrary commands or inject false repository names. 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) @@ -396,25 +400,36 @@ end #### Define a before filter -Sinatra uses [before filters](https://github.com/sinatra/sinatra#filters) that allow you to execute code before the route handler. The `before` block in the template calls four [helper methods](https://github.com/sinatra/sinatra#helpers). The template app defines those helper methods in a [later section](#define-the-helper-methods). +Sinatra uses [before filters](https://github.com/sinatra/sinatra#filters) that allow you to execute code before the route handler. The `before` block in the template calls four [helper methods](https://github.com/sinatra/sinatra#helpers): `get_payload_request`, `verify_webhook_signature`, `authenticate_app`, and `authenticate_installation`. The template app defines those helper methods in a `helpers do` block later on in the code. For more information, see "[Define the helper methods](#define-the-helper-methods)." ``` ruby -# Before each request to the `/event_handler` route -before '/event_handler' do - get_payload_request(request) - verify_webhook_signature - authenticate_app - # Authenticate the app installation in order to run API operations - authenticate_installation(@payload) -end + # Executed before each request to the `/event_handler` route + before '/event_handler' do + get_payload_request(request) + verify_webhook_signature + + # This tutorial example uses the repository name in the webhook with + # command line utilities. For security reasons, this code validates the + # repository name to ensure that a bad actor isn't attempting to execute + # arbitrary commands or inject false repository names. 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 ``` +Under `verify_webhook_signature`, the code that starts with `unless @payload` is an additional security measure. If a repository name is provided with a webhook payload, this code validates that the repository name only contains Latin alphabetic characters, hyphens, and underscores. This helps ensure that a bad actor isn't attempting to execute arbitrary commands or inject false repository names. The `verify_webhook_signature` helper method also validates incoming webhook payloads. For more information, see "[Define the helper methods](#verifying-the-webhook-signature)." + #### Define a route handler An empty route is included in the template code. This code handles all `POST` requests to the `/event_handler` route. You will add more code to this later. -TODOCS: Make sure we talk about adding more code to this later in the tutorial. - ``` ruby post '/event_handler' do @@ -767,6 +782,8 @@ You can pass specific files or entire directories for RuboCop to check. In this 3. Add the following content to `myfile.rb`: ```ruby{:copy} + # frozen_string_literal: true + # The Octocat class tells you about different breeds of Octocat class Octocat def initialize(name, *breeds) @@ -1123,7 +1140,7 @@ The RuboCop tool offers the `--auto-correct` command-line option to automaticall To push to a repository, your app must have write permissions for "Contents" in a repository. You already set that permission to **Read & write** back in [Step 2.2. Cloning the repository](#step-22-clone-the-repository). -To commit files, Git must know which username and email address to associate with the commit. Next you'll add environment variables to store the name (`GITHUB_APP_USER_NAME`) and email (`GITHUB_APP_USER_EMAIL`) that your app will use when it makes Git commits. +To commit files, Git must know which username and email address to associate with the commit. Next you'll add environment variables to store the name and email address that your app will use when it makes Git commits. 1. Open the `.env` file you created earlier in this tutorial. 2. Add the following environment variables to your `.env` file. Replace `APP_NAME` with the name of your app, and `EMAIL_ADDRESS` with any email you'd like to use for this example. @@ -1137,7 +1154,7 @@ Next you'll need to add code to read the environment variables and set the Git c When someone clicks the "Fix this" button, your app receives the [check run webhook](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) with the `requested_action` action type. -In [Step 1.4. Updating a check run](#step-14-updating-a-check-run) you updated the your `event_handler` to handle look for actions in the `check_run` event. You already have a case statement to handle the `created` and `rerequested` action types: +In [Step 1.3. Updating a check run](#step-13-updating-a-check-run) you updated the `event_handler` in your `server.rb` file to look for actions in the `check_run` event. You already have a case statement to handle the `created` and `rerequested` action types: ``` ruby when 'check_run' @@ -1152,16 +1169,18 @@ when 'check_run' end ``` -Add another `when` statement after the `rerequested` case to handle the `rerequested_action` event: +After the `rerequested` case, add the following `when` statement to handle the `rerequested_action` event: -``` ruby +``` ruby{:copy} when 'requested_action' take_requested_action ``` -This code calls a new method that will handle all `requested_action` events for your app. Add the following method to the helper methods section of your code: +This code calls a new method that will handle all `requested_action` events for your app. -``` ruby +Under `helpers do`, add the following helper method: + +``` ruby{:copy} # Handles the check run `requested_action` event # See /webhooks/event-payloads/#check_run def take_requested_action @@ -1194,55 +1213,36 @@ def take_requested_action end ``` -The code above clones a repository just like the code you added in [Step 2.2. Cloning the repository](#step-22-cloning-the-repository). An `if` statement checks that the requested action's identifier matches the RuboCop button identifier (`fix_rubocop_notices`). When they match, the code clones the repository, sets the Git username and email, and runs RuboCop with the option `--auto-correct`. The `--auto-correct` option applies the changes to the local CI server files automatically. - -The files are changed locally, but you'll still need to push them to {% data variables.product.prodname_dotcom %}. You'll use the handy `ruby-git` gem again to commit all of the files. Git has a single command that stages all modified or deleted files and commits them: `git commit -a`. To do the same thing using `ruby-git`, the code above uses the `commit_all` method. Then the code pushes the committed files to {% data variables.product.prodname_dotcom %} using the installation token, using the same authentication method as the Git `clone` command. Finally, it removes the repository directory to ensure the working directory is prepared for the next event. - -That's it! The code you have written now completes your Checks API CI server. 💪 Restart your `server.rb` server again and create a new pull request: - -```shell -$ ruby server.rb -``` - -{% note %} - -**Note:** You'll need to restart the Sinatra server before you can test changes. Enter `Ctrl-C` to stop the server, and then run `ruby server.rb` again. If you don't want to do this every time you change your app code, you can look into [reloading](http://sinatrarb.com/faq.html#reloading). +The code above clones a repository, just like the code you added in [Step 2.2. Clone the repository](#step-22-clone-the-repository). An `if` statement checks that the requested action's identifier matches the RuboCop button identifier (`fix_rubocop_notices`). When they match, the code clones the repository, sets the Git username and email, and runs RuboCop with the option `--auto-correct`. The `--auto-correct` option applies the changes to the local CI server files automatically. -{% endnote %} - -This time, click the "Fix this" button to automatically fix the errors RuboCop found from the **Checks** tab. +The files are changed locally, but you'll still need to push them to {% data variables.product.prodname_dotcom %}. You'll use the `ruby-git` gem to commit all of the files. Git has a single command that stages all modified or deleted files and commits them: `git commit -a`. To do the same thing using `ruby-git`, the code above uses the `commit_all` method. Then the code pushes the committed files to {% data variables.product.prodname_dotcom %} using the installation token, using the same authentication method as the Git `clone` command. Finally, it removes the repository directory to ensure the working directory is prepared for the next event. -In the **Commits** tab, you'll see a brand new commit by the username you set in your Git configuration. You may need to refresh your browser to see the update. +That's it! The code you have written now completes your continuous integration server that you built using a {% data variables.product.prodname_github_app %} and checks. To see the full final code for your app, see "[Full code example](#full-code-example)." -Because a new commit was pushed to the repo, you'll see a new check suite for Octo RuboCop in the **Checks** tab. But this time there are no errors because RuboCop fixed them all. +### Test the code -To see the full final code for the app you just built, see "[Full code example](#full-code-example)." +The following steps will show you how to test that RuboCop can automatically fix the errors it finds. -## Step 2.7. Security tips +1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. -The template {% data variables.product.prodname_github_app %} code already has a method to verify incoming webhook payloads to ensure they are from a trusted source. If you are not validating webhook payloads, you'll need to ensure that when repository names are included in the webhook payload, the webhook does not contain arbitrary commands that could be used maliciously. The code below validates that the repository name only contains Latin alphabetic characters, hyphens, and underscores. To provide you with a complete example, the complete `server.rb` code available in the [companion repository](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) for this tutorial includes both the method of validating incoming webhook payloads and this check to verify the repository name. + ```shell{:copy} + ruby server.rb + ``` -``` ruby -# This tutorial example uses the repository name in the webhook with -# command-line utilities. For security reasons, you should validate the -# repository name to ensure that a bad actor isn't attempting to execute -# arbitrary commands or inject false repository names. 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 -``` +1. In the repository where you installed your app, create a new pull request. +1. In the new pull request you created, navigate to the **Checks** tab, and click the "Fix this" button to automatically fix the errors RuboCop found. +1. Navigate to the **Commits** tab. You should see a new commit by the username you set in your Git configuration. You may need to refresh your browser to see the update. +1. Navigate to the **Checks** tab. You should see a new check suite for Octo RuboCop. But this time there should be no errors, because RuboCop fixed them all. ## Full code example This is what the final code in `server.rb` should look like, after you've followed all of the steps in this tutorial. There are also comments throughout the code that provide additional context. ```ruby{:copy} -require 'sinatra' -require 'octokit' +require 'sinatra' # Uses the Sinatra web framework +require 'octokit' # Uses the Octokit Ruby library to interact with GitHub's REST API require 'dotenv/load' # Manages environment variables -require 'json' +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 @@ -1252,13 +1252,17 @@ require 'git' set :port, 3000 set :bind, '0.0.0.0' +# 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 - # Converts the newlines. Expects that the private key has been set as an - # environment variable in PEM format. + # Expects that the private key has been set as an environment + # variable 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 secret set. The secret is used to verify + # 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'] @@ -1322,52 +1326,34 @@ class GHAapp < Sinatra::Application helpers do - # Create a new check run with the status queued + # Create a new check run with status "queued" def create_check_run - # At the time of writing, Octokit does not support the Checks API, but - # it does provide generic HTTP methods you can use: - # https://developer.github.com/v3/checks/runs/#create-a-check-run - check_run = @installation_client.post( - "repos/#{@payload['repository']['full_name']}/check-runs", - { - # This header allows for beta access to Checks API - accept: 'application/vnd.github.antiope-preview+json', - # The name of your check run. - name: 'Octo RuboCop', - # The payload structure differs depending on whether a check run or a check suite event occurred. - head_sha: @payload['check_run'].nil? ? @payload['check_suite']['head_sha'] : @payload['check_run']['head_sha'] - } + @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' ) - - # You requested the creation of a check run from GitHub. Now, you'll wait - # to get confirmation from GitHub, in the form of a webhook, that it was - # created before starting CI. Equivalently, a 201 response from - # POST /repos/:owner/:repo/check-runs could also be used as confirmation. 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. - # At the time of writing, Octokit doesn't support the Checks API, but - # it does provide generic HTTP methods you can use: - # https://developer.github.com/v3/checks/runs/#update-a-check-run - updated_check_run = @installation_client.patch( - "repos/#{@payload['repository']['full_name']}/check-runs/#{@payload['check_run']['id']}", - { - accept: 'application/vnd.github.antiope-preview+json', - name: 'Octo RuboCop', - status: 'in_progress', - started_at: Time.now.utc.iso8601 - } + @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 ***** - # Ideally this would be performed async, so you could return immediately. - # But for now you'll do a simulated CI process syncronously, and update - # the check run right here. full_repo_name = @payload['repository']['full_name'] repository = @payload['repository']['name'] head_sha = @payload['check_run']['head_sha'] @@ -1383,7 +1369,7 @@ class GHAapp < Sinatra::Application # 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 https://developer.github.com/v3/checks/runs/#update-a-check-run + # See https://docs.github.com/en/rest/checks/runs#update-a-check-run # for details. max_annotations = 50 @@ -1415,6 +1401,8 @@ class GHAapp < Sinatra::Application 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 } @@ -1429,32 +1417,31 @@ class GHAapp < Sinatra::Application 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']}" + 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. - updated_check_run = @installation_client.patch( - "repos/#{@payload['repository']['full_name']}/check-runs/#{@payload['check_run']['id']}", - { - accept: 'application/vnd.github.antiope-preview+json', - name: 'Octo RuboCop', - status: 'completed', - conclusion: conclusion, - completed_at: Time.now.utc.iso8601, - output: { - title: 'Octo RuboCop', - summary: summary, - text: text, - annotations: annotations - }, - actions: [{ - label: 'Fix this', - description: 'Automatically fix all linter notices.', - identifier: 'fix_rubocop_notices' - }] - } + @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 # Handles the check run `requested_action` event @@ -1584,43 +1571,51 @@ class GHAapp < Sinatra::Application end ``` -## Troubleshooting +## Next steps -Here are a few common problems and some suggested solutions. If you run into any other trouble, you can ask for help or advice in the {% data reusables.support.prodname_support_forum_with_url %}. +You should now have an app that receives API events, creates check runs, uses RuboCop to find Ruby errors and create annotations in a pull request, and automatically fix linter errors. Next you might want to expand your app's code, deploy your app, and make your app public. -* **Q:** My app isn't pushing code to {% data variables.product.prodname_dotcom %}. I don't see the fixes that RuboCop automatically makes! +### Modify the app code - **A:** Make sure you have **Read & write** permissions for "Repository contents," and that you are cloning the repository with your installation token. See [Step 2.2. Cloning the repository](#step-22-cloning-the-repository) for details. +This tutorial demonstrated how to create a "Fix this" button that is always displayed in pull requests. You can update the code to display the "Fix this" button only when RuboCop finds errors. -* **Q:** I see an error in the `server.rb` debug output related to cloning my repository. +If you'd prefer that RuboCop doesn't commit files directly to the head branch, you can update the code to instead create a pull request with a new branch that's based on the head branch. - **A:** If you see the following error, you haven't deleted the checkout of the repository in one or both of the `initiate_check_run` or `take_requested_action` methods: +If you have any questions or run into any trouble, you can ask for help or advice on GitHub Community, in the "[API and Webhooks discussions](https://github.com/orgs/community/discussions/categories/api-and-webhooks)." - ```shell - 2018-11-26 16:55:13 - Git::GitExecuteError - git clone '--' 'https://x-access-token:ghs_9b2080277016f797074c4dEbD350745f4257@github.com/codertocat/octocat-breeds.git' 'Octocat-breeds' 2>&1:fatal: destination path 'Octocat-breeds' already exists and is not an empty directory.: - ``` +### Deploy your app - Compare your code to the `server.rb` file to ensure you have the same code in your `initiate_check_run` and `take_requested_action` methods. +This tutorial demonstrated how to develop your app locally. When you are ready to deploy your app, you need to make changes to serve your app and keep your app's credential secure. The steps you take depend on the server that you use, but the following sections offer general guidance. -* **Q:** New check runs are not showing up in the "Checks" tab on {% data variables.product.prodname_dotcom %}. +#### Host your app on a server - **A:** Restart Smee and re-run your `server.rb` server. +This tutorial used your computer or codespace as a server. Once the app is ready for production use, you should deploy your app to a dedicated server. For example, you can use [Azure App Service](https://azure.microsoft.com/products/app-service/). -* **Q:** I do not see the "Re-run all" button in the "Checks" tab on {% data variables.product.prodname_dotcom %}. +#### Update the webhook URL - **A:** Restart Smee and re-run your `server.rb` server. +Once you have a server that is set up to receive webhook traffic from {% data variables.product.company_short %}, update the webhook URL in your app settings. You should not use Smee.io to forward your webhooks in production. -## Conclusion +#### Update the `port` and `host` constants -After walking through this guide, you've learned the basics of using the Checks API to create a CI server! To review, you: +When you deploy your app, you will want to change the host and port where your server is listening. -* Configured your server to receive Checks API events and create check runs. -* Used RuboCop to check code in repositories and create annotations for the errors. -* Implemented a requested action that automatically fixes linter errors. +For example, you can set a `PORT` environment variable on your server to indicate the port where your server should listen. You can set a `NODE_ENV` environment variable on your server to `production`. Then, you can update the place where your code defines the `port` and `host` constants so that your server listens to all available network interfaces (`0.0.0.0`) instead of the local network interface (`localhost`) on your deployment port: -## Next steps +```javascript copy +const port = process.env.PORT || 3000; +const host = process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost'; +``` + +#### Secure your app's credentials + +You should never publicize your app's private key or webhook secret. This tutorial stored your app's credentials in a gitignored `.env` file. When you deploy your app, you should choose a secure way to store the credentials and update your code to get the value accordingly. For example, you can store the credentials with a secret management service like [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault). When your app runs, it can retrieve the credentials and store them in environment variables on the server where your app is deployed. + +For more information, see "[AUTOTITLE](/apps/creating-github-apps/setting-up-a-github-app/best-practices-for-creating-a-github-app)." + +### Share your app + +If you want to share your app with other users and organizations, make your app public. For more information, see "[AUTOTITLE](/apps/creating-github-apps/creating-github-apps/making-a-github-app-public-or-private)." -Here are some ideas for what you can do next: +### Follow best practices -* Currently, the "Fix this" button is always displayed. Update the code you wrote to display the "Fix this" button only when RuboCop finds errors. -* If you'd prefer that RuboCop doesn't commit files directly to the head branch, you can update the code to [create a pull request](/rest/pulls#create-a-pull-request) with a new branch based on the head branch. +You should aim to follow best practices with your {% data variables.product.prodname_github_app %}. For more information, see "[AUTOTITLE](/apps/creating-github-apps/setting-up-a-github-app/best-practices-for-creating-a-github-app)." \ No newline at end of file From 262c52b9aece7bc4d2bb1dd0a24e2dd9ac195113 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Fri, 16 Jun 2023 19:48:10 -0600 Subject: [PATCH 17/48] Code fixes --- .../building-ci-checks-with-a-github-app.md | 200 +++++++++--------- 1 file changed, 103 insertions(+), 97 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 256211fed3ee..c07d3c6912dd 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -205,8 +205,8 @@ This section will show you how to add some basic template code for your {% data Add the following template code to your `server.rb` file: ```ruby{:copy} -require 'sinatra' # Uses the Sinatra web framework -require 'octokit' # Uses the Octokit Ruby library to interact with GitHub's REST API +require 'sinatra' # 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 @@ -263,7 +263,7 @@ class GHAapp < Sinatra::Application post '/event_handler' do - # ADD YOUR CODE HERE # + # ADD EVENT HANDLING HERE # 200 # success status end @@ -271,7 +271,13 @@ class GHAapp < Sinatra::Application helpers do - # ADD YOUR HELPER METHODS HERE # + # 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) @@ -601,22 +607,25 @@ Open the `server.rb` file that you created in "[Add code for your {% data variab ``` ruby post '/event_handler' do - # ADD YOUR CODE HERE # + # ADD EVENT HANDLING HERE # 200 # success status end ``` -This route will handle the `check_suite` event. Under `post '/event_handler' do`, where it says `# ADD YOUR CODE HERE #`, add the following code: +Under `post '/event_handler' do`, where it says `# ADD EVENT HANDLING HERE #`, add the following code. This route will handle the `check_suite` event. ``` ruby{:copy} # 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 ``` @@ -628,7 +637,7 @@ The `requested` action requests a check run each time code is pushed to the repo You'll add this new method as a [Sinatra helper](https://github.com/sinatra/sinatra#helpers) in case you want other routes to use it too. -Under `helpers do`, where it says `# ADD YOUR HELPER METHODS HERE #`, add this `create_check_run` method: +Under `helpers do`, where it says `# ADD CREATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: ``` ruby{:copy} # Create a new check run with status "queued" @@ -675,9 +684,9 @@ So far you've told {% data variables.product.prodname_dotcom %} to create a chec When your `create_check_run` method runs, it asks {% data variables.product.prodname_dotcom %} to create a new check run. When {% data variables.product.prodname_dotcom %} finishes creating the check run, you'll receive the `check_run` webhook event with the `created` action. That event is your signal to begin running the check. -You'll want to update your event handler to look for the `created` action. While you're updating the event handler, you can add a conditional for the `rerequested` action. When someone re-runs a single test on {% data variables.product.prodname_dotcom %} by clicking the "Re-run" button, {% data variables.product.prodname_dotcom %} sends the `rerequested` check run event to your app. When a check run is `rerequested`, you'll want to start the process all over and create a new check run. +You'll update your event handler to look for the `created` action. While you're updating the event handler, you can add a conditional for the `rerequested` action. When someone re-runs a single test on {% data variables.product.prodname_dotcom %} by clicking the "Re-run" button, {% data variables.product.prodname_dotcom %} sends the `rerequested` check run event to your app. When a check run is `rerequested`, you'll start the process all over and create a new check run. To do that, you'll include a condition for the `check_run` event in the `post '/event_handler'` route. -To do that, you'll include a condition for the `check_run` event in the `post '/event_handler'` route. Under `post '/event_handler' do`, add the following code below `case request.env['HTTP_X_GITHUB_EVENT']`: +Under `post '/event_handler' do`, where it says `# ADD CHECK_RUN METHOD HERE #`, add the following code: ``` ruby{:copy} when 'check_run' @@ -688,6 +697,7 @@ when 'check_run' initiate_check_run when 'rerequested' create_check_run + # ADD requested_action METHOD HERE # end end ``` @@ -700,7 +710,7 @@ In this section, you're not going to kick off the CI test yet, but you'll walk t Let's create the `initiate_check_run` method and update the status of the check run. Add the following code to the helpers section: -Under `helpers do`, add the following code: +Under `helpers do`, where it says `# ADD INITIATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: ``` ruby{:copy} # Start the CI process @@ -726,6 +736,7 @@ def initiate_check_run conclusion: 'success', accept: 'application/vnd.github+json' ) + end ``` @@ -735,15 +746,21 @@ Here's what this code is doing. First, it updates the check run's status to `in_ You'll notice in the "[AUTOTITLE](/rest/checks#update-a-check-run)" docs that when you provide a status of `completed`, the `conclusion` and `completed_at` parameters are required. The `conclusion` summarizes the outcome of a check run and can be `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `skipped`, or `action_required`. You'll set the conclusion to `success`, the `completed_at` time to the current time, and the status to `completed`. -You could also provide more details about what your check is doing, but you'll get to that in the next section. Let's test this code again. If your server is currently running, enter `Ctrl-C` in your terminal to stop the server. Run the following command to restart the server: +You could also provide more details about what your check is doing, but you'll get to that in the next section. -```shell{:copy} -ruby server.rb -``` +### Test the code + +The following steps will show you how to test that the code works, and that the new "Re-run all" button you created works. -Head over to your open pull request and click the **Checks** tab. Click the "Re-run all" button in the upper right corner. You should see the check run move from `pending` to `in_progress` and end with `success`. +1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. + + ```shell{:copy} + ruby server.rb + ``` -[TODOCS: I had to push another commit first, before the PR would update to show the "Re-run" button. But pushing another commit already re-runs the test. So it maybe feels weird to also tell them to click the re-run all button... but maybe not? Is there another way to get the re-run all button to show up?] +1. In the repository where you installed your app, create a new pull request. +1. In the pull request you just created, navigate to the **Checks** tab. You should see a "Re-run all" button. +1. Click the "Re-run all" button in the upper right corner. The test should run again, and end with `success`. ## Part 2. Creating the Octo RuboCop CI test @@ -777,7 +794,7 @@ Let's get started! These are the steps you'll complete in this section: You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. RuboCop only checks Ruby code. You'll need to add a Ruby file in your repository that contains errors for RuboCop to find. -1. Navigate to the directory where your app is installed. +1. On {% data variables.product.prodname_dotcom %}, navigate to the repository where your app is installed. 2. Create a new file named `myfile.rb`. For more information, see "[AUTOTITLE](/repositories/working-with-files/managing-files/creating-new-files)." 3. Add the following content to `myfile.rb`: @@ -830,14 +847,14 @@ Next you'll need to update your {% data variables.product.prodname_github_app %} To clone a repository, the code will use your {% data variables.product.prodname_github_app %}'s permissions and the Octokit SDK to create an installation token for your app (`x-access-token:`) and use it in the following clone command: ```shell -git clone https://x-access-token:<TOKEN>@github.com/<OWNER>/<REPO>.git +git clone https://x-access-token:TOKEN@github.com/OWNER/REPO.git ``` The command above clones a repository over HTTP. It requires the full repository name, which includes the repository owner (user or organization) and the repository name. For example, the [octocat Hello-World](https://github.com/octocat/Hello-World) repository has a full name of `octocat/hello-world`. After your app clones the repository, it needs to pull the latest code changes and check out a specific Git ref. The code to do all of this will fit nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. -Open your `server.rb` file. Under `helpers do`, add the following code: +Open your `server.rb` file. Under `helpers do`, where it says `# ADD CLONE_REPOSITORY HELPER METHOD HERE #`, add the following code: ``` ruby{:copy} # Clones the repository to the current working directory, updates the @@ -870,15 +887,17 @@ 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 # ``` The code above gets the full repository name and the head SHA of the commit from the `check_run` webhook payload. ## Step 2.3. Run RuboCop -So far your code clones the repository and creates check runs using your CI server. Now you'll get into the details of the [RuboCop linter](https://docs.rubocop.org/rubocop/usage/basic_usage.html#code-style-checker) and [checks annotations](/rest/checks#create-a-check-run). First, add some code to run RuboCop and save the style code errors in JSON format. +So far your code clones the repository and creates check runs using your CI server. Now you'll get into the details of the [RuboCop linter](https://docs.rubocop.org/rubocop/usage/basic_usage.html#code-style-checker) and [checks annotations](/rest/checks#create-a-check-run). First, add code to run RuboCop and save the style code errors in JSON format. -Under `clone_repository`, which you just added in the [previous step](#step-22-clone-the-repository), add the following code: +Under `clone_repository`, where it says `# ADD CODE HERE TO RUN RUBOCOP #`, add the following code: ``` ruby{:copy} # Run RuboCop on all files in the repository @@ -886,19 +905,16 @@ Under `clone_repository`, which you just added in the [previous step](#step-22-c logger.debug @report `rm -rf #{repository}` @output = JSON.parse @report -``` - -The code above runs RuboCop on all files in the repository's directory. The option `--format json` saves a copy of the linting results in a machine-parsable format. For more information, and an example of the JSON format, see "[JSON Formatter](https://docs.rubocop.org/rubocop/formatters.html#json-formatter)" in the RuboCop docs. -Because this code stores the RuboCop results in a `@report` variable, it can safely remove the checkout of the repository. This code also parses the JSON so you can easily access the keys and values in your {% data variables.product.prodname_github_app %} using the `@output` variable. +# ADD ANNOTATIONS CODE HERE # +``` -{% note %} +The code above runs RuboCop on all files in the repository's directory. The option `--format json` saves a copy of the linting results in a machine-parsable format. For more information, and an example of the JSON format, see "[JSON Formatter](https://docs.rubocop.org/rubocop/formatters.html#json-formatter)" in the RuboCop docs. This code also parses the JSON so you can easily access the keys and values in your {% data variables.product.prodname_github_app %} using the `@output` variable. -**Note:** The command used to remove the repository (`rm -rf`) cannot be undone. See [Step 2.7. Security tips](#step-27-security-tips) to learn how to check webhooks for injected malicious commands that could be used to remove a different directory than intended by your app. For example, if a bad actor sent a webhook with the repository name `./`, your app would remove the root directory. 😱 If for some reason you're _not_ using the method `verify_webhook_signature` (which is included in `server.rb`) to validate the sender of the webhook, make sure you check that the repository name is valid. +After running RuboCop and saving the linting results, this code runs the command `rm -rf` to remove the checkout of the repository. Because the code stores the RuboCop results in a `@report` variable, it can safely remove the checkout of the repository. -TODOCS: If we remove the security tips section, update this note text. +The `rm -rf` command cannot be undone. To keep your app secure, the code in this tutorial checks incoming webhooks for injected malicious commands that could be used to remove a different directory than intended by your app. For example, if a bad actor sent a webhook with the repository name `./`, your app would remove the root directory. The `verify_webhook_signature` method validates the sender of the webhook. The `verify_webhook_signature` event handler also checks that the repository name is valid. For more information, see "[Define a before filter](#define-a-before-filter)." -{% endnote %} ### Test the code @@ -911,7 +927,7 @@ The following steps will show you how to test that the code works and view the e ``` 2. In the repository where you installed your app, create a new pull request. -3. In your terminal tab where the server is running, you should see debug output that contains linting errors. The linting errors are printed without any formatting. You can use a web tool like [JSON formatter](https://jsonformatter.org/) to format your JSON output like the following example, so it's easier to read. +3. In your terminal tab where the server is running, you should see debug output that contains linting errors. The linting errors are printed without any formatting. You can copy and paste your debug output into a web tool like [JSON formatter](https://jsonformatter.org/), to format your JSON output like the following example: ```json { @@ -979,7 +995,7 @@ A check run expects annotations as an array of objects. Each annotation object m Now you'll add code to extract the required information from RuboCop that's needed to create each annotation. -In the [previous step](#step-23-run-rubocop), you added code under `clone_repository` that runs RuboCop on all files in the repository and outputs the linting results as JSON. Immediately under that code block, below `@output = JSON.parse @report`, add the following code: +Under the code you added in the previous step, where it says `# ADD ANNOTATIONS CODE HERE #`, add the following code: ``` ruby{:copy} annotations = [] @@ -1032,6 +1048,8 @@ else end end end + +# ADD CODE HERE TO UPDATE CHECK RUN SUMMARY # ``` This code limits the total number of annotations to 50. But you can modify this code to update the check run for each batch of 50 annotations. The code above includes the variable `max_annotations` that sets the limit to 50, which is used in the loop that iterates through the offenses. @@ -1050,21 +1068,7 @@ Each check run from {% data variables.product.prodname_dotcom %} contains an `ou For the `summary`, this example uses the summary information from RuboCop and adds newlines (`\n`) to format the output. You can customize what you add to the `text` parameter, but this example sets the `text` parameter to the RuboCop version. The following code sets the `summary` and `text`. -In the [previous step](#step-24-collect-rubocop-errors), you appended code to an existing code block in your `server.rb` file. The end of that code block should look like this: - -```ruby - # 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 -``` - -Below the final `end`, add the following code: +Under the code you added in the previous step, where it says `# ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #`, add the following code: ``` ruby{:copy} # Updated check run summary and text parameters @@ -1165,11 +1169,12 @@ when 'check_run' initiate_check_run when 'rerequested' create_check_run + # ADD requested_action METHOD HERE # end end ``` -After the `rerequested` case, add the following `when` statement to handle the `rerequested_action` event: +After the `rerequested` case, where it says `# ADD requested_action METHOD HERE #`, add the following code: ``` ruby{:copy} when 'requested_action' @@ -1178,7 +1183,7 @@ when 'requested_action' This code calls a new method that will handle all `requested_action` events for your app. -Under `helpers do`, add the following helper method: +Under `helpers do`, where it says `# ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #`, add the following helper method: ``` ruby{:copy} # Handles the check run `requested_action` event @@ -1239,8 +1244,8 @@ The following steps will show you how to test that RuboCop can automatically fix This is what the final code in `server.rb` should look like, after you've followed all of the steps in this tutorial. There are also comments throughout the code that provide additional context. ```ruby{:copy} -require 'sinatra' # Uses the Sinatra web framework -require 'octokit' # Uses the Octokit Ruby library to interact with GitHub's REST API +require 'sinatra' # 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 @@ -1274,7 +1279,6 @@ class GHAapp < Sinatra::Application set :logging, Logger::DEBUG end - # Executed before each request to the `/event_handler` route before '/event_handler' do get_payload_request(request) @@ -1295,14 +1299,14 @@ class GHAapp < Sinatra::Application 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' + if @payload['action'] == 'requested' || @payload['action'] == 'rerequested' create_check_run end @@ -1318,12 +1322,13 @@ class GHAapp < Sinatra::Application take_requested_action end end + end + 200 # success status end - helpers do # Create a new check run with status "queued" @@ -1340,6 +1345,7 @@ class GHAapp < Sinatra::Application 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 @@ -1353,7 +1359,6 @@ class GHAapp < Sinatra::Application accept: 'application/vnd.github+json' ) - # ***** RUN A CI TEST ***** full_repo_name = @payload['repository']['full_name'] repository = @payload['repository']['name'] head_sha = @payload['check_run']['head_sha'] @@ -1365,15 +1370,16 @@ class GHAapp < Sinatra::Application 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 https://docs.github.com/en/rest/checks/runs#update-a-check-run + # See /rest/reference/checks#update-a-check-run # for details. max_annotations = 50 - # RuboCop reports the number of errors found in 'offense_count' + # RuboCop reports the number of errors found in "offense_count" if @output['summary']['offense_count'] == 0 conclusion = 'success' else @@ -1416,11 +1422,39 @@ class GHAapp < Sinatra::Application end end + # Handles the check run `requested_action` event + # See /webhooks/event-payloads/#check_run + def take_requested_action + full_repo_name = @payload['repository']['full_name'] + repository = @payload['repository']['name'] + head_branch = @payload['check_run']['check_suite']['head_branch'] + + if (@payload['requested_action']['identifier'] == 'fix_rubocop_notices') + clone_repository(full_repo_name, repository, head_branch) + + # Sets your commit username and email address + @git.config('user.name', ENV['GITHUB_APP_USER_NAME']) + @git.config('user.email', ENV['GITHUB_APP_USER_EMAIL']) + + # Automatically correct RuboCop style errors + @report = `rubocop '#{repository}/*' --format json --auto-correct` + + pwd = Dir.getwd() + Dir.chdir(repository) + begin + @git.commit_all('Automatically fix Octo RuboCop notices.') + @git.push("https://x-access-token:#{@installation_token.to_s}@github.com/#{full_repo_name}.git", head_branch) + rescue + # Nothing to commit! + puts 'Nothing to commit' + end + Dir.chdir(pwd) + `rm -rf '#{repository}'` + end + end + # 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']}" + 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. @@ -1442,37 +1476,7 @@ class GHAapp < Sinatra::Application }], accept: 'application/vnd.github+json' ) - end - # Handles the check run `requested_action` event - # See https://developer.github.com/v3/activity/events/types/#checkrunevent - 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 # Clones the repository to the current working directory, updates the @@ -1490,6 +1494,8 @@ class GHAapp < Sinatra::Application Dir.chdir(pwd) 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 @@ -1500,7 +1506,7 @@ class GHAapp < Sinatra::Application begin @payload = JSON.parse @payload_raw rescue => e - fail "Invalid JSON (#{e}): #{@payload_raw}" + fail 'Invalid JSON (#{e}): #{@payload_raw}' end end @@ -1573,15 +1579,15 @@ end ## Next steps -You should now have an app that receives API events, creates check runs, uses RuboCop to find Ruby errors and create annotations in a pull request, and automatically fix linter errors. Next you might want to expand your app's code, deploy your app, and make your app public. +You should now have an app that receives API events, creates check runs, and uses RuboCop to find Ruby errors, create annotations in a pull request, and automatically fix linter errors. Next you might want to expand your app's code, deploy your app, and make your app public. -### Modify the app code +If you have any questions, you can ask for help or advice on GitHub Community, in the "[API and Webhooks discussions](https://github.com/orgs/community/discussions/categories/api-and-webhooks)." -This tutorial demonstrated how to create a "Fix this" button that is always displayed in pull requests. You can update the code to display the "Fix this" button only when RuboCop finds errors. +### Modify the app code -If you'd prefer that RuboCop doesn't commit files directly to the head branch, you can update the code to instead create a pull request with a new branch that's based on the head branch. +This tutorial demonstrated how to create a "Fix this" button that is always displayed in pull requests in the repository. Try updating the code to display the "Fix this" button only when RuboCop finds errors. -If you have any questions or run into any trouble, you can ask for help or advice on GitHub Community, in the "[API and Webhooks discussions](https://github.com/orgs/community/discussions/categories/api-and-webhooks)." +If you'd prefer that RuboCop doesn't commit files directly to the head branch, update the code to instead create a pull request with a new branch that's based on the head branch. ### Deploy your app From 91bbe91ad64763d6b66ad758a9a7feba224e07e6 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Mon, 19 Jun 2023 19:39:55 -0600 Subject: [PATCH 18/48] More tutorial updates --- .../building-ci-checks-with-a-github-app.md | 256 ++++++++---------- 1 file changed, 113 insertions(+), 143 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index c07d3c6912dd..6be9b54d3037 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -24,6 +24,11 @@ In this tutorial, you will use your computer or codespace as a server while you This tutorial uses Ruby, but you can use any programming language that you can run on your server. +This tutorial is broken into two parts: + +- In part one, you'll learn how to set up the framework for a CI server using {% data variables.product.prodname_dotcom %}'s REST API, create new check runs for CI tests when a repository receives newly pushed commits, and re-run check runs when a user requests that action on {% data variables.product.prodname_dotcom %}. +- In part two, you'll add a linter test to your CI server, create annotations that are displayed in the **Checks** and **Files Changed** tab of a pull request, and automatically fix linter recommendations by exposing a "Fix this" button in the **Checks** tab of the pull request. + ### About continuous integration (CI) CI is a software practice that requires frequently committing code to a shared repository. Committing code more often raises errors sooner and reduces the amount of code a developer needs to debug when finding the source of an error. Frequent code updates also make it easier to merge changes from different members of a software development team. This is great for developers, who can spend more time writing code and less time debugging errors or resolving merge conflicts. @@ -42,40 +47,23 @@ Checks include check runs, check suites, and commit statuses. Both check suites and check runs contain commit statuses. For more information about checks, see "[AUTOTITLE](/rest/checks)" and "[AUTOTITLE](/rest/guides/using-the-rest-api-to-interact-with-checks)." -You can also create annotations with additional details for specific lines of code. Annotations are visible in the **Checks** tab. When you create an annotation for a file that is part of the pull request, the annotations are also shown in the **Files changed** tab. - Each time new code is pushed to a repository, {% data variables.product.prodname_dotcom %} sends the [`check_suite` webhook event](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) to all {% data variables.product.prodname_github_apps %} installed on the repository. To receive all checks event actions, the app must have the `checks:write` permission. {% data variables.product.prodname_dotcom %} automatically creates `check_suite` events for new code commits in a repository using the default flow, although you can change the default settings. For more information, see "[AUTOTITLE](/rest/checks#update-repository-preferences-for-check-suites)." Here's how the default flow works: 1. Whenever someone pushes code to the repository, {% data variables.product.prodname_dotcom %} sends the `check_suite` event with an action of `requested` to all {% data variables.product.prodname_github_apps %} installed on the repository that have the `checks:write` permission. This event lets the apps know that code was pushed and that {% data variables.product.prodname_dotcom %} has automatically created a new check suite. 1. When your app receives this event, it can [add check runs](/rest/checks#create-a-check-run) to that suite. -1. Your check runs can include [annotations](/rest/checks#annotations-object) that are displayed on specific lines of code. - -**In this guide, you’ll learn how to:** - -* Part 1: Set up the framework for a CI server using the Checks API. - * Configure a {% data variables.product.prodname_github_app %} as a server that receives Checks API events. - * Create new check runs for CI tests when a repository receives newly pushed commits. - * Re-run check runs when a user requests that action on {% data variables.product.prodname_dotcom %}. -* Part 2: Build on the CI server framework you created by adding a linter CI test. - * Update a check run with a `status`, `conclusion`, and `output` details. - * Create annotations on lines of code that {% data variables.product.prodname_dotcom %} displays in the **Checks** and **Files Changed** tab of a pull request. - * Automatically fix linter recommendations by exposing a "Fix this" button in the **Checks** tab of the pull request. +1. Your check runs can include [annotations](/rest/checks#annotations-object) that are displayed on specific lines of code. Annotations are visible in the **Checks** tab. When you create an annotation for a file that is part of the pull request, the annotations are also shown in the **Files changed** tab. ## Prerequisites This tutorial assumes you have a basic understanding of the [Ruby programming language](https://www.ruby-lang.org/en/). -This tutorial requires your computer or codespace to run Node.js version 12 or greater and npm version 6.12.0 or greater. For more information, see [Node.js](https://nodejs.org/en). TODOCS: Is this true? Grabbed it from other tutorial. - Before you get started, you may want to familiarize yourself with the following concepts: - [{% data variables.product.prodname_github_apps %}](/apps) - [Webhooks](/webhooks-and-events/webhooks/about-webhooks) - [REST API checks endpoints](/rest/checks) -The Checks endpoints are also available to use in GraphQL, but this tutorial focuses on REST. For more information about the GraphQL objects, see [Checks Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. - -This tutorial also uses the [Smee](https://smee.io/) webhook payload delivery service, the [Octokit.rb Ruby library](https://octokit.github.io/octokit.rb/) for the {% data variables.product.prodname_dotcom %} REST API, and the [Sinatra web framework](https://sinatrarb.com/). +The Checks endpoints are also available to use in GraphQL, but this tutorial focuses on REST. For more information about the GraphQL objects, see [Check Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. ## Setup @@ -116,7 +104,7 @@ In order to develop your app locally, you can use a webhook proxy URL to forward 1. In your browser, navigate to https://smee.io/. 1. Click **Start a new channel**. -1. Copy the full URL under "Webhook Proxy URL". You will use this URL in a following step, and during the app registration steps later in the tutorial. +1. Copy the full URL under "Webhook Proxy URL." You will use this URL in a following step, and during the app registration steps later in the tutorial. 1. In a terminal, run the following command to install the Smee client: ```shell{:copy} npm install --global smee-client @@ -131,9 +119,9 @@ In order to develop your app locally, you can use a webhook proxy URL to forward Connected https://smee.io/YOUR_DOMAIN ``` -The `smee --url https://smee.io/YOUR_DOMAIN` command tells Smee to forward all webhook events received by the Smee channel to the Smee client running on your computer. The `--path /event_handler` option forwards events to the `/event_handler` route, which we'll cover in a [later section](#review-the-template-code). The `--port 3000` option specifies port 3000, which is the port your server will be listening to. Using Smee, your machine does not need to be open to the public internet to receive webhooks from {% data variables.product.prodname_dotcom %}. You can also open that Smee URL in your browser to inspect webhook payloads as they come in. +The `smee --url https://smee.io/YOUR_DOMAIN` command tells Smee to forward all webhook events received by the Smee channel to the Smee client running on your computer. The `--path /event_handler` option forwards events to the `/event_handler` route. The `--port 3000` option specifies port 3000, which is the port we'll tell your server to listen to, when we add more code later in the tutorial. Using Smee, your machine does not need to be open to the public internet to receive webhooks from {% data variables.product.prodname_dotcom %}. You can also open that Smee URL in your browser to inspect webhook payloads as they come in. -We recommend leaving this terminal window open and keeping Smee connected while you complete the rest of the steps in this guide. Although you _can_ disconnect and reconnect the Smee client without losing your unique domain, you may find it easier to leave it connected and do other command-line tasks in a different terminal window. +We recommend leaving this terminal window open and keeping Smee connected while you complete the rest of the steps in this guide. Although you can disconnect and reconnect the Smee client without losing your unique domain, you may find it easier to leave it connected and do other command-line tasks in a different terminal window. ### Register a {% data variables.product.prodname_github_app %} @@ -154,18 +142,18 @@ The following steps will guide you through configuring a {% data variables.produ 1. Under "Homepage URL," enter a URL for your app. For example, you can use the URL of the repository that you created to store the code for your app. 1. Skip the "Identifying and authorizing users" and "Post installation" sections for this tutorial. For more information about these settings, see "[AUTOTITLE](/apps/creating-github-apps/creating-github-apps/creating-a-github-app)." 1. Make sure that **Active** is selected under "Webhooks." -1. Under "Webhook URL", enter your webhook proxy URL from earlier. For more information, see "[Get a webhook proxy URL](#get-a-webhook-proxy-url)." +1. Under "Webhook URL," enter your webhook proxy URL from earlier. For more information, see "[Get a webhook proxy URL](#get-a-webhook-proxy-url)." 1. Under "Webhook secret," enter a random string. This secret is used to verify that webhooks are sent by {% data variables.product.prodname_dotcom %}. You will use this string later. 1. Under "Repository permissions," next to "Checks," select **Read & write**. 1. Under "Subscribe to events," select **Check suite** and **Check run**. -1. Under "Where can this GitHub App be installed?", select **Only on this account**. You can change this later if you want to publish your app. +1. Under "Where can this GitHub App be installed?," select **Only on this account**. You can change this later if you want to publish your app. 1. Click **Create GitHub App**. ### Store your app's identifying information and credentials -This tutorial will show you how to store your app's credentials and identifying information as environment variables in a `.env` file. When you deploy your app, you will want to change how you store the credentials. For more information, see "[Deploy your app](#deploy-your-app)." +This tutorial will show you how to store your app's credentials and identifying information as environment variables in a `.env` file. When you deploy your app, you should change how you store the credentials. For more information, see "[Deploy your app](#deploy-your-app)." -Make sure that you are on a secure machine before performing these steps since you will store your credentials locally. +Make sure that you are on a secure machine before performing these steps, since you will store your credentials locally. 1. In your terminal, navigate to the directory where your clone is stored. 1. Create a file called `.env` at the top level of this directory. @@ -223,15 +211,14 @@ set :bind, '0.0.0.0' class GHAapp < Sinatra::Application - # Expects that the private key has been set as an environment - # variable in PEM format. Converts the newlines. + # 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. + # 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) set when registering an app. + # The GitHub App's identifier (type integer). APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] # Turn on Sinatra's verbose logging during development @@ -245,12 +232,8 @@ class GHAapp < Sinatra::Application get_payload_request(request) verify_webhook_signature - # This tutorial example uses the repository name in the webhook with - # command line utilities. For security reasons, you should validate the - # repository name to ensure that a bad actor isn't attempting to execute - # arbitrary commands or inject false repository names. If a repository name - # is provided in the webhook, validate that it consists only of latin - # alphabetic characters, `-`, and `_`. + # 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 @@ -382,14 +365,14 @@ The class in the template does the following things: First, this class reads the three environment variables you set in "[Store your app's identifying information and credentials](#store-your-apps-identifying-information-and-credentials)," and stores them in variables to use later: ``` ruby -# Expects that the private key in PEM format. Converts the newlines +# 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 secret set. The secret is used to verify -# that webhooks are sent by GitHub. +# 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) set when registering an app. +# The GitHub App's identifier (type integer). APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] ``` @@ -414,12 +397,8 @@ Sinatra uses [before filters](https://github.com/sinatra/sinatra#filters) that a get_payload_request(request) verify_webhook_signature - # This tutorial example uses the repository name in the webhook with - # command line utilities. For security reasons, this code validates the - # repository name to ensure that a bad actor isn't attempting to execute - # arbitrary commands or inject false repository names. If a repository name - # is provided in the webhook, validate that it consists only of latin - # alphabetic characters, `-`, and `_`. + # 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 @@ -430,7 +409,7 @@ Sinatra uses [before filters](https://github.com/sinatra/sinatra#filters) that a end ``` -Under `verify_webhook_signature`, the code that starts with `unless @payload` is an additional security measure. If a repository name is provided with a webhook payload, this code validates that the repository name only contains Latin alphabetic characters, hyphens, and underscores. This helps ensure that a bad actor isn't attempting to execute arbitrary commands or inject false repository names. The `verify_webhook_signature` helper method also validates incoming webhook payloads. For more information, see "[Define the helper methods](#verifying-the-webhook-signature)." +Under `verify_webhook_signature`, the code that starts with `unless @payload` is an additional security measure. If a repository name is provided with a webhook payload, this code validates that the repository name contains only Latin alphabetic characters, hyphens, and underscores. This helps ensure that a bad actor isn't attempting to execute arbitrary commands or inject false repository names. As an additional security measure, the `verify_webhook_signature` helper method also validates incoming webhook payloads. For more information, see "[Define the helper methods](#verifying-the-webhook-signature)." #### Define a route handler @@ -444,18 +423,20 @@ end #### Define the helper methods -Four helper methods are called in the `before` block of the template code. The `helpers do` block in the template defines each of these helper methods. +Four helper methods are called in the `before` block of the template code. The `helpers do` code block defines each of these helper methods. ##### Handling the webhook payload -The first method `get_payload_request` captures the webhook payload and converts it to JSON format, which makes accessing the payload's data much easier. +The first helper method `get_payload_request` captures the webhook payload and converts it to JSON format, which makes accessing the payload's data much easier. ##### Verifying the webhook signature -The second method `verify_webhook_signature` performs verification of the webhook signature to ensure that {% data variables.product.prodname_dotcom %} generated the event. To learn more about the code in the `verify_webhook_signature` helper method, see "[AUTOTITLE](/webhooks-and-events/webhooks/securing-your-webhooks)." If the webhooks are secure, this method will log all incoming payloads to your terminal. The logger code is helpful in verifying your web server is working but you can always remove it later. +The second helper method `verify_webhook_signature` performs verification of the webhook signature to ensure that {% data variables.product.prodname_dotcom %} generated the event. To learn more about the code in the `verify_webhook_signature` helper method, see "[AUTOTITLE](/webhooks-and-events/webhooks/securing-your-webhooks)." If the webhooks are secure, this method will log all incoming payloads to your terminal. The logger code is helpful in verifying your web server is working. ##### Authenticating as a {% data variables.product.prodname_github_app %} +The third helper method `authenticate_app` allows your {% data variables.product.prodname_github_app %} to authenticate, so it can request an installation token. + To make API calls, you'll be using the [Octokit library](https://octokit.github.io/octokit.rb/). Doing anything interesting with this library will require your {% data variables.product.prodname_github_app %} to authenticate. {% data variables.product.prodname_github_apps %} have two methods of authentication: - Authenticating as a {% data variables.product.prodname_github_app %} using a [JSON Web Token (JWT)](https://jwt.io/introduction). @@ -470,7 +451,7 @@ Authenticating as a {% data variables.product.prodname_github_app %} lets you do For example, you would authenticate as a {% data variables.product.prodname_github_app %} to retrieve a list of the accounts (organization and personal) that have installed your app. But this authentication method doesn't allow you to do much with the API. To access a repository's data and perform operations on behalf of the installation, you need to authenticate as an installation. To do that, you'll need to authenticate as a {% data variables.product.prodname_github_app %} first to request an installation access token. For more information, see "[AUTOTITLE](/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app)." -Before you can use the Octokit.rb library to make API calls, you'll need to initialize an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as a GitHub App, using the `authenticate_app` helper method. +Before you can use the Octokit.rb library to make API calls, you'll need to initialize an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as a {% data variables.product.prodname_github_app %}, using the `authenticate_app` helper method. ``` ruby # Instantiate an Octokit client authenticated as a GitHub App. @@ -502,7 +483,9 @@ The code above generates a JSON Web Token (JWT) and uses it (along with your app ##### Authenticating as an installation -An _installation_ refers to any user or organization account that has installed the app. Even if someone installs the app on more than one repository, it only counts as one installation because it's within the same account. The last helper method `authenticate_installation` initializes an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as an installation. This Octokit client is what you'd use to make authenticated API calls. +The fourth and final helper method `authenticate_installation` initializes an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as an installation, which you can use to make authenticated calls to the API. + +An _installation_ refers to any user or organization account that has installed the app. Even if someone installs the app on more than one repository, it only counts as one installation because it's within the same account. ``` ruby # Instantiate an Octokit client authenticated as an installation of a @@ -527,7 +510,7 @@ With this method in place, each time your app receives a new webhook payload, it Your app doesn't do anything yet, but at this point, you can get it running on the server. -Keep Smee running in the current tab in your terminal. Open a new tab and `cd` into the directory where you [cloned the template app code](#create-a-repository-to-store-code-for-your-app). The Ruby code in this repository will start up a [Sinatra](https://sinatrarb.com/) web server. This code has a few dependencies. You can install these by running: +Keep Smee running in the current tab in your terminal. Open a new tab and `cd` into the directory where you [cloned the repository you created](#create-a-repository-to-store-code-for-your--data-variablesproductprodname_github_app). The Ruby code in this repository will start up a [Sinatra](https://sinatrarb.com/) web server. This code has a few dependencies. You can install these by running: ```shell{:copy} gem install bundler @@ -561,16 +544,14 @@ You should see a response like: If you see an error, make sure you've created the `.env` file in the directory that contains `server.rb`. -Once the server is running, test it by going to `http://localhost:3000` in your browser. If you see an error page that says "Sinatra doesn't know this ditty," the app is working as expected. - -Even though it's an error page, it's a Sinatra error page, which means your app is connected to the server as expected. You're seeing this message because you haven't given the app anything else to show. +Once the server is running, test it by going to `http://localhost:3000` in your browser. If you see an error page that says "Sinatra doesn't know this ditty," the app is working as expected. Even though it's an error page, it's a Sinatra error page, which means your app is connected to the server as expected. You're seeing this message because you haven't given the app anything else to show. ## Test that the server is listening to your app -You can test that the server is listening to your app by triggering an event for it to receive. A simple event you can test is installing the app on your {% data variables.product.prodname_dotcom %} account, which should send the [`installation`](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) event. If the app receives it, you should see some output in the terminal tab where you started `server.rb`. +You can test that the server is listening to your app by triggering an event for it to receive. You'll do that by installing the app on your {% data variables.product.prodname_dotcom %} account, which will send the [`installation` event](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) to your app. If the app receives it, you should see output in the terminal tab where you're running `server.rb`. 1. Install the {% data variables.product.prodname_github_app %} on your account. For more information, see "[AUTOTITLE](/apps/using-github-apps/installing-your-own-github-app#installing-your-own-github-app)." You can choose to install it on all of your repositories, or just one. For example, you could install it on the repository you created for this tutorial. -2. After you click **Install**, look at the output in the terminal tab where you started `server.rb`. You should see something like this: +2. After you click **Install**, look at the output in the terminal tab where you're running `server.rb`. You should see something like this: ```shell > D, [2023-06-08T15:45:43.773077 #30488] DEBUG -- : ---- received event installation @@ -580,19 +561,19 @@ You can test that the server is listening to your app by triggering an event for If you see output like this, it means your app received a notification that it was installed on your {% data variables.product.prodname_dotcom %} account. The app is running on the server as expected. -If you don't see this output, make sure Smee is running correctly in another terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in terminal. If Smee isn't the problem, see the "[Troubleshooting](#troubleshooting)" section for other ideas. [TODOCS: Remove troubleshooting link if we remove troubleshooting section.] +If you don't see this output, make sure Smee is running correctly in another terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in terminal. -If you're wondering where the terminal output above is coming from, it's written in the app template code you added to `server.rb` in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your-github-app)". +If you're wondering where the terminal output above is coming from, it's written in the app template code you added to `server.rb` in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your-github-app)." ## Part 1. Creating the Checks API interface In this part, you will add the code necessary to receive `check_suite` webhook events and create and update check runs. You'll also learn how to create check runs when a check was re-requested on {% data variables.product.prodname_dotcom %}. At the end of this section, you'll be able to view the check run you created in a {% data variables.product.prodname_dotcom %} pull request. -Your check run will not be performing any checks on the code in this section. You'll add that functionality in [Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test). +Your check run will not perform any checks on the code in this section. You'll add that functionality in [Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test). You should already have a Smee channel configured that is forwarding webhook payloads to your local server. Your server should be running and connected to the {% data variables.product.prodname_github_app %} you registered and installed on a test repository. -Let's get started! These are the steps you'll complete in Part 1: +These are the steps you'll complete in Part 1: 1. [Add event handling](#step-11-add-event-handling) 1. [Create a check run](#step-12-create-a-check-run) @@ -602,7 +583,7 @@ Let's get started! These are the steps you'll complete in Part 1: Because your app is subscribed to the **Check suite** and **Check run** events, it will receive the [`check_suite`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) and [`check_run`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) webhooks. {% data variables.product.prodname_dotcom %} sends webhook payloads as `POST` requests. Because you forwarded your Smee webhook payloads to `http://localhost:3000/event_handler`, your server will receive the `POST` request payloads at the `post '/event_handler'` route. -Open the `server.rb` file that you created in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your--data-variablesproductprodname_github_app)", and look for the following code. An empty `post '/event_handler'` route is already included in the template code. The empty route looks like this: +Open the `server.rb` file that you created in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your--data-variablesproductprodname_github_app)," and look for the following code. An empty `post '/event_handler'` route is already included in the template code. The empty route looks like this: ``` ruby post '/event_handler' do @@ -666,21 +647,24 @@ You're only supplying the required parameters now to get the basic functionality In the code above, you're using the [ternary operator](https://ruby-doc.org/core-2.3.0/doc/syntax/control_expressions_rdoc.html#label-Ternary+if), which works like an `if/else` statement, to check if the payload contains a `check_run` object. If it does, you read the `head_sha` from the `check_run` object, otherwise you read it from the `check_suite` object. -### Test that a check run is created +### Test the code -To test the code you just added, use the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. +The following steps will show you how to test that the code works, and that it successfully creates a new check run. -```shell{:copy} -ruby server.rb -``` +1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. -Now open a pull request in the repository where you installed your app. Your app should respond by creating a check run on your pull request. Click on the **Checks** tab, and you should see a check run with the name "Octo RuboCop," or whichever name you chose earlier for the check run. + ```shell{:copy} + ruby server.rb + ``` -If you see other apps in the Checks tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. +1. In the repository where you installed your app, create a new pull request. +1. In the pull request you just created, navigate to the **Checks** tab. You should see a check run with the name "Octo RuboCop," or whichever name you chose earlier for the check run. + +If you see other apps in the **Checks** tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. So far you've told {% data variables.product.prodname_dotcom %} to create a check run. The check run status in the pull request is set to queued with a yellow icon. Next, you'll want to wait for {% data variables.product.prodname_dotcom %} to create the check run and update its status. -## Step 1.3. Updating a check run +## Step 1.3. Update a check run When your `create_check_run` method runs, it asks {% data variables.product.prodname_dotcom %} to create a new check run. When {% data variables.product.prodname_dotcom %} finishes creating the check run, you'll receive the `check_run` webhook event with the `created` action. That event is your signal to begin running the check. @@ -697,7 +681,7 @@ when 'check_run' initiate_check_run when 'rerequested' create_check_run - # ADD requested_action METHOD HERE # + # ADD REQUESTED_ACTION METHOD HERE # end end ``` @@ -708,7 +692,7 @@ Next you'll write the `initiate_check_run` method, which is where you'll update In this section, you're not going to kick off the CI test yet, but you'll walk through how to update the status of the check run from `queued` to `pending` and then from `pending` to `completed` to see the overall flow of a check run. In "[Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test)," you'll add the code that actually performs the CI test. -Let's create the `initiate_check_run` method and update the status of the check run. Add the following code to the helpers section: +Let's create the `initiate_check_run` method and update the status of the check run. Under `helpers do`, where it says `# ADD INITIATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: @@ -742,9 +726,9 @@ end The code above calls the "[AUTOTITLE](/rest/checks#update-a-check-run)" API endpoint using the [`update_check_run` Octokit method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#update_check_run-instance_method) to update the check run that you already created. -Here's what this code is doing. First, it updates the check run's status to `in_progress` and implicitly sets the `started_at` time to the current time. In [Part 2](#part-2-creating-the-octo-rubocop-ci-test) of this tutorial, you'll add code that kicks off a real CI test under `***** RUN A CI TEST *****`. For now, you'll leave that section as a placeholder, so the code that follows it will just simulate that the CI process succeeds and all tests pass. Finally, the code updates the status of the check run again to `completed`. +Here's what this code is doing. First, it updates the check run's status to `in_progress` and implicitly sets the `started_at` time to the current time. In Part 2 of this tutorial, you'll add code that kicks off a real CI test under `***** RUN A CI TEST *****`. For now, you'll leave that section as a placeholder, so the code that follows it will just simulate that the CI process succeeds and all tests pass. Finally, the code updates the status of the check run again to `completed`. -You'll notice in the "[AUTOTITLE](/rest/checks#update-a-check-run)" docs that when you provide a status of `completed`, the `conclusion` and `completed_at` parameters are required. The `conclusion` summarizes the outcome of a check run and can be `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `skipped`, or `action_required`. You'll set the conclusion to `success`, the `completed_at` time to the current time, and the status to `completed`. +You'll notice in the "[AUTOTITLE](/rest/checks#update-a-check-run)" documentation that when you provide a status of `completed`, the `conclusion` and `completed_at` parameters are required. The `conclusion` summarizes the outcome of a check run and can be `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `skipped`, or `action_required`. You'll set the conclusion to `success`, the `completed_at` time to the current time, and the status to `completed`. You could also provide more details about what your check is doing, but you'll get to that in the next section. @@ -780,15 +764,14 @@ Annotations are information about specific lines of code in a repository. An ann To take advantage of requested actions, app developers can create buttons in the **Checks** tab of pull requests. When someone clicks one of these buttons, the click sends a `requested_action` `check_run` event to the {% data variables.product.prodname_github_app %}. The action that the app takes is completely configurable by the app developer. This tutorial will walk you through adding a button that allows users to request that RuboCop fix the errors it finds. RuboCop supports automatically fixing errors using a command-line option, and you'll configure the `requested_action` to take advantage of this option. -Let's get started! These are the steps you'll complete in this section: +These are the steps you'll complete in this section: -1. [Adding a Ruby file](#step-21-adding-a-ruby-file) -1. [Cloning the repository](#step-22-cloning-the-repository) -1. [Running RuboCop](#step-23-running-rubocop) -1. [Collecting RuboCop errors](#step-24-collecting-rubocop-errors) -1. [Updating the check run with CI test results](#step-25-updating-the-check-run-with-ci-test-results) -1. [Automatically fixing RuboCop errors](#step-26-automatically-fixing-rubocop-errors) -1. [Security tips](#step-27-security-tips) +1. [Add a Ruby file](#step-21-add-a-ruby-file) +1. [Clone the repository](#step-22-clone-the-repository) +1. [Run RuboCop](#step-23-run-rubocop) +1. [Collect RuboCop errors](#step-24-collect-rubocop-errors) +1. [Update the check run with CI test results](#step-25-update-the-check-run-with-ci-test-results) +1. [Automatically fix RuboCop errors](#step-26-automatically-fix-rubocop-errors) ## Step 2.1. Add a Ruby file @@ -873,7 +856,7 @@ def clone_repository(full_repo_name, repository, ref) end ``` -The code above uses the `ruby-git` gem to clone the repository using the app's installation token. This code clones the code in the same directory as `server.rb`. To run Git commands in the repository, the code needs to change into the repository directory. Before changing directories, the code stores the current working directory in a variable (`pwd`) to remember where to return before exiting the `clone_repository` method. +The code above uses the `ruby-git` gem to clone the repository using the app's installation token. It clones the code in the same directory as `server.rb`. To run Git commands in the repository, the code needs to change into the repository directory. Before changing directories, the code stores the current working directory in a variable (`pwd`) to remember where to return before exiting the `clone_repository` method. From the repository directory, this code fetches and merges the latest changes (`@git.pull`), checks out the ref (`@git.checkout(ref)`), then changes the directory back to the original working directory (`pwd`). @@ -987,7 +970,7 @@ The following steps will show you how to test that the code works and view the e The `@output` variable contains the parsed JSON results of the RuboCop report. As shown in the example output in the previous step, the results contain a `summary` section that your code can use to quickly determine if there are any errors. The following code will set the check run conclusion to `success` when there are no reported errors. RuboCop reports errors for each file in the `files` array, so if there are errors, you'll need to extract some data from the file object. -The REST API checks endpoints allow you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this tutorial you will update the check run with annotations, using the "[Update a check run](/rest/checks#update-a-check-run)" endpoint. +The REST API checks endpoints allow you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this tutorial you will update the check run with annotations, using the "[Update a check run](/rest/checks/runs#update-a-check-run)" endpoint. The API limits the number of annotations to a maximum of 50 per request. To create more than 50 annotations, you will have to make multiple requests to the "Update a check run" endpoint. For example, to create 105 annotations you would need to make three separate requests to the API. The first two requests would each have 50 annotations, and the third request would include the five remaining annotations. Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. @@ -1076,7 +1059,7 @@ summary = "Octo RuboCop summary\n-Offense count: #{@output['summary']['offense_c text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}" ``` -Now your code should have all the information it needs to update your check run. In [Part 1 of this tutorial](#step-13-updating-a-check-run), you added code to set the status of the check run to `success`. You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). Here's the code you added previously to your `server.rb` file: +Now your code should have all the information it needs to update your check run. In [Part 1 of this tutorial](#step-13-update-a-check-run), you added code to set the status of the check run to `success`. You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). Here's the code you added previously to your `server.rb` file: ``` ruby # Mark the check run as complete! @@ -1121,7 +1104,7 @@ The code above doesn't have RuboCop automatically fix errors yet. You'll add tha ### Test the code -The following steps will show you how to test that the code works, and view the CI test that you just created. +The following steps will show you how to test that the code works and view the CI test that you just created. 1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. @@ -1130,11 +1113,7 @@ The following steps will show you how to test that the code works, and view the ``` 2. In the repository where you installed your app, create a new pull request. -3. In the pull request you just created, navigate to the **Checks** tab. - -You should see annotations for each of the errors that RuboCop found. Also notice the "Fix this" button that you created by adding a requested action. - -If the annotations are related to a file already included in the PR, the annotations will also show up in the **Files changed** tab. +3. In the pull request you just created, navigate to the **Checks** tab. You should see annotations for each of the errors that RuboCop found. Also notice the "Fix this" button that you created by adding a requested action. ## Step 2.6. Automatically fix RuboCop errors @@ -1169,12 +1148,12 @@ when 'check_run' initiate_check_run when 'rerequested' create_check_run - # ADD requested_action METHOD HERE # + # ADD REQUESTED_ACTION METHOD HERE # end end ``` -After the `rerequested` case, where it says `# ADD requested_action METHOD HERE #`, add the following code: +After the `rerequested` case, where it says `# ADD REQUESTED_ACTION METHOD HERE #`, add the following code: ``` ruby{:copy} when 'requested_action' @@ -1222,11 +1201,11 @@ The code above clones a repository, just like the code you added in [Step 2.2. C The files are changed locally, but you'll still need to push them to {% data variables.product.prodname_dotcom %}. You'll use the `ruby-git` gem to commit all of the files. Git has a single command that stages all modified or deleted files and commits them: `git commit -a`. To do the same thing using `ruby-git`, the code above uses the `commit_all` method. Then the code pushes the committed files to {% data variables.product.prodname_dotcom %} using the installation token, using the same authentication method as the Git `clone` command. Finally, it removes the repository directory to ensure the working directory is prepared for the next event. -That's it! The code you have written now completes your continuous integration server that you built using a {% data variables.product.prodname_github_app %} and checks. To see the full final code for your app, see "[Full code example](#full-code-example)." +The code you have written now completes your continuous integration server that you built using a {% data variables.product.prodname_github_app %} and checks. To see the full final code for your app, see "[Full code example](#full-code-example)." ### Test the code -The following steps will show you how to test that RuboCop can automatically fix the errors it finds. +The following steps will show you how to test that the code works, and that RuboCop can automatically fix the errors it finds. 1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. @@ -1263,15 +1242,14 @@ set :bind, '0.0.0.0' class GHAapp < Sinatra::Application - # Expects that the private key has been set as an environment - # variable in PEM format. Converts the newlines. + # 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. + # 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) set when registering an app. + # The GitHub App's identifier (type integer). APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] # Turn on Sinatra's verbose logging during development @@ -1284,12 +1262,8 @@ class GHAapp < Sinatra::Application get_payload_request(request) verify_webhook_signature - # This tutorial example uses the repository name in the webhook with - # command line utilities. For security reasons, you should validate the - # repository name to ensure that a bad actor isn't attempting to execute - # arbitrary commands or inject false repository names. If a repository name - # is provided in the webhook, validate that it consists only of latin - # alphabetic characters, `-`, and `_`. + # 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 @@ -1325,7 +1299,6 @@ class GHAapp < Sinatra::Application end - 200 # success status end @@ -1422,37 +1395,6 @@ class GHAapp < Sinatra::Application end end - # Handles the check run `requested_action` event - # See /webhooks/event-payloads/#check_run - def take_requested_action - full_repo_name = @payload['repository']['full_name'] - repository = @payload['repository']['name'] - head_branch = @payload['check_run']['check_suite']['head_branch'] - - if (@payload['requested_action']['identifier'] == 'fix_rubocop_notices') - clone_repository(full_repo_name, repository, head_branch) - - # Sets your commit username and email address - @git.config('user.name', ENV['GITHUB_APP_USER_NAME']) - @git.config('user.email', ENV['GITHUB_APP_USER_EMAIL']) - - # Automatically correct RuboCop style errors - @report = `rubocop '#{repository}/*' --format json --auto-correct` - - pwd = Dir.getwd() - Dir.chdir(repository) - begin - @git.commit_all('Automatically fix Octo RuboCop notices.') - @git.push("https://x-access-token:#{@installation_token.to_s}@github.com/#{full_repo_name}.git", head_branch) - rescue - # Nothing to commit! - puts 'Nothing to commit' - end - Dir.chdir(pwd) - `rm -rf '#{repository}'` - end - end - # 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']}" @@ -1476,7 +1418,6 @@ class GHAapp < Sinatra::Application }], accept: 'application/vnd.github+json' ) - end # Clones the repository to the current working directory, updates the @@ -1494,7 +1435,36 @@ class GHAapp < Sinatra::Application 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) @@ -1579,7 +1549,7 @@ end ## Next steps -You should now have an app that receives API events, creates check runs, and uses RuboCop to find Ruby errors, create annotations in a pull request, and automatically fix linter errors. Next you might want to expand your app's code, deploy your app, and make your app public. +You should now have an app that receives API events, creates check runs, uses RuboCop to find Ruby errors, creates annotations in a pull request, and automatically fixes linter errors. Next you might want to expand your app's code, deploy your app, and make your app public. If you have any questions, you can ask for help or advice on GitHub Community, in the "[API and Webhooks discussions](https://github.com/orgs/community/discussions/categories/api-and-webhooks)." From 8a443549f265ed53987aeddfe398ce7df93afb9b Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:06:24 -0600 Subject: [PATCH 19/48] Fix code blocks for commonmark compliance --- .../building-ci-checks-with-a-github-app.md | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 6be9b54d3037..85929a642bc8 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -81,7 +81,7 @@ The following sections will lead you through setting up the following components 1. Create a Ruby file named `server.rb`. This file will contain all the code for your app. You will add content to this file later. 1. If the directory doesn't already include a `.gitignore` file, add a `.gitignore` file. You will add content to this file later. For more information about `.gitignore` files, see "[AUTOTITLE](/get-started/getting-started-with-git/ignoring-files)." 1. Create a file named `Gemfile`. This file will describe the gem dependencies that your Ruby code needs. Add the following contents to your `Gemfile`: - ```ruby{:copy} + ```ruby copy source 'http://rubygems.org' gem 'sinatra', '~> 2.0' @@ -93,7 +93,7 @@ The following sections will lead you through setting up the following components gem 'git' ``` 1. Create a file named `config.ru`. This file will configure your Sinatra server to run. Add the following contents to your `config.ru` file: - ```ruby{:copy} + ```ruby copy require './server' run GHAapp ``` @@ -106,11 +106,11 @@ In order to develop your app locally, you can use a webhook proxy URL to forward 1. Click **Start a new channel**. 1. Copy the full URL under "Webhook Proxy URL." You will use this URL in a following step, and during the app registration steps later in the tutorial. 1. In a terminal, run the following command to install the Smee client: - ```shell{:copy} + ```shell copy npm install --global smee-client ``` 1. In the terminal, run the following command to start the Smee client. Replace `https://smee.io/YOUR_DOMAIN` with the Webhook Proxy URL you copied in the previous step. - ```shell{:copy} + ```shell copy smee --url https://smee.io/YOUR_DOMAIN --path /event_handler --port 3000 ``` You should see output like the following: @@ -160,7 +160,7 @@ Make sure that you are on a secure machine before performing these steps, since 1. Add `.env` to your `.gitignore` file. This will prevent you from accidentally committing your app's credentials. 1. Add the following contents to your `.env` file. {% ifversion ghes or ghae %}Replace `YOUR_HOSTNAME` with the name of {% data variables.location.product_location %}. You will update the other values in a later step.{% else %}You will update the values in a later step.{% endif %} - ```{:copy} + ```shell copy GITHUB_APP_IDENTIFIER="YOUR_APP_ID" GITHUB_WEBHOOK_SECRET="YOUR_WEBHOOK_SECRET" GITHUB_PRIVATE_KEY="YOUR_PRIVATE_KEY" @@ -176,7 +176,7 @@ Make sure that you are on a secure machine before performing these steps, since Here is an example .env file: - ``` + ```shell GITHUB_APP_IDENTIFIER=12345 GITHUB_WEBHOOK_SECRET=your webhook secret GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- @@ -192,7 +192,7 @@ This section will show you how to add some basic template code for your {% data Add the following template code to your `server.rb` file: -```ruby{:copy} +```ruby copy require 'sinatra' # 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 @@ -364,7 +364,7 @@ The class in the template does the following things: First, this class reads the three environment variables you set in "[Store your app's identifying information and credentials](#store-your-apps-identifying-information-and-credentials)," and stores them in variables to use later: -``` ruby +```ruby # Expects the private key in PEM format. Converts the newlines. PRIVATE_KEY = OpenSSL::PKey::RSA.new(ENV['GITHUB_PRIVATE_KEY'].gsub('\n', "\n")) @@ -380,7 +380,7 @@ APP_IDENTIFIER = ENV['GITHUB_APP_IDENTIFIER'] Next is a code block that enables logging during development, which is the default environment in Sinatra. This code turns on logging at the `DEBUG` level to show useful output in the terminal while you are developing the app. -``` ruby +```ruby # Turn on Sinatra's verbose logging during development configure :development do set :logging, Logger::DEBUG @@ -391,7 +391,7 @@ end Sinatra uses [before filters](https://github.com/sinatra/sinatra#filters) that allow you to execute code before the route handler. The `before` block in the template calls four [helper methods](https://github.com/sinatra/sinatra#helpers): `get_payload_request`, `verify_webhook_signature`, `authenticate_app`, and `authenticate_installation`. The template app defines those helper methods in a `helpers do` block later on in the code. For more information, see "[Define the helper methods](#define-the-helper-methods)." -``` ruby +```ruby # Executed before each request to the `/event_handler` route before '/event_handler' do get_payload_request(request) @@ -415,7 +415,7 @@ Under `verify_webhook_signature`, the code that starts with `unless @payload` is An empty route is included in the template code. This code handles all `POST` requests to the `/event_handler` route. You will add more code to this later. -``` ruby +```ruby post '/event_handler' do end @@ -453,7 +453,7 @@ For example, you would authenticate as a {% data variables.product.prodname_gith Before you can use the Octokit.rb library to make API calls, you'll need to initialize an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as a {% data variables.product.prodname_github_app %}, using the `authenticate_app` helper method. -``` ruby +```ruby # 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, @@ -487,7 +487,7 @@ The fourth and final helper method `authenticate_installation` initializes an [O An _installation_ refers to any user or organization account that has installed the app. Even if someone installs the app on more than one repository, it only counts as one installation because it's within the same account. -``` ruby +```ruby # Instantiate an Octokit client authenticated as an installation of a # GitHub App to run API operations. def authenticate_installation(payload) @@ -512,19 +512,19 @@ Your app doesn't do anything yet, but at this point, you can get it running on t Keep Smee running in the current tab in your terminal. Open a new tab and `cd` into the directory where you [cloned the repository you created](#create-a-repository-to-store-code-for-your--data-variablesproductprodname_github_app). The Ruby code in this repository will start up a [Sinatra](https://sinatrarb.com/) web server. This code has a few dependencies. You can install these by running: -```shell{:copy} +```shell copy gem install bundler ``` Followed by: -```shell{:copy} +```shell copy bundle install ``` With the dependencies installed, you can start the server: -```shell{:copy} +```shell copy bundle exec ruby server.rb ``` @@ -585,7 +585,7 @@ Because your app is subscribed to the **Check suite** and **Check run** events, Open the `server.rb` file that you created in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your--data-variablesproductprodname_github_app)," and look for the following code. An empty `post '/event_handler'` route is already included in the template code. The empty route looks like this: -``` ruby +```ruby post '/event_handler' do # ADD EVENT HANDLING HERE # @@ -596,7 +596,7 @@ Open the `server.rb` file that you created in "[Add code for your {% data variab Under `post '/event_handler' do`, where it says `# ADD EVENT HANDLING HERE #`, add the following code. This route will handle the `check_suite` event. -``` ruby{:copy} +```ruby copy # Get the event type from the HTTP_X_GITHUB_EVENT header case request.env['HTTP_X_GITHUB_EVENT'] @@ -620,7 +620,7 @@ You'll add this new method as a [Sinatra helper](https://github.com/sinatra/sina Under `helpers do`, where it says `# ADD CREATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: -``` ruby{:copy} +```ruby copy # Create a new check run with status "queued" def create_check_run @installation_client.create_check_run( @@ -653,7 +653,7 @@ The following steps will show you how to test that the code works, and that it s 1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. - ```shell{:copy} + ```shell copy ruby server.rb ``` @@ -672,7 +672,7 @@ You'll update your event handler to look for the `created` action. While you're Under `post '/event_handler' do`, where it says `# ADD CHECK_RUN METHOD HERE #`, add the following code: -``` ruby{:copy} +```ruby copy when 'check_run' # Check that the event is being sent to this app if @payload['check_run']['app']['id'].to_s === APP_IDENTIFIER @@ -696,7 +696,7 @@ Let's create the `initiate_check_run` method and update the status of the check Under `helpers do`, where it says `# ADD INITIATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: -``` ruby{:copy} +```ruby copy # Start the CI process def initiate_check_run # Once the check run is created, you'll update the status of the check run @@ -738,7 +738,7 @@ The following steps will show you how to test that the code works, and that the 1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. - ```shell{:copy} + ```shell copy ruby server.rb ``` @@ -781,7 +781,7 @@ You can pass specific files or entire directories for RuboCop to check. In this 2. Create a new file named `myfile.rb`. For more information, see "[AUTOTITLE](/repositories/working-with-files/managing-files/creating-new-files)." 3. Add the following content to `myfile.rb`: - ```ruby{:copy} + ```ruby copy # frozen_string_literal: true # The Octocat class tells you about different breeds of Octocat @@ -813,7 +813,7 @@ To run Git operations in your Ruby app, you can use the [ruby-git](https://githu At the top of your `server.rb` file, below the other `require` items, add the following code: -``` ruby{:copy} +```ruby copy require 'git' ``` @@ -839,7 +839,7 @@ After your app clones the repository, it needs to pull the latest code changes a Open your `server.rb` file. Under `helpers do`, where it says `# ADD CLONE_REPOSITORY HELPER METHOD HERE #`, add the following code: -``` ruby{:copy} +```ruby copy # Clones the repository to the current working directory, updates the # contents using Git pull, and checks out the ref. # @@ -864,7 +864,7 @@ Now you've got a method that clones a repository and checks out a ref. Next, you Under `helpers do`, in the `initiate_check_run` helper method where it says `# ***** RUN A CI TEST *****`, add the following code: -``` ruby{:copy} +```ruby copy full_repo_name = @payload['repository']['full_name'] repository = @payload['repository']['name'] head_sha = @payload['check_run']['head_sha'] @@ -882,7 +882,7 @@ So far your code clones the repository and creates check runs using your CI serv Under `clone_repository`, where it says `# ADD CODE HERE TO RUN RUBOCOP #`, add the following code: -``` ruby{:copy} +```ruby copy # Run RuboCop on all files in the repository @report = `rubocop '#{repository}' --format json` logger.debug @report @@ -905,7 +905,7 @@ The following steps will show you how to test that the code works and view the e 1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. - ```shell{:copy} + ```shell copy ruby server.rb ``` @@ -980,7 +980,7 @@ Now you'll add code to extract the required information from RuboCop that's need Under the code you added in the previous step, where it says `# ADD ANNOTATIONS CODE HERE #`, add the following code: -``` ruby{:copy} +```ruby copy 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 @@ -1053,7 +1053,7 @@ For the `summary`, this example uses the summary information from RuboCop and ad Under the code you added in the previous step, where it says `# ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #`, add the following code: -``` ruby{:copy} +``` ruby copy # 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']}" @@ -1061,7 +1061,7 @@ text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}" Now your code should have all the information it needs to update your check run. In [Part 1 of this tutorial](#step-13-update-a-check-run), you added code to set the status of the check run to `success`. You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). Here's the code you added previously to your `server.rb` file: -``` ruby +```ruby # Mark the check run as complete! @installation_client.update_check_run( @payload['repository']['full_name'], @@ -1074,7 +1074,7 @@ Now your code should have all the information it needs to update your check run. Replace that code with the following code: -``` ruby{:copy} +```ruby copy # Mark the check run as complete! And if there are warnings, share them. @installation_client.update_check_run( @payload['repository']['full_name'], @@ -1108,7 +1108,7 @@ The following steps will show you how to test that the code works and view the C 1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. - ```shell{:copy} + ```shell copy ruby server.rb ``` @@ -1128,7 +1128,7 @@ To commit files, Git must know which username and email address to associate wit 1. Open the `.env` file you created earlier in this tutorial. 2. Add the following environment variables to your `.env` file. Replace `APP_NAME` with the name of your app, and `EMAIL_ADDRESS` with any email you'd like to use for this example. - ```{:copy} + ```shell copy GITHUB_APP_USER_NAME="APP_NAME" GITHUB_APP_USER_EMAIL="EMAIL_ADDRESS" ``` @@ -1139,7 +1139,7 @@ When someone clicks the "Fix this" button, your app receives the [check run webh In [Step 1.3. Updating a check run](#step-13-updating-a-check-run) you updated the `event_handler` in your `server.rb` file to look for actions in the `check_run` event. You already have a case statement to handle the `created` and `rerequested` action types: -``` ruby +```ruby when 'check_run' # Check that the event is being sent to this app if @payload['check_run']['app']['id'].to_s === APP_IDENTIFIER @@ -1155,7 +1155,7 @@ end After the `rerequested` case, where it says `# ADD REQUESTED_ACTION METHOD HERE #`, add the following code: -``` ruby{:copy} +```ruby copy when 'requested_action' take_requested_action ``` @@ -1164,7 +1164,7 @@ This code calls a new method that will handle all `requested_action` events for Under `helpers do`, where it says `# ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #`, add the following helper method: -``` ruby{:copy} +```ruby copy # Handles the check run `requested_action` event # See /webhooks/event-payloads/#check_run def take_requested_action @@ -1209,7 +1209,7 @@ The following steps will show you how to test that the code works, and that Rubo 1. Run the following command to restart the server from your terminal. If the server is already running, first enter `Ctrl-C` in your terminal to stop the server, and then run the following command to start the server again. - ```shell{:copy} + ```shell copy ruby server.rb ``` @@ -1222,7 +1222,7 @@ The following steps will show you how to test that the code works, and that Rubo This is what the final code in `server.rb` should look like, after you've followed all of the steps in this tutorial. There are also comments throughout the code that provide additional context. -```ruby{:copy} +```ruby copy require 'sinatra' # 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 From 7acced1367e134eb7c000e25224b3057d4e8a85a Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:01:28 -0600 Subject: [PATCH 20/48] Misc link fixes for style --- .../building-ci-checks-with-a-github-app.md | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 85929a642bc8..e3da8d686adf 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -33,7 +33,7 @@ This tutorial is broken into two parts: CI is a software practice that requires frequently committing code to a shared repository. Committing code more often raises errors sooner and reduces the amount of code a developer needs to debug when finding the source of an error. Frequent code updates also make it easier to merge changes from different members of a software development team. This is great for developers, who can spend more time writing code and less time debugging errors or resolving merge conflicts. -A CI server hosts code that runs CI tests such as code linters (which check style formatting), security checks, code coverage, and other checks against new code commits in a repository. CI servers can even build and deploy code to staging or production servers. For some examples of the types of CI tests you can create with a {% data variables.product.prodname_github_app %}, check out the [continuous integration apps](https://github.com/marketplace/category/continuous-integration) available in {% data variables.product.prodname_marketplace %}. +A CI server hosts code that runs CI tests such as code linters (which check style formatting), security checks, code coverage, and other checks against new code commits in a repository. CI servers can even build and deploy code to staging or production servers. For examples of the types of CI tests you can create with a {% data variables.product.prodname_github_app %}, see the [continuous integration apps](https://github.com/marketplace/category/continuous-integration) that are available in {% data variables.product.prodname_marketplace %}. ### About checks @@ -43,15 +43,15 @@ Checks include check runs, check suites, and commit statuses. - A _check run_ is an individual CI test that runs on a commit. - A _check suite_ is a group of check runs. -- A _commit status_ marks the state of a commit, for example `error`, `failure`, `pending`, or `success`, and is visible in a pull request on {% data variables.product.prodname_dotcom %}. +- A _commit status_ marks the state of a commit, for example `error`, `failure`, `pending`, or `success`, and is visible in a pull request on {% data variables.product.prodname_dotcom %}. Both check suites and check runs contain commit statuses. -Both check suites and check runs contain commit statuses. For more information about checks, see "[AUTOTITLE](/rest/checks)" and "[AUTOTITLE](/rest/guides/using-the-rest-api-to-interact-with-checks)." +{% data variables.product.prodname_dotcom %} automatically creates `check_suite` events for new code commits in a repository using the default flow, although you can change the default settings. For more information, see "[AUTOTITLE](/rest/checks/suites#update-repository-preferences-for-check-suites)." Here's how the default flow works: -Each time new code is pushed to a repository, {% data variables.product.prodname_dotcom %} sends the [`check_suite` webhook event](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) to all {% data variables.product.prodname_github_apps %} installed on the repository. To receive all checks event actions, the app must have the `checks:write` permission. {% data variables.product.prodname_dotcom %} automatically creates `check_suite` events for new code commits in a repository using the default flow, although you can change the default settings. For more information, see "[AUTOTITLE](/rest/checks#update-repository-preferences-for-check-suites)." Here's how the default flow works: +1. When someone pushes code to the repository, {% data variables.product.prodname_dotcom %} automatically sends the `check_suite` event with an action of `requested` to all {% data variables.product.prodname_github_apps %} installed on the repository that have the `checks:write` permission. This event lets the apps know that code was pushed to the repository, and that {% data variables.product.prodname_dotcom %} has automatically created a new check suite. +1. When your app receives this event, it can add check runs to that suite. +1. Your check runs can include annotations that are displayed on specific lines of code. Annotations are visible in the **Checks** tab. When you create an annotation for a file that is part of the pull request, the annotations are also shown in the **Files changed** tab. For more information, see the `annotations` object in the "[AUTOTITLE](/rest/checks/runs#create-a-check-run)" documentation. -1. Whenever someone pushes code to the repository, {% data variables.product.prodname_dotcom %} sends the `check_suite` event with an action of `requested` to all {% data variables.product.prodname_github_apps %} installed on the repository that have the `checks:write` permission. This event lets the apps know that code was pushed and that {% data variables.product.prodname_dotcom %} has automatically created a new check suite. -1. When your app receives this event, it can [add check runs](/rest/checks#create-a-check-run) to that suite. -1. Your check runs can include [annotations](/rest/checks#annotations-object) that are displayed on specific lines of code. Annotations are visible in the **Checks** tab. When you create an annotation for a file that is part of the pull request, the annotations are also shown in the **Files changed** tab. +For more information about checks, see "[AUTOTITLE](/rest/checks)" and "[AUTOTITLE](/rest/guides/using-the-rest-api-to-interact-with-checks)." ## Prerequisites @@ -140,10 +140,10 @@ The following steps will guide you through configuring a {% data variables.produ 1. Click **New GitHub App**. 1. Under "GitHub App name," enter a name for your app. For example, `USERNAME-ci-test-app` where `USERNAME` is your {% data variables.product.company_short %} username. 1. Under "Homepage URL," enter a URL for your app. For example, you can use the URL of the repository that you created to store the code for your app. -1. Skip the "Identifying and authorizing users" and "Post installation" sections for this tutorial. For more information about these settings, see "[AUTOTITLE](/apps/creating-github-apps/creating-github-apps/creating-a-github-app)." +1. Skip the "Identifying and authorizing users" and "Post installation" sections for this tutorial. 1. Make sure that **Active** is selected under "Webhooks." 1. Under "Webhook URL," enter your webhook proxy URL from earlier. For more information, see "[Get a webhook proxy URL](#get-a-webhook-proxy-url)." -1. Under "Webhook secret," enter a random string. This secret is used to verify that webhooks are sent by {% data variables.product.prodname_dotcom %}. You will use this string later. +1. Under "Webhook secret," enter a random string. This secret is used to verify that webhooks are sent by {% data variables.product.prodname_dotcom %}. Save this string; You will use it later. 1. Under "Repository permissions," next to "Checks," select **Read & write**. 1. Under "Subscribe to events," select **Check suite** and **Check run**. 1. Under "Where can this GitHub App be installed?," select **Only on this account**. You can change this later if you want to publish your app. @@ -351,18 +351,17 @@ Open up the `server.rb` file in a text editor. You'll see comments throughout th At the top of the file you'll see `set :port 3000`, which sets the port used when starting the web server to match the port you redirected your webhook payloads to in "[Get a Webhook Proxy URL](#get-a-webhook-proxy-url)." -The next code you'll see is the `class GHApp < Sinatra::Application` declaration. You'll write all of the code for your {% data variables.product.prodname_github_app %} inside this class. +The next code you'll see is the `class GHApp < Sinatra::Application` declaration. You'll write all of the code for your {% data variables.product.prodname_github_app %} inside this class. The following sections explain in detail what the code does inside this class. -The class in the template does the following things: -* [Read the environment variables](#read-the-environment-variables) -* [Turn on logging](#turn-on-logging) -* [Define a before filter](#define-a-before-filter) -* [Define the route handler](#define-a-route-handler) -* [Define the helper methods](#define-the-helper-methods) +- [Read the environment variables](#read-the-environment-variables) +- [Turn on logging](#turn-on-logging) +- [Define a before filter](#define-a-before-filter) +- [Define the route handler](#define-a-route-handler) +- [Define the helper methods](#define-the-helper-methods) #### Read the environment variables -First, this class reads the three environment variables you set in "[Store your app's identifying information and credentials](#store-your-apps-identifying-information-and-credentials)," and stores them in variables to use later: +First, this class reads the three environment variables you set in "[Store your app's identifying information and credentials](#store-your-apps-identifying-information-and-credentials)," and stores them in variables to use later. ```ruby # Expects the private key in PEM format. Converts the newlines. @@ -389,7 +388,7 @@ end #### Define a before filter -Sinatra uses [before filters](https://github.com/sinatra/sinatra#filters) that allow you to execute code before the route handler. The `before` block in the template calls four [helper methods](https://github.com/sinatra/sinatra#helpers): `get_payload_request`, `verify_webhook_signature`, `authenticate_app`, and `authenticate_installation`. The template app defines those helper methods in a `helpers do` block later on in the code. For more information, see "[Define the helper methods](#define-the-helper-methods)." +Sinatra uses before filters that allow you to execute code before the route handler. The `before` block in the template calls four helper methods: `get_payload_request`, `verify_webhook_signature`, `authenticate_app`, and `authenticate_installation`. For more information, see "[Filters](https://github.com/sinatra/sinatra#filters)" and "[Helpers](https://github.com/sinatra/sinatra#helpers)" in the Sinatra documentation. ```ruby # Executed before each request to the `/event_handler` route @@ -409,7 +408,9 @@ Sinatra uses [before filters](https://github.com/sinatra/sinatra#filters) that a end ``` -Under `verify_webhook_signature`, the code that starts with `unless @payload` is an additional security measure. If a repository name is provided with a webhook payload, this code validates that the repository name contains only Latin alphabetic characters, hyphens, and underscores. This helps ensure that a bad actor isn't attempting to execute arbitrary commands or inject false repository names. As an additional security measure, the `verify_webhook_signature` helper method also validates incoming webhook payloads. For more information, see "[Define the helper methods](#verifying-the-webhook-signature)." +Each of these helper methods are defined later in the code, in the `helpers do` code block. For more information, see "[Define the helper methods](#define-the-helper-methods)." + +Under `verify_webhook_signature`, the code that starts with `unless @payload` is a security measure. If a repository name is provided with a webhook payload, this code validates that the repository name contains only Latin alphabetic characters, hyphens, and underscores. This helps ensure that a bad actor isn't attempting to execute arbitrary commands or inject false repository names. Later in the code, under `helpers do`, the `verify_webhook_signature` helper method also validates incoming webhook payloads as an additional security measure. #### Define a route handler @@ -437,12 +438,14 @@ The second helper method `verify_webhook_signature` performs verification of the The third helper method `authenticate_app` allows your {% data variables.product.prodname_github_app %} to authenticate, so it can request an installation token. -To make API calls, you'll be using the [Octokit library](https://octokit.github.io/octokit.rb/). Doing anything interesting with this library will require your {% data variables.product.prodname_github_app %} to authenticate. {% data variables.product.prodname_github_apps %} have two methods of authentication: +To make API calls, you'll be using the Octokit library. Doing anything interesting with this library will require your {% data variables.product.prodname_github_app %} to authenticate. For more information about the Octokit library, see the [Octokit documentation](https://octokit.github.io/octokit.rb/). + +{% data variables.product.prodname_github_apps %} have two methods of authentication: - Authenticating as a {% data variables.product.prodname_github_app %} using a [JSON Web Token (JWT)](https://jwt.io/introduction). - Authenticating as a specific installation of a {% data variables.product.prodname_github_app %} using an installation access token. -You'll learn about authenticating as an installation in the [next section](#authenticating-as-an-installation). +You'll learn about authenticating as an installation in the next section, "[Authenticating as an installation](#authenticating-as-an-installation)." Authenticating as a {% data variables.product.prodname_github_app %} lets you do a couple of things: @@ -497,7 +500,9 @@ def authenticate_installation(payload) end ``` -The [`create_app_installation_access_token`](https://octokit.github.io/octokit.rb/Octokit/Client/Apps.html#create_app_installation_access_token-instance_method) Octokit method creates an installation token. This method accepts two arguments: +The `create_app_installation_access_token` Octokit method creates an installation token. For more information, see "[create_installation_access_token](https://octokit.github.io/octokit.rb/Octokit/Client/Apps.html#create_app_installation_access_token-instance_method)" in the Octokit documentation. + +This method accepts two arguments: * Installation (integer): The ID of a {% data variables.product.prodname_github_app %} installation * Options (hash, defaults to `{}`): A customizable set of options From b20f1ac586023ed4bbb89cb2e18e78a2fa9941a1 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Fri, 23 Jun 2023 14:41:01 -0600 Subject: [PATCH 21/48] Fix links for style --- .../building-ci-checks-with-a-github-app.md | 103 ++++++++++-------- 1 file changed, 57 insertions(+), 46 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index e3da8d686adf..dbd28f6505d6 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -18,6 +18,10 @@ topics: --- ## Introduction +```bash +git init YOUR_REPO +``` + This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to Checks webhook events using {% data variables.product.prodname_dotcom %}'s REST API. In this tutorial, you will use your computer or codespace as a server while you develop your app. Once the app is ready for production use, you should deploy your app to a dedicated server. @@ -515,41 +519,44 @@ With this method in place, each time your app receives a new webhook payload, it Your app doesn't do anything yet, but at this point, you can get it running on the server. -Keep Smee running in the current tab in your terminal. Open a new tab and `cd` into the directory where you [cloned the repository you created](#create-a-repository-to-store-code-for-your--data-variablesproductprodname_github_app). The Ruby code in this repository will start up a [Sinatra](https://sinatrarb.com/) web server. This code has a few dependencies. You can install these by running: +1. In your terminal, make sure that Smee is still running. For more information, see "[Get a webhook proxy URL](#get-a-webhook-proxy-url)." +1. Open a new tab in your terminal, and `cd` into the directory where you cloned the repository that you created earlier in the tutorial. For more information, see "[Create a repository to store code for your GitHub App](#create-a-repository-to-store-code-for-your-github-app)." The Ruby code in this repository will start up a [Sinatra](https://sinatrarb.com/) web server. -```shell copy -gem install bundler -``` +1. Install the dependencies by running the following two commands one after the other: -Followed by: + ```shell copy + gem install bundler + ``` -```shell copy -bundle install -``` + ```shell copy + bundle install + ``` -With the dependencies installed, you can start the server: +1. After installing the dependencies, start the server by running this command: -```shell copy -bundle exec ruby server.rb -``` + ```shell copy + bundle exec ruby server.rb + ``` -You should see a response like: + You should see a response like this: -```shell -> == 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 -``` + ```shell + > == 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 + ``` + + If you see an error, make sure you've created the `.env` file in the directory that contains `server.rb`. -If you see an error, make sure you've created the `.env` file in the directory that contains `server.rb`. +1. To test the server, navigate in your browser to `http://localhost:3000`. -Once the server is running, test it by going to `http://localhost:3000` in your browser. If you see an error page that says "Sinatra doesn't know this ditty," the app is working as expected. Even though it's an error page, it's a Sinatra error page, which means your app is connected to the server as expected. You're seeing this message because you haven't given the app anything else to show. + If you see an error page that says "Sinatra doesn't know this ditty," the app is working as expected. Even though it's an error page, it's a Sinatra error page, which means your app is connected to the server as expected. You're seeing this message because you haven't given the app anything else to show. ## Test that the server is listening to your app @@ -566,15 +573,15 @@ You can test that the server is listening to your app by triggering an event for If you see output like this, it means your app received a notification that it was installed on your {% data variables.product.prodname_dotcom %} account. The app is running on the server as expected. -If you don't see this output, make sure Smee is running correctly in another terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in terminal. + If you don't see this output, make sure Smee is running correctly in another terminal tab. If you need to restart Smee, note that you'll also need to _uninstall_ and _reinstall_ the app to send the `installation` event to your app again and see the output in terminal. If you're wondering where the terminal output above is coming from, it's written in the app template code you added to `server.rb` in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your-github-app)." ## Part 1. Creating the Checks API interface -In this part, you will add the code necessary to receive `check_suite` webhook events and create and update check runs. You'll also learn how to create check runs when a check was re-requested on {% data variables.product.prodname_dotcom %}. At the end of this section, you'll be able to view the check run you created in a {% data variables.product.prodname_dotcom %} pull request. +In this part, you will add the code necessary to receive `check_suite` webhook events, and create and update check runs. You'll also learn how to create check runs when a check was re-requested on {% data variables.product.prodname_dotcom %}. At the end of this section, you'll be able to view the check run you created in a {% data variables.product.prodname_dotcom %} pull request. -Your check run will not perform any checks on the code in this section. You'll add that functionality in [Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test). +Your check run will not perform any checks on the code in this section. You'll add that functionality in "[Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test)." You should already have a Smee channel configured that is forwarding webhook payloads to your local server. Your server should be running and connected to the {% data variables.product.prodname_github_app %} you registered and installed on a test repository. @@ -588,7 +595,7 @@ These are the steps you'll complete in Part 1: Because your app is subscribed to the **Check suite** and **Check run** events, it will receive the [`check_suite`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) and [`check_run`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) webhooks. {% data variables.product.prodname_dotcom %} sends webhook payloads as `POST` requests. Because you forwarded your Smee webhook payloads to `http://localhost:3000/event_handler`, your server will receive the `POST` request payloads at the `post '/event_handler'` route. -Open the `server.rb` file that you created in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your--data-variablesproductprodname_github_app)," and look for the following code. An empty `post '/event_handler'` route is already included in the template code. The empty route looks like this: +Open the `server.rb` file that you created in "[Add code for your {% data variables.product.prodname_github_app %}](#add-code-for-your-github-app)," and look for the following code. An empty `post '/event_handler'` route is already included in the template code. The empty route looks like this: ```ruby post '/event_handler' do @@ -644,13 +651,13 @@ end This code calls the "[AUTOTITLE](/rest/checks#create-a-check-run)" endpoint using the Octokit [create_check_run method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#create_check_run-instance_method). -To create a check run, only two input parameters are required: `name` and `head_sha`. We will use [RuboCop](https://rubocop.readthedocs.io/en/latest/) to implement the CI test later in this tutorial, which is why the name "Octo RuboCop" is used here, but you can choose any name you'd like for the check run. +To create a check run, only two input parameters are required: `name` and `head_sha`. In this code, we name the check run "Octo RuboCop," because we'll use RuboCop to implement the CI test later in the tutorial. But you can choose any name you'd like for the check run. For more information about RuboCop, see the [RuboCop documentation](https://rubocop.readthedocs.io/en/latest/). You're only supplying the required parameters now to get the basic functionality working, but you'll update the check run later as you collect more information about the check run. By default, {% data variables.product.prodname_dotcom %} sets the `status` to `queued`. {% data variables.product.prodname_dotcom %} creates a check run for a specific commit SHA, which is why `head_sha` is a required parameter. You can find the commit SHA in the webhook payload. Although you're only creating a check run for the `check_suite` event right now, it's good to know that the `head_sha` is included in both the `check_suite` and `check_run` objects in the event payloads. -In the code above, you're using the [ternary operator](https://ruby-doc.org/core-2.3.0/doc/syntax/control_expressions_rdoc.html#label-Ternary+if), which works like an `if/else` statement, to check if the payload contains a `check_run` object. If it does, you read the `head_sha` from the `check_run` object, otherwise you read it from the `check_suite` object. +The code above uses a [ternary operator](https://ruby-doc.org/core-2.3.0/doc/syntax/control_expressions_rdoc.html#label-Ternary+if), which works like an `if/else` statement, to check if the payload contains a `check_run` object. If it does, you read the `head_sha` from the `check_run` object, otherwise you read it from the `check_suite` object. ### Test the code @@ -729,11 +736,11 @@ def initiate_check_run end ``` -The code above calls the "[AUTOTITLE](/rest/checks#update-a-check-run)" API endpoint using the [`update_check_run` Octokit method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#update_check_run-instance_method) to update the check run that you already created. +The code above calls the "[Update a check run](/rest/checks#update-a-check-run)" endpoint using the [`update_check_run` Octokit method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#update_check_run-instance_method), and updates the check run that you already created. Here's what this code is doing. First, it updates the check run's status to `in_progress` and implicitly sets the `started_at` time to the current time. In Part 2 of this tutorial, you'll add code that kicks off a real CI test under `***** RUN A CI TEST *****`. For now, you'll leave that section as a placeholder, so the code that follows it will just simulate that the CI process succeeds and all tests pass. Finally, the code updates the status of the check run again to `completed`. -You'll notice in the "[AUTOTITLE](/rest/checks#update-a-check-run)" documentation that when you provide a status of `completed`, the `conclusion` and `completed_at` parameters are required. The `conclusion` summarizes the outcome of a check run and can be `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `skipped`, or `action_required`. You'll set the conclusion to `success`, the `completed_at` time to the current time, and the status to `completed`. +When you use the REST API to provide a check run status of `completed`, the `conclusion` and `completed_at` parameters are required. The `conclusion` summarizes the outcome of a check run and can be `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `skipped`, or `action_required`. You'll set the conclusion to `success`, the `completed_at` time to the current time, and the status to `completed`. You could also provide more details about what your check is doing, but you'll get to that in the next section. @@ -753,17 +760,19 @@ The following steps will show you how to test that the code works, and that the ## Part 2. Creating the Octo RuboCop CI test -[RuboCop](https://rubocop.readthedocs.io/en/latest/) is a Ruby code linter and formatter. It checks Ruby code to ensure that it complies with the "[Ruby Style Guide](https://github.com/rubocop-hq/ruby-style-guide)." RuboCop has three primary functions: +RuboCop is a Ruby code linter and formatter. It checks Ruby code to ensure that it complies with the Ruby Style Guide. For more information, see the [RuboCop documentation](https://rubocop.readthedocs.io/en/latest/). + +RuboCop has three primary functions: * Linting to check code style * Code formatting * Replaces the native Ruby linting capabilities using `ruby -w` -Now that you've got the interface created to receive Checks API events and create check runs, you can create a check run that implements a CI test. +Now that you've got the interface created to receive API events and create check runs, you can create a check run that implements a CI test. Your app will run RuboCop on the CI server, and create check runs (CI tests in this case) that report the results that RuboCop reports to {% data variables.product.prodname_dotcom %}. -The Checks API allows you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. +The checks API endpoints allow you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. For example, you could show that information as a comment, error, or warning on a specific line of code. This tutorial uses annotations to visualize RuboCop errors. @@ -814,9 +823,9 @@ RuboCop is available as a command-line utility. That means your {% data variable ### Allow Git operations -To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. The `Gemfile` in the `building-a-checks-api-ci-server` repository already includes the ruby-git gem, and you installed it when you ran `bundle install` in the [prerequisite steps](#prerequisites). +To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. The `Gemfile` you created in "[Setup](#setup)" already includes the ruby-git gem, and you installed it when you ran `bundle install` in "[Start the server](#start-the-server)." -At the top of your `server.rb` file, below the other `require` items, add the following code: +Now, at the top of your `server.rb` file, below the other `require` items, add the following code: ```ruby copy require 'git' @@ -883,7 +892,9 @@ The code above gets the full repository name and the head SHA of the commit from ## Step 2.3. Run RuboCop -So far your code clones the repository and creates check runs using your CI server. Now you'll get into the details of the [RuboCop linter](https://docs.rubocop.org/rubocop/usage/basic_usage.html#code-style-checker) and [checks annotations](/rest/checks#create-a-check-run). First, add code to run RuboCop and save the style code errors in JSON format. +So far, your code clones the repository and creates check runs using your CI server. Now you'll get into the details of the [RuboCop linter](https://docs.rubocop.org/rubocop/usage/basic_usage.html#code-style-checker) and [checks annotations](/rest/checks#create-a-check-run). + +First, you'll add code to run RuboCop and save the style code errors in JSON format. Under `clone_repository`, where it says `# ADD CODE HERE TO RUN RUBOCOP #`, add the following code: @@ -1064,7 +1075,7 @@ summary = "Octo RuboCop summary\n-Offense count: #{@output['summary']['offense_c text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}" ``` -Now your code should have all the information it needs to update your check run. In [Part 1 of this tutorial](#step-13-update-a-check-run), you added code to set the status of the check run to `success`. You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). Here's the code you added previously to your `server.rb` file: +Now your code should have all the information it needs to update your check run. In "[Step 1.3. Update a check run](#step-13-update-a-check-run)," you added code to set the status of the check run to `success`. You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). Here's the code you added previously to your `server.rb` file: ```ruby # Mark the check run as complete! @@ -1103,7 +1114,7 @@ Replace that code with the following code: Now that your code sets a conclusion based on the status of the CI test, and adds the output from the RuboCop results, you've created a CI test. -The code above also adds a feature to your CI server called [requested actions](https://developer.github.com/changes/2018-05-23-request-actions-on-checks/) via the `actions` object. {% ifversion fpt or ghec %}(Note this is not related to [GitHub Actions](/actions).) {% endif %}Requested actions add a button in the **Checks** tab on {% data variables.product.prodname_dotcom %} that allows someone to request the check run to take additional action. The additional action is completely configurable by your app. For example, because RuboCop has a feature to automatically fix the errors it finds in Ruby code, your CI server can use a requested actions button to allow people to request automatic error fixes. When someone clicks the button, the app receives the `check_run` event with a `requested_action` action. Each requested action has an `identifier` that the app uses to determine which button was clicked. +The code above also adds a feature called requested actions to your CI server, via the `actions` object. {% ifversion fpt or ghec %}(Note this is not related to [GitHub Actions](/actions).) {% endif %}For more information, see "[Request further actions from a check run](https://developer.github.com/changes/2018-05-23-request-actions-on-checks/)." Requested actions add a button in the **Checks** tab on {% data variables.product.prodname_dotcom %} that allows someone to request the check run to take additional action. The additional action is completely configurable by your app. For example, because RuboCop has a feature to automatically fix the errors it finds in Ruby code, your CI server can use a requested actions button to allow people to request automatic error fixes. When someone clicks the button, the app receives the `check_run` event with a `requested_action` action. Each requested action has an `identifier` that the app uses to determine which button was clicked. The code above doesn't have RuboCop automatically fix errors yet. You'll add that later in the tutorial. @@ -1122,11 +1133,11 @@ The following steps will show you how to test that the code works and view the C ## Step 2.6. Automatically fix RuboCop errors -So far you've created a CI test. In this section, you'll add one more feature that uses RuboCop to automatically fix the errors it finds. You already added the "Fix this" button in the [previous section](#step-25-update-the-check-run-with-ci-test-results). Now you'll add the code to handle the `requested_action` check run event that's triggered when someone clicks the "Fix this" button. +So far you've created a CI test. In this section, you'll add one more feature that uses RuboCop to automatically fix the errors it finds. You already added the "Fix this" button in "[Step 2.5. Update the check run with CI test results](#step-25-update-the-check-run-with-ci-test-results)." Now you'll add the code to handle the `requested_action` check run event that's triggered when someone clicks the "Fix this" button. The RuboCop tool offers the `--auto-correct` command-line option to automatically fix the errors it finds. For more information, see "[Autocorrecting offenses](https://docs.rubocop.org/rubocop/usage/basic_usage.html#autocorrecting-offenses)" in the RuboCop documentation. When you use the `--auto-correct` feature, the updates are applied to the local files on the server. You'll need to push the changes to {% data variables.product.prodname_dotcom %} after RuboCop makes the fixes. -To push to a repository, your app must have write permissions for "Contents" in a repository. You already set that permission to **Read & write** back in [Step 2.2. Cloning the repository](#step-22-clone-the-repository). +To push to a repository, your app must have write permissions for "Contents" in a repository. You already set that permission to **Read & write** back in "[Step 2.2. Cloning the repository](#step-22-clone-the-repository)." To commit files, Git must know which username and email address to associate with the commit. Next you'll add environment variables to store the name and email address that your app will use when it makes Git commits. @@ -1142,7 +1153,7 @@ Next you'll need to add code to read the environment variables and set the Git c When someone clicks the "Fix this" button, your app receives the [check run webhook](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) with the `requested_action` action type. -In [Step 1.3. Updating a check run](#step-13-updating-a-check-run) you updated the `event_handler` in your `server.rb` file to look for actions in the `check_run` event. You already have a case statement to handle the `created` and `rerequested` action types: +In "[Step 1.3. Updating a check run](#step-13-updating-a-check-run)" you updated the `event_handler` in your `server.rb` file to look for actions in the `check_run` event. You already have a case statement to handle the `created` and `rerequested` action types: ```ruby when 'check_run' @@ -1202,7 +1213,7 @@ def take_requested_action end ``` -The code above clones a repository, just like the code you added in [Step 2.2. Clone the repository](#step-22-clone-the-repository). An `if` statement checks that the requested action's identifier matches the RuboCop button identifier (`fix_rubocop_notices`). When they match, the code clones the repository, sets the Git username and email, and runs RuboCop with the option `--auto-correct`. The `--auto-correct` option applies the changes to the local CI server files automatically. +The code above clones a repository, just like the code you added in "[Step 2.2. Clone the repository](#step-22-clone-the-repository)." An `if` statement checks that the requested action's identifier matches the RuboCop button identifier (`fix_rubocop_notices`). When they match, the code clones the repository, sets the Git username and email, and runs RuboCop with the option `--auto-correct`. The `--auto-correct` option applies the changes to the local CI server files automatically. The files are changed locally, but you'll still need to push them to {% data variables.product.prodname_dotcom %}. You'll use the `ruby-git` gem to commit all of the files. Git has a single command that stages all modified or deleted files and commits them: `git commit -a`. To do the same thing using `ruby-git`, the code above uses the `commit_all` method. Then the code pushes the committed files to {% data variables.product.prodname_dotcom %} using the installation token, using the same authentication method as the Git `clone` command. Finally, it removes the repository directory to ensure the working directory is prepared for the next event. From 841b5727d782694935f714a6f453109b6dd15ebc Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Fri, 23 Jun 2023 14:59:06 -0600 Subject: [PATCH 22/48] Misc fix --- .../building-ci-checks-with-a-github-app.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index dbd28f6505d6..02ca4b27bb7a 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -18,10 +18,6 @@ topics: --- ## Introduction -```bash -git init YOUR_REPO -``` - This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to Checks webhook events using {% data variables.product.prodname_dotcom %}'s REST API. In this tutorial, you will use your computer or codespace as a server while you develop your app. Once the app is ready for production use, you should deploy your app to a dedicated server. From a3f462e22dc38c4362c146ee359f4d2ad29e5104 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Fri, 23 Jun 2023 17:13:31 -0600 Subject: [PATCH 23/48] Update link --- content/rest/checks/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/rest/checks/index.md b/content/rest/checks/index.md index c3ae2762ad07..db60022cb6dd 100644 --- a/content/rest/checks/index.md +++ b/content/rest/checks/index.md @@ -17,6 +17,6 @@ children: autogenerated: rest --- -You can create apps that perform continuous integration, code linting, or code scanning services and provide detailed feedback on commits. For more information, see "[AUTOTITLE](/rest/guides/using-the-rest-api-to-interact-with-checks)" and "[AUTOTITLE](/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api)." +You can create apps that perform continuous integration, code linting, or code scanning services and provide detailed feedback on commits. For more information, see "[AUTOTITLE](/rest/guides/using-the-rest-api-to-interact-with-checks)" and "[AUTOTITLE](/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app)." From ee2127bb794b89ab744f84bd44b38d73ca030567 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:50:31 -0600 Subject: [PATCH 24/48] Apply suggestions from code review Co-authored-by: Sarah Edwards --- .../building-ci-checks-with-a-github-app.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 02ca4b27bb7a..b78562e2a715 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -18,7 +18,7 @@ topics: --- ## Introduction -This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to Checks webhook events using {% data variables.product.prodname_dotcom %}'s REST API. +This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to `check_run` and `check_suite` webhook events using {% data variables.product.prodname_dotcom %}'s REST API. In this tutorial, you will use your computer or codespace as a server while you develop your app. Once the app is ready for production use, you should deploy your app to a dedicated server. @@ -63,7 +63,7 @@ Before you get started, you may want to familiarize yourself with the following - [Webhooks](/webhooks-and-events/webhooks/about-webhooks) - [REST API checks endpoints](/rest/checks) -The Checks endpoints are also available to use in GraphQL, but this tutorial focuses on REST. For more information about the GraphQL objects, see [Check Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. +Checks are also available to use with the GraphQL API, but this tutorial focuses on the REST API. For more information about the GraphQL objects, see [Check Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. ## Setup From 8cfb6c7a69c362a0ecc07eef24e9cc5addf3e5f6 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:51:10 -0600 Subject: [PATCH 25/48] Delete old article --- .../creating-ci-tests-with-the-checks-api.md | 736 ------------------ 1 file changed, 736 deletions(-) delete mode 100644 content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md deleted file mode 100644 index c22a7177a239..000000000000 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/creating-ci-tests-with-the-checks-api.md +++ /dev/null @@ -1,736 +0,0 @@ ---- -title: Creating CI tests with the Checks API -intro: 'Build a continuous integration server to run tests using a {% data variables.product.prodname_github_app %} and the Checks API.' -redirect_from: - - /apps/quickstart-guides/creating-ci-tests-with-the-checks-api - - /developers/apps/creating-ci-tests-with-the-checks-api - - /developers/apps/guides/creating-ci-tests-with-the-checks-api - - /apps/creating-github-apps/guides/creating-ci-tests-with-the-checks-api -versions: - fpt: '*' - ghes: '*' - ghae: '*' - ghec: '*' -topics: - - GitHub Apps -shortTitle: CI tests using Checks API ---- -## Introduction - -This guide will introduce you to [GitHub Apps](/apps) and the [Checks API](/rest/checks), which you'll use to build a continuous integration (CI) server that runs tests. - -CI is a software practice that requires frequently committing code to a shared repository. Committing code more often raises errors sooner and reduces the amount of code a developer needs to debug when finding the source of an error. Frequent code updates also make it easier to merge changes from different members of a software development team. This is great for developers, who can spend more time writing code and less time debugging errors or resolving merge conflicts. 🙌 - -A CI server hosts code that runs CI tests such as code linters (which check style formatting), security checks, code coverage, and other checks against new code commits in a repository. CI servers can even build and deploy code to staging or production servers. For some examples of the types of CI tests you can create with a GitHub App, check out the [continuous integration apps](https://github.com/marketplace/category/continuous-integration) available in GitHub Marketplace. - -{% data reusables.apps.app-ruby-guides %} - -### Checks API overview - -The [Checks API](/rest/checks) allows you to set up CI tests that are automatically run against each code commit in a repository. The Checks API reports detailed information about each check on GitHub in the pull request's **Checks** tab. With the Checks API, you can create annotations with additional details for specific lines of code. Annotations are visible in the **Checks** tab. When you create an annotation for a file that is part of the pull request, the annotations are also shown in the **Files changed** tab. - -A _check suite_ is a group of _check runs_ (individual CI tests). Both the suite and the runs contain _statuses_ that are visible in a pull request on GitHub. You can use statuses to determine when a code commit introduces errors. Using these statuses with [protected branches](/rest/repos#branches) can prevent people from merging pull requests prematurely. See "[AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging)" for more details. - -The Checks API sends the [`check_suite` webhook event](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) to all GitHub Apps installed on a repository each time new code is pushed to the repository. To receive all Checks API event actions, the app must have the `checks:write` permission. GitHub automatically creates `check_suite` events for new code commits in a repository using the default flow, although [Update repository preferences for check suites](/rest/checks#update-repository-preferences-for-check-suites) if you'd like. Here's how the default flow works: - -1. Whenever someone pushes code to the repository, GitHub sends the `check_suite` event with an action of `requested` to all GitHub Apps installed on the repository that have the `checks:write` permission. This event lets the apps know that code was pushed and that GitHub has automatically created a new check suite. -1. When your app receives this event, it can [add check runs](/rest/checks#create-a-check-run) to that suite. -1. Your check runs can include [annotations](/rest/checks#annotations-object) that are displayed on specific lines of code. - -**In this guide, you’ll learn how to:** - -- Part 1: Set up the framework for a CI server using the Checks API. - - Configure a GitHub App as a server that receives Checks API events. - - Create new check runs for CI tests when a repository receives newly pushed commits. - - Re-run check runs when a user requests that action on GitHub. -- Part 2: Build on the CI server framework you created by adding a linter CI test. - - Update a check run with a `status`, `conclusion`, and `output` details. - - Create annotations on lines of code that GitHub displays in the **Checks** and **Files Changed** tab of a pull request. - - Automatically fix linter recommendations by exposing a "Fix this" button in the **Checks** tab of the pull request. - -## Prerequisites - -Before you get started, you may want to familiarize yourself with [GitHub Apps](/apps), [Webhooks](/webhooks-and-events/webhooks/about-webhooks), and the [Checks API](/rest/checks), if you're not already. You'll find more APIs in the [REST API docs](/rest). The Checks API is also available to use in [GraphQL](/graphql), but this quickstart focuses on REST. See the GraphQL [Checks Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) objects for more details. - -You'll use the [Ruby programming language](https://www.ruby-lang.org/en/), the [Smee](https://smee.io/) webhook payload delivery service, the [Octokit.rb Ruby library](https://octokit.github.io/octokit.rb/) for the GitHub REST API, and the [Sinatra web framework](https://sinatrarb.com/) to create your Checks API CI server app. - -You don't need to be an expert in any of these tools or concepts to complete this project. This guide will walk you through all the required steps. Before you begin creating CI tests with the Checks API, you'll need to do the following: - -1. Clone the [Creating CI tests with the Checks API](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) repository. - ```shell - $ git clone https://github.com/github-developer/creating-ci-tests-with-the-checks-api.git - ``` - - Inside the directory, you'll find a `template_server.rb` file with the template code you'll use in this quickstart and a `server.rb` file with the completed project code. - -1. Follow the steps in the "[AUTOTITLE](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app)" quickstart to configure and run the app server. **Note:** Instead of [cloning the GitHub App template repository](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app#prerequisites), use the `template_server.rb` file in the repository you cloned in the previous step in this quickstart. - - If you've completed a GitHub App quickstart before, make sure to register a _new_ GitHub App and start a new Smee channel to use with this quickstart. - - See the [troubleshooting](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app#troubleshooting) section if you are running into problems setting up your template GitHub App. - -## Part 1. Creating the Checks API interface - -In this part, you will add the code necessary to receive `check_suite` webhook events and create and update check runs. You'll also learn how to create check runs when a check was re-requested on GitHub. At the end of this section, you'll be able to view the check run you created in a GitHub pull request. - -Your check run will not be performing any checks on the code in this section. You'll add that functionality in [Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test). - -You should already have a Smee channel configured that is forwarding webhook payloads to your local server. Your server should be running and connected to the GitHub App you registered and installed on a test repository. If you haven't completed the steps in "[AUTOTITLE](/apps/creating-github-apps/guides/setting-up-your-development-environment-to-create-a-github-app)," you'll need to do that before you can continue. - -Let's get started! These are the steps you'll complete in Part 1: - -1. [Updating app permissions](#step-11-updating-app-permissions) -1. [Adding event handling](#step-12-adding-event-handling) -1. [Creating a check run](#step-13-creating-a-check-run) -1. [Updating a check run](#step-14-updating-a-check-run) - -## Step 1.1. Updating app permissions - -When you [first registered your app](#prerequisites), you accepted the default permissions, which means your app doesn't have access to most resources. For this example, your app will need permission to read and write checks. - -To update your app's permissions: - -1. Select your app from the [app settings page](https://github.com/settings/apps) and click **Permissions & Webhooks** in the sidebar. -1. In the "Permissions" section, find "Checks", and select **Read & write** in the Access dropdown next to it. -1. In the "Subscribe to events" section, select **Check suite** and **Check run** to subscribe to these events. -{% data reusables.apps.accept_new_permissions_steps %} - -Great! Your app has permission to do the tasks you want it to do. Now you can add the code to handle the events. - -## Step 1.2. Adding event handling - -Now that your app is subscribed to the **Check suite** and **Check run** events, it will start receiving the [`check_suite`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_suite) and [`check_run`](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) webhooks. GitHub sends webhook payloads as `POST` requests. Because you forwarded your Smee webhook payloads to `http://localhost:3000/event_handler`, your server will receive the `POST` request payloads at the `post '/event_handler'` route. - -An empty `post '/event_handler'` route is already included in the `template_server.rb` file, which you downloaded in the [prerequisites](#prerequisites) section. The empty route looks like this: - -``` ruby - post '/event_handler' do - - # # # # # # # # # # # # - # ADD YOUR CODE HERE # - # # # # # # # # # # # # - - 200 # success status - end -``` - -Use this route to handle the `check_suite` event by adding the following code: - -``` 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 -end -``` - -Every event that GitHub sends includes a request header called `HTTP_X_GITHUB_EVENT`, which indicates the type of event in the `POST` request. Right now, you're only interested in events of type `check_suite`, which are emitted when a new check suite is created. Each event has an additional `action` field that indicates the type of action that triggered the events. For `check_suite`, the `action` field can be `requested`, `rerequested`, or `completed`. - -The `requested` action requests a check run each time code is pushed to the repository, while the `rerequested` action requests that you re-run a check for code that already exists in the repository. Because both the `requested` and `rerequested` actions require creating a check run, you'll call a helper called `create_check_run`. Let's write that method now. - -## Step 1.3. Creating a check run - -You'll add this new method as a [Sinatra helper](https://github.com/sinatra/sinatra#helpers) in case you want other routes to use it too. Under `helpers do`, add this `create_check_run` method: - -``` 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 -``` - -This code calls the "[AUTOTITLE](/rest/checks#create-a-check-run)" endpoint using the [create_check_run method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#create_check_run-instance_method). - -To create a check run, only two input parameters are required: `name` and `head_sha`. We will use [RuboCop](https://rubocop.readthedocs.io/en/latest/) to implement the CI test later in this quickstart, which is why the name "Octo RuboCop" is used here, but you can choose any name you'd like for the check run. - -You're only supplying the required parameters now to get the basic functionality working, but you'll update the check run later as you collect more information about the check run. By default, GitHub sets the `status` to `queued`. - -GitHub creates a check run for a specific commit SHA, which is why `head_sha` is a required parameter. You can find the commit SHA in the webhook payload. Although you're only creating a check run for the `check_suite` event right now, it's good to know that the `head_sha` is included in both the `check_suite` and `check_run` objects in the event payloads. - -In the code above, you're using the [ternary operator](https://ruby-doc.org/core-2.3.0/doc/syntax/control_expressions_rdoc.html#label-Ternary+if), which works like an `if/else` statement, to check if the payload contains a `check_run` object. If it does, you read the `head_sha` from the `check_run` object, otherwise you read it from the `check_suite` object. - -To test this code, restart the server from your terminal: - -```shell -$ ruby template_server.rb -``` - -{% data reusables.apps.sinatra_restart_instructions %} - -Now open a pull request in the repository where you installed your app. Your app should respond by creating a check run on your pull request. Click on the **Checks** tab, and you should see a check run with the name "Octo RuboCop", or whichever name you chose earlier for the check run. - -If you see other apps in the Checks tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. - -Great! You've told GitHub to create a check run. You can see the check run status is set to `queued` next to a yellow icon. Next, you'll want to wait for GitHub to create the check run and update its status. - -## Step 1.4. Updating a check run - -When your `create_check_run` method runs, it asks GitHub to create a new check run. When GitHub finishes creating the check run, you'll receive the `check_run` webhook event with the `created` action. That event is your signal to begin running the check. - -You'll want to update your event handler to look for the `created` action. While you're updating the event handler, you can add a conditional for the `rerequested` action. When someone re-runs a single test on GitHub by clicking the "Re-run" button, GitHub sends the `rerequested` check run event to your app. When a check run is `rerequested`, you'll want to start the process all over and create a new check run. - -To include a condition for the `check_run` event in the `post '/event_handler'` route, add the following code under `case request.env['HTTP_X_GITHUB_EVENT']`: - -``` 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 - end - end -``` - -GitHub sends all events for `created` check runs to every app installed on a repository that has the necessary checks permissions. That means that your app will receive check runs created by other apps. A `created` check run is a little different from a `requested` or `rerequested` check suite, which GitHub sends only to apps that are being requested to run a check. The code above looks for the check run's application ID. This filters out all check runs for other apps on the repository. - -Next you'll write the `initiate_check_run` method, which is where you'll update the check run status and prepare to kick off your CI test. - -In this section, you're not going to kick off the CI test yet, but you'll walk through how to update the status of the check run from `queued` to `pending` and then from `pending` to `completed` to see the overall flow of a check run. In "[Part 2: Creating the Octo RuboCop CI test](#part-2-creating-the-octo-rubocop-ci-test)," you'll add the code that actually performs the CI test. - -Let's create the `initiate_check_run` method and update the status of the check run. Add the following code to the helpers section: - -``` 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 -``` - -The code above calls the "[AUTOTITLE](/rest/checks#update-a-check-run)" API endpoint using the [`update_check_run` Octokit method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#update_check_run-instance_method) to update the check run that you already created. - -Here's what this code is doing. First, it updates the check run's status to `in_progress` and implicitly sets the `started_at` time to the current time. In [Part 2](#part-2-creating-the-octo-rubocop-ci-test) of this quickstart, you'll add code that kicks off a real CI test under `***** RUN A CI TEST *****`. For now, you'll leave that section as a placeholder, so the code that follows it will just simulate that the CI process succeeds and all tests pass. Finally, the code updates the status of the check run again to `completed`. - -You'll notice in the "[AUTOTITLE](/rest/checks#update-a-check-run)" docs that when you provide a status of `completed`, the `conclusion` and `completed_at` parameters are required. The `conclusion` summarizes the outcome of a check run and can be `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `skipped`, or `action_required`. You'll set the conclusion to `success`, the `completed_at` time to the current time, and the status to `completed`. - -You could also provide more details about what your check is doing, but you'll get to that in the next section. Let's test this code again by re-running `template_server.rb`: - -```shell -$ ruby template_server.rb -``` - -Head over to your open pull request and click the **Checks** tab. Click the "Re-run all" button in the upper right corner. You should see the check run move from `pending` to `in_progress` and end with `success`. - -## Part 2. Creating the Octo RuboCop CI test - -[RuboCop](https://rubocop.readthedocs.io/en/latest/) is a Ruby code linter and formatter. It checks Ruby code to ensure that it complies with the "[Ruby Style Guide](https://github.com/rubocop-hq/ruby-style-guide)." RuboCop has three primary functions: - -- Linting to check code style -- Code formatting -- Replaces the native Ruby linting capabilities using `ruby -w` - -Now that you've got the interface created to receive Checks API events and create check runs, you can create a check run that implements a CI test. - -Your app will run RuboCop on the CI server and create check runs (CI tests in this case) that report the results that RuboCop reports to GitHub. - -The Checks API allows you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. - -Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. That information can be anything: for example, a comment, an error, or a warning. This quickstart uses annotations to visualize RuboCop errors. - -To take advantage of requested actions, app developers can create buttons in the **Checks** tab of pull requests. When someone clicks one of these buttons, the click sends a `requested_action` `check_run` event to the GitHub App. The action that the app takes is completely configurable by the app developer. This quickstart will walk you through adding a button that allows users to request that RuboCop fix the errors it finds. RuboCop supports automatically fixing errors using a command-line option, and you'll configure the `requested_action` to take advantage of this option. - -Let's get started! These are the steps you'll complete in this section: - -1. [Adding a Ruby file](#step-21-adding-a-ruby-file) -1. [Cloning the repository](#step-22-cloning-the-repository) -1. [Running RuboCop](#step-23-running-rubocop) -1. [Collecting RuboCop errors](#step-24-collecting-rubocop-errors) -1. [Updating the check run with CI test results](#step-25-updating-the-check-run-with-ci-test-results) -1. [Automatically fixing RuboCop errors](#step-26-automatically-fixing-rubocop-errors) -1. [Security tips](#step-27-security-tips) - -## Step 2.1. Adding a Ruby file - -You can pass specific files or entire directories for RuboCop to check. In this quickstart, you'll run RuboCop on an entire directory. Because RuboCop only checks Ruby code, you'll want at least one Ruby file in your repository that contains errors. The example file provided below contains a few errors. Add this example Ruby file to the repository where your app is installed (make sure to name the file with an `.rb` extension, as in `myfile.rb`): - -```ruby -# 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 -``` - -## Step 2.2. Cloning the repository - -RuboCop is available as a command-line utility. That means your GitHub App will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To run Git operations in your Ruby app, you can use the [ruby-git](https://github.com/ruby-git/ruby-git) gem. - -The `Gemfile` in the `building-a-checks-api-ci-server` repository already includes the ruby-git gem, and you installed it when you ran `bundle install` in the [prerequisite steps](#prerequisites). To use the gem, add this code to the top of your `template_server.rb` file: - -``` ruby -require 'git' -``` - -Your app needs read permission for "Repository contents" to clone a repository. Later in this quickstart, you'll need to push contents to GitHub, which requires write permission. Go ahead and set your app's "Repository contents" permission to **Read & write** now so you don't need to update it again later. To update your app's permissions: - -1. Select your app from the [app settings page](https://github.com/settings/apps) and click **Permissions & Webhooks** in the sidebar. -1. In the "Permissions" section, find "Repository contents", and select **Read & write** in the "Access" dropdown next to it. -{% data reusables.apps.accept_new_permissions_steps %} - -To clone a repository using your GitHub App's permissions, you can use the app's installation token (`x-access-token:`) shown in the example below: - -```shell -git clone https://x-access-token:@github.com//.git -``` - -The code above clones a repository over HTTP. It requires the full repository name, which includes the repository owner (user or organization) and the repository name. For example, the [octocat Hello-World](https://github.com/octocat/Hello-World) repository has a full name of `octocat/hello-world`. - -After your app clones the repository, it needs to pull the latest code changes and check out a specific Git ref. The code to do all of this will fit nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. Add the following new method to the helper method section in `template_server.rb`: - -``` 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 -``` - -The code above uses the `ruby-git` gem to clone the repository using the app's installation token. This code clones the code in the same directory as `template_server.rb`. To run Git commands in the repository, the code needs to change into the repository directory. Before changing directories, the code stores the current working directory in a variable (`pwd`) to remember where to return before exiting the `clone_repository` method. - -From the repository directory, this code fetches and merges the latest changes (`@git.pull`), checks out the ref (`@git.checkout(ref)`), then changes the directory back to the original working directory (`pwd`). - -Now you've got a method that clones a repository and checks out a ref. Next, you need to add code to get the required input parameters and call the new `clone_repository` method. Add the following code under the `***** RUN A CI TEST *****` comment in your `initiate_check_run` helper method: - -``` ruby -# ***** RUN A CI TEST ***** -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) -``` - -The code above gets the full repository name and the head SHA of the commit from the `check_run` webhook payload. - -## Step 2.3. Running RuboCop - -Great! You're cloning the repository and creating check runs using your CI server. Now you'll get into the nitty gritty details of the [RuboCop linter](https://docs.rubocop.org/rubocop/usage/basic_usage.html#code-style-checker) and [Checks API annotations](/rest/checks#create-a-check-run). - -The following code runs RuboCop and saves the style code errors in JSON format. Add this code below the call to `clone_repository` you added in the [previous step](#step-22-cloning-the-repository) and above the code that updates the check run to complete. - -``` ruby -# Run RuboCop on all files in the repository -@report = `rubocop '#{repository}' --format json` -logger.debug @report -`rm -rf #{repository}` -@output = JSON.parse @report -``` - -The code above runs RuboCop on all files in the repository's directory. The option `--format json` is a handy way to save a copy of the linting results in a machine-parsable format. See the [RuboCop docs](https://docs.rubocop.org/rubocop/formatters.html#json-formatter) for details and an example of the JSON format. - -Because this code stores the RuboCop results in a `@report` variable, it can safely remove the checkout of the repository. This code also parses the JSON so you can easily access the keys and values in your GitHub App using the `@output` variable. - -{% note %} - -**Note:** The command used to remove the repository (`rm -rf`) cannot be undone. See [Step 2.7. Security tips](#step-27-security-tips) to learn how to check webhooks for injected malicious commands that could be used to remove a different directory than intended by your app. For example, if a bad actor sent a webhook with the repository name `./`, your app would remove the root directory. 😱 If for some reason you're _not_ using the method `verify_webhook_signature` (which is included in `template_server.rb`) to validate the sender of the webhook, make sure you check that the repository name is valid. - -{% endnote %} - -You can test that this code works and see the errors reported by RuboCop in your server's debug output. Start up the `template_server.rb` server again and create a new pull request in the repository where you're testing your app: - -```shell -$ ruby template_server.rb -``` - -You should see the linting errors in the debug output, although they aren't printed with formatting. You can use a web tool like [JSON formatter](https://jsonformatter.org/) to format your JSON output like this formatted linting error output: - -```json -{ - "metadata": { - "rubocop_version": "0.60.0", - "ruby_engine": "ruby", - "ruby_version": "2.3.7", - "ruby_patchlevel": "456", - "ruby_platform": "universal.x86_64-darwin18" - }, - "files": [ - { - "path": "Octocat-breeds/octocat.rb", - "offenses": [ - { - "severity": "convention", - "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.", - "cop_name": "Style/StringLiterals", - "corrected": false, - "location": { - "start_line": 17, - "start_column": 17, - "last_line": 17, - "last_column": 22, - "length": 6, - "line": 17, - "column": 17 - } - }, - { - "severity": "convention", - "message": "Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.", - "cop_name": "Style/StringLiterals", - "corrected": false, - "location": { - "start_line": 17, - "start_column": 25, - "last_line": 17, - "last_column": 29, - "length": 5, - "line": 17, - "column": 25 - } - } - ] - } - ], - "summary": { - "offense_count": 2, - "target_file_count": 1, - "inspected_file_count": 1 - } -} -``` - -## Step 2.4. Collecting RuboCop errors - -The `@output` variable contains the parsed JSON results of the RuboCop report. As shown above, the results contain a `summary` section that your code can use to quickly determine if there are any errors. The following code will set the check run conclusion to `success` when there are no reported errors. RuboCop reports errors for each file in the `files` array, so if there are errors, you'll need to extract some data from the file object. - -The Checks API allows you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this quickstart you are [updating the check run](/rest/checks#update-a-check-run) with annotations. - -The Checks API limits the number of annotations to a maximum of 50 per API request. To create more than 50 annotations, you have to make multiple requests to the [AUTOTITLE](/rest/checks#update-a-check-run) endpoint. For example, to create 105 annotations you'd need to call the [AUTOTITLE](/rest/checks#update-a-check-run) endpoint three times. The first two requests would each have 50 annotations, and the third request would include the five remaining annotations. Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. - -A check run expects annotations as an array of objects. Each annotation object must include the `path`, `start_line`, `end_line`, `annotation_level`, and `message`. RuboCop provides the `start_column` and `end_column` too, so you can include those optional parameters in the annotation. Annotations only support `start_column` and `end_column` on the same line. See the [`annotations` object](/rest/checks#annotations-object-1) reference documentation for details. - -You'll extract the required information from RuboCop needed to create each annotation. Append the following code to the code you added in the [previous section](#step-23-running-rubocop): - -``` 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 -``` - -This code limits the total number of annotations to 50. But you can modify this code to update the check run for each batch of 50 annotations. The code above includes the variable `max_annotations` that sets the limit to 50, which is used in the loop that iterates through the offenses. - -When the `offense_count` is zero, the CI test is a `success`. If there are errors, this code sets the conclusion to `neutral` in order to prevent strictly enforcing errors from code linters. But you can change the conclusion to `failure` if you would like to ensure that the check suite fails when there are linting errors. - -When errors are reported, the code above iterates through the `files` array in the RuboCop report. For each file, it extracts the file path and sets the annotation level to `notice`. You could go even further and set specific warning levels for each type of [RuboCop Cop](https://docs.rubocop.org/rubocop/cops.html), but to keep things simpler in this quickstart, all errors are set to a level of `notice`. - -This code also iterates through each error in the `offenses` array and collects the location of the offense and error message. After extracting the information needed, the code creates an annotation for each error and stores it in the `annotations` array. Because annotations only support start and end columns on the same line, `start_column` and `end_column` are only added to the `annotation` object if the start and end line values are the same. - -This code doesn't yet create an annotation for the check run. You'll add that code in the next section. - -## Step 2.5. Updating the check run with CI test results - -Each check run from GitHub contains an `output` object that includes a `title`, `summary`, `text`, `annotations`, and `images`. The `summary` and `title` are the only required parameters for the `output`, but those alone don't offer much detail, so this quickstart adds `text` and `annotations` too. The code here doesn't add an image, but feel free to add one if you'd like! - -For the `summary`, this example uses the summary information from RuboCop and adds some newlines (`\n`) to format the output. You can customize what you add to the `text` parameter, but this example sets the `text` parameter to the RuboCop version. To set the `summary` and `text`, append this code to the code you added in the [previous section](#step-24-collecting-rubocop-errors): - -``` 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']}" -``` - -Now you've got all the information you need to update your check run. In the [first half of this quickstart](#step-14-updating-a-check-run), you added this code to set the status of the check run to `success`: - -``` ruby -# 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' -) -``` - -You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). You can update the code with the following: - -``` 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' -) -``` - -Now that you're setting a conclusion based on the status of the CI test and you've added the output from the RuboCop results, you've created a CI test! Congratulations. 🙌 - -The code above also adds a feature to your CI server called [requested actions](https://developer.github.com/changes/2018-05-23-request-actions-on-checks/) via the `actions` object. {% ifversion fpt or ghec %}(Note this is not related to [GitHub Actions](/actions).) {% endif %}Requested actions add a button in the **Checks** tab on GitHub that allows someone to request the check run to take additional action. The additional action is completely configurable by your app. For example, because RuboCop has a feature to automatically fix the errors it finds in Ruby code, your CI server can use a requested actions button to allow people to request automatic error fixes. When someone clicks the button, the app receives the `check_run` event with a `requested_action` action. Each requested action has an `identifier` that the app uses to determine which button was clicked. - -The code above doesn't have RuboCop automatically fix errors yet. You'll add that in the next section. But first, take a look at the CI test that you just created by starting up the `template_server.rb` server again and creating a new pull request: - -```shell -$ ruby template_server.rb -``` - -The annotations will show up in the **Checks** tab. Also notice the "Fix this" button that you created by adding a requested action. - -If the annotations are related to a file already included in the PR, the annotations will also show up in the **Files changed** tab. - -## Step 2.6. Automatically fixing RuboCop errors - -If you've made it this far, kudos! 👏 You've already created a CI test. In this section, you'll add one more feature that uses RuboCop to automatically fix the errors it finds. You already added the "Fix this" button in the [previous section](#step-25-updating-the-check-run-with-ci-test-results). Now you'll add the code to handle the `requested_action` check run event triggered when someone clicks the "Fix this" button. - -The RuboCop tool [offers](https://docs.rubocop.org/rubocop/usage/basic_usage.html#auto-correcting-offenses) the `--auto-correct` command-line option to automatically fix errors it finds. When you use the `--auto-correct` feature, the updates are applied to the local files on the server. You'll need to push the changes to GitHub after RuboCop does its magic. - -To push to a repository, your app must have write permissions for "Repository contents." You set that permission back in [Step 2.2. Cloning the repository](#step-22-cloning-the-repository) to **Read & write**, so you're all set. - -In order to commit files, Git must know which [username](/get-started/getting-started-with-git/setting-your-username-in-git) and [email](/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address) to associate with the commit. Add two more environment variables in your `.env` file to store the name (`GITHUB_APP_USER_NAME`) and email (`GITHUB_APP_USER_EMAIL`) settings. Your name can be the name of your app and the email can be any email you'd like for this example. For example: - -```ini -GITHUB_APP_USER_NAME=Octoapp -GITHUB_APP_USER_EMAIL=octoapp@octo-org.com -``` - -Once you've updated your `.env` file with the name and email of the author and committer, you'll be ready to add code to read the environment variables and set the Git configuration. You'll add that code soon. - -When someone clicks the "Fix this" button, your app receives the [check run webhook](/webhooks-and-events/webhooks/webhook-events-and-payloads#check_run) with the `requested_action` action type. - -In [Step 1.4. Updating a check run](#step-14-updating-a-check-run) you updated the your `event_handler` to handle look for actions in the `check_run` event. You already have a case statement to handle the `created` and `rerequested` action types: - -``` 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 - end -end -``` - -Add another `when` statement after the `rerequested` case to handle the `rerequested_action` event: - -``` ruby -when 'requested_action' - take_requested_action -``` - -This code calls a new method that will handle all `requested_action` events for your app. Add the following method to the helper methods section of your code: - -``` 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 -``` - -The code above clones a repository just like the code you added in [Step 2.2. Cloning the repository](#step-22-cloning-the-repository). An `if` statement checks that the requested action's identifier matches the RuboCop button identifier (`fix_rubocop_notices`). When they match, the code clones the repository, sets the Git username and email, and runs RuboCop with the option `--auto-correct`. The `--auto-correct` option applies the changes to the local CI server files automatically. - -The files are changed locally, but you'll still need to push them to GitHub. You'll use the handy `ruby-git` gem again to commit all of the files. Git has a single command that stages all modified or deleted files and commits them: `git commit -a`. To do the same thing using `ruby-git`, the code above uses the `commit_all` method. Then the code pushes the committed files to GitHub using the installation token, using the same authentication method as the Git `clone` command. Finally, it removes the repository directory to ensure the working directory is prepared for the next event. - -That's it! The code you have written now completes your Checks API CI server. 💪 Restart your `template_server.rb` server again and create a new pull request: - -```shell -$ ruby template_server.rb -``` - -{% data reusables.apps.sinatra_restart_instructions %} - -This time, click the "Fix this" button to automatically fix the errors RuboCop found from the **Checks** tab. - -In the **Commits** tab, you'll see a brand new commit by the username you set in your Git configuration. You may need to refresh your browser to see the update. - -Because a new commit was pushed to the repo, you'll see a new check suite for Octo RuboCop in the **Checks** tab. But this time there are no errors because RuboCop fixed them all. 🎉 - -You can find the completed code for the app you just built in the `server.rb` file in the [Creating CI tests with the Checks API](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) repository. - -## Step 2.7. Security tips - -The template GitHub App code already has a method to verify incoming webhook payloads to ensure they are from a trusted source. If you are not validating webhook payloads, you'll need to ensure that when repository names are included in the webhook payload, the webhook does not contain arbitrary commands that could be used maliciously. The code below validates that the repository name only contains Latin alphabetic characters, hyphens, and underscores. To provide you with a complete example, the complete `server.rb` code available in the [companion repository](https://github.com/github-developer/creating-ci-tests-with-the-checks-api) for this quickstart includes both the method of validating incoming webhook payloads and this check to verify the repository name. - -``` ruby -# This quickstart example uses the repository name in the webhook with -# command-line utilities. For security reasons, you should validate the -# repository name to ensure that a bad actor isn't attempting to execute -# arbitrary commands or inject false repository names. 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 -``` - -## Troubleshooting - -Here are a few common problems and some suggested solutions. If you run into any other trouble, you can ask for help or advice in the {% data reusables.support.prodname_support_forum_with_url %}. - -- **Q:** My app isn't pushing code to GitHub. I don't see the fixes that RuboCop automatically makes! - - **A:** Make sure you have **Read & write** permissions for "Repository contents," and that you are cloning the repository with your installation token. See [Step 2.2. Cloning the repository](#step-22-cloning-the-repository) for details. - -- **Q:** I see an error in the `template_server.rb` debug output related to cloning my repository. - - **A:** If you see the following error, you haven't deleted the checkout of the repository in one or both of the `initiate_check_run` or `take_requested_action` methods: - - ```shell - 2018-11-26 16:55:13 - Git::GitExecuteError - git clone '--' 'https://x-access-token:ghs_9b2080277016f797074c4dEbD350745f4257@github.com/codertocat/octocat-breeds.git' 'Octocat-breeds' 2>&1:fatal: destination path 'Octocat-breeds' already exists and is not an empty directory.: - ``` - - Compare your code to the `server.rb` file to ensure you have the same code in your `initiate_check_run` and `take_requested_action` methods. - -- **Q:** New check runs are not showing up in the "Checks" tab on GitHub. - - **A:** Restart Smee and re-run your `template_server.rb` server. - -- **Q:** I do not see the "Re-run all" button in the "Checks" tab on GitHub. - - **A:** Restart Smee and re-run your `template_server.rb` server. - -## Conclusion - -After walking through this guide, you've learned the basics of using the Checks API to create a CI server! To review, you: - -- Configured your server to receive Checks API events and create check runs. -- Used RuboCop to check code in repositories and create annotations for the errors. -- Implemented a requested action that automatically fixes linter errors. - -## Next steps - -Here are some ideas for what you can do next: - -- Currently, the "Fix this" button is always displayed. Update the code you wrote to display the "Fix this" button only when RuboCop finds errors. -- If you'd prefer that RuboCop doesn't commit files directly to the head branch, you can update the code to [create a pull request](/rest/pulls#create-a-pull-request) with a new branch based on the head branch. From 5bcdb92d84e71f245ebb0b40a9af97c6ae5ecbb5 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:00:39 -0600 Subject: [PATCH 26/48] Update order of webhook url procedure steps --- .../building-ci-checks-with-a-github-app.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index b78562e2a715..5fe3e2adf40b 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -18,7 +18,7 @@ topics: --- ## Introduction -This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to `check_run` and `check_suite` webhook events using {% data variables.product.prodname_dotcom %}'s REST API. +This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to Checks webhook events using {% data variables.product.prodname_dotcom %}'s REST API. In this tutorial, you will use your computer or codespace as a server while you develop your app. Once the app is ready for production use, you should deploy your app to a dedicated server. @@ -63,7 +63,7 @@ Before you get started, you may want to familiarize yourself with the following - [Webhooks](/webhooks-and-events/webhooks/about-webhooks) - [REST API checks endpoints](/rest/checks) -Checks are also available to use with the GraphQL API, but this tutorial focuses on the REST API. For more information about the GraphQL objects, see [Check Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. +The Checks endpoints are also available to use in GraphQL, but this tutorial focuses on REST. For more information about the GraphQL objects, see [Check Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. ## Setup @@ -102,13 +102,13 @@ The following sections will lead you through setting up the following components In order to develop your app locally, you can use a webhook proxy URL to forward webhook events from {% data variables.product.company_short %} to your computer or codespace. This tutorial uses Smee.io to provide a webhook proxy URL and forward events. -1. In your browser, navigate to https://smee.io/. -1. Click **Start a new channel**. -1. Copy the full URL under "Webhook Proxy URL." You will use this URL in a following step, and during the app registration steps later in the tutorial. 1. In a terminal, run the following command to install the Smee client: ```shell copy npm install --global smee-client ``` +1. In your browser, navigate to https://smee.io/. +1. Click **Start a new channel**. +1. Copy the full URL under "Webhook Proxy URL." You will use this URL in the following step, and during the app registration steps in the next section. 1. In the terminal, run the following command to start the Smee client. Replace `https://smee.io/YOUR_DOMAIN` with the Webhook Proxy URL you copied in the previous step. ```shell copy smee --url https://smee.io/YOUR_DOMAIN --path /event_handler --port 3000 From 2ce20e4949151f7f4c973a212449b3258d54f1dd Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:03:21 -0600 Subject: [PATCH 27/48] Incorporate feedback from @skedwards88 --- .../building-ci-checks-with-a-github-app.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 5fe3e2adf40b..e1dd2c1d6604 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -18,7 +18,7 @@ topics: --- ## Introduction -This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to Checks webhook events using {% data variables.product.prodname_dotcom %}'s REST API. +This tutorial demonstrates how to build a continuous integration (CI) server that runs tests on new code that's pushed to a repository. The tutorial shows how to build and configure a {% data variables.product.prodname_github_app %} to act as a server that receives and responds to `check_run` and `check_suite` webhook events using {% data variables.product.prodname_dotcom %}'s REST API. In this tutorial, you will use your computer or codespace as a server while you develop your app. Once the app is ready for production use, you should deploy your app to a dedicated server. @@ -63,7 +63,7 @@ Before you get started, you may want to familiarize yourself with the following - [Webhooks](/webhooks-and-events/webhooks/about-webhooks) - [REST API checks endpoints](/rest/checks) -The Checks endpoints are also available to use in GraphQL, but this tutorial focuses on REST. For more information about the GraphQL objects, see [Check Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. +Checks are also available to use with the GraphQL API, but this tutorial focuses on the REST API. For more information about the GraphQL objects, see [Check Suite](/graphql/reference/objects#checksuite) and [Check Run](/graphql/reference/objects#checkrun) in the GraphQL documentation. ## Setup From 202ebe69234c7baef2648c106509e350daf4c4d1 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:06:57 -0600 Subject: [PATCH 28/48] Apply suggestions from code review Co-authored-by: Sarah Edwards --- .../building-ci-checks-with-a-github-app.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index e1dd2c1d6604..9c7c1841da61 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -109,9 +109,9 @@ In order to develop your app locally, you can use a webhook proxy URL to forward 1. In your browser, navigate to https://smee.io/. 1. Click **Start a new channel**. 1. Copy the full URL under "Webhook Proxy URL." You will use this URL in the following step, and during the app registration steps in the next section. -1. In the terminal, run the following command to start the Smee client. Replace `https://smee.io/YOUR_DOMAIN` with the Webhook Proxy URL you copied in the previous step. +1. In the terminal, run the following command to start the Smee client. Replace `YOUR_DOMAIN` with the Webhook Proxy URL you copied in the previous step. ```shell copy - smee --url https://smee.io/YOUR_DOMAIN --path /event_handler --port 3000 + smee --url YOUR_DOMAIN --path /event_handler --port 3000 ``` You should see output like the following: ```shell @@ -119,7 +119,7 @@ In order to develop your app locally, you can use a webhook proxy URL to forward Connected https://smee.io/YOUR_DOMAIN ``` -The `smee --url https://smee.io/YOUR_DOMAIN` command tells Smee to forward all webhook events received by the Smee channel to the Smee client running on your computer. The `--path /event_handler` option forwards events to the `/event_handler` route. The `--port 3000` option specifies port 3000, which is the port we'll tell your server to listen to, when we add more code later in the tutorial. Using Smee, your machine does not need to be open to the public internet to receive webhooks from {% data variables.product.prodname_dotcom %}. You can also open that Smee URL in your browser to inspect webhook payloads as they come in. +The `smee --url https://smee.io/YOUR_DOMAIN` command tells Smee to forward all webhook events received by the Smee channel to the Smee client running on your computer. The `--path /event_handler` option forwards events to the `/event_handler` route. The `--port 3000` option specifies port 3000, which is the port you will tell your server to listen to, when you add more code later in the tutorial. Using Smee, your machine does not need to be open to the public internet to receive webhooks from {% data variables.product.prodname_dotcom %}. You can also open that Smee URL in your browser to inspect webhook payloads as they come in. We recommend leaving this terminal window open and keeping Smee connected while you complete the rest of the steps in this guide. Although you can disconnect and reconnect the Smee client without losing your unique domain, you may find it easier to leave it connected and do other command-line tasks in a different terminal window. From 48b0dc8e8b1612ccc3d995e8051e50ea304b90f3 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:17:40 -0600 Subject: [PATCH 29/48] Incoporate feedback from review --- .../building-ci-checks-with-a-github-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 9c7c1841da61..0c1d5b23d772 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -108,7 +108,7 @@ In order to develop your app locally, you can use a webhook proxy URL to forward ``` 1. In your browser, navigate to https://smee.io/. 1. Click **Start a new channel**. -1. Copy the full URL under "Webhook Proxy URL." You will use this URL in the following step, and during the app registration steps in the next section. +1. Copy the full URL under "Webhook Proxy URL." 1. In the terminal, run the following command to start the Smee client. Replace `YOUR_DOMAIN` with the Webhook Proxy URL you copied in the previous step. ```shell copy smee --url YOUR_DOMAIN --path /event_handler --port 3000 From d1538960a2fab8c7790d00afb258eab16b67f178 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:34:26 -0600 Subject: [PATCH 30/48] Apply suggestions from code review Co-authored-by: Sarah Edwards --- .../building-ci-checks-with-a-github-app.md | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 0c1d5b23d772..c0624c9e5571 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -130,7 +130,7 @@ For this tutorial, you must register a {% data variables.product.prodname_github - Has webhooks active - Uses a webhook URL that you can receive locally - Has the "Checks" repository permission -- Subscribes to the "Checks" webhook event +- Subscribes to the "Check suite" and "Check run" webhook events The following steps will guide you through configuring a {% data variables.product.prodname_github_app %} with these settings. For more information about {% data variables.product.prodname_github_app %} settings, see "[AUTOTITLE](/apps/creating-github-apps/creating-github-apps/creating-a-github-app)." @@ -143,7 +143,7 @@ The following steps will guide you through configuring a {% data variables.produ 1. Skip the "Identifying and authorizing users" and "Post installation" sections for this tutorial. 1. Make sure that **Active** is selected under "Webhooks." 1. Under "Webhook URL," enter your webhook proxy URL from earlier. For more information, see "[Get a webhook proxy URL](#get-a-webhook-proxy-url)." -1. Under "Webhook secret," enter a random string. This secret is used to verify that webhooks are sent by {% data variables.product.prodname_dotcom %}. Save this string; You will use it later. +1. Under "Webhook secret," enter a random string. This secret is used to verify that webhooks are sent by {% data variables.product.prodname_dotcom %}. Save this string; you will use it later. 1. Under "Repository permissions," next to "Checks," select **Read & write**. 1. Under "Subscribe to events," select **Check suite** and **Check run**. 1. Under "Where can this GitHub App be installed?," select **Only on this account**. You can change this later if you want to publish your app. @@ -345,7 +345,7 @@ end The rest of this section will explain what the template code does. There aren't any steps that you need to complete in this section. If you're already familiar with the template code, you can skip ahead to "[Start the server](#start-the-server)." -### Review the template code +### Understand the template code Open up the `server.rb` file in a text editor. You'll see comments throughout the file that provide additional context for the template code. We recommend reading those comments carefully and even adding your own comments to accompany new code you write. @@ -386,7 +386,7 @@ configure :development do end ``` -#### Define a before filter +#### Define a `before` filter Sinatra uses before filters that allow you to execute code before the route handler. The `before` block in the template calls four helper methods: `get_payload_request`, `verify_webhook_signature`, `authenticate_app`, and `authenticate_installation`. For more information, see "[Filters](https://github.com/sinatra/sinatra#filters)" and "[Helpers](https://github.com/sinatra/sinatra#helpers)" in the Sinatra documentation. @@ -408,7 +408,7 @@ Sinatra uses before filters that allow you to execute code before the route hand end ``` -Each of these helper methods are defined later in the code, in the `helpers do` code block. For more information, see "[Define the helper methods](#define-the-helper-methods)." +Each of these helper methods are defined later in the code, in the code block that starts with `helpers do`. For more information, see "[Define the helper methods](#define-the-helper-methods)." Under `verify_webhook_signature`, the code that starts with `unless @payload` is a security measure. If a repository name is provided with a webhook payload, this code validates that the repository name contains only Latin alphabetic characters, hyphens, and underscores. This helps ensure that a bad actor isn't attempting to execute arbitrary commands or inject false repository names. Later in the code, under `helpers do`, the `verify_webhook_signature` helper method also validates incoming webhook payloads as an additional security measure. @@ -440,10 +440,11 @@ The third helper method `authenticate_app` allows your {% data variables.product To make API calls, you'll be using the Octokit library. Doing anything interesting with this library will require your {% data variables.product.prodname_github_app %} to authenticate. For more information about the Octokit library, see the [Octokit documentation](https://octokit.github.io/octokit.rb/). -{% data variables.product.prodname_github_apps %} have two methods of authentication: +{% data variables.product.prodname_github_apps %} have three methods of authentication: - Authenticating as a {% data variables.product.prodname_github_app %} using a [JSON Web Token (JWT)](https://jwt.io/introduction). - Authenticating as a specific installation of a {% data variables.product.prodname_github_app %} using an installation access token. +- Authenticating on behalf of a user. This tutorial won't use this method of authentication. You'll learn about authenticating as an installation in the next section, "[Authenticating as an installation](#authenticating-as-an-installation)." @@ -486,9 +487,9 @@ The code above generates a JSON Web Token (JWT) and uses it (along with your app ##### Authenticating as an installation -The fourth and final helper method `authenticate_installation` initializes an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as an installation, which you can use to make authenticated calls to the API. +The fourth and final helper method, `authenticate_installation`, initializes an [Octokit client](https://octokit.github.io/octokit.rb/Octokit/Client.html) authenticated as an installation, which you can use to make authenticated calls to the API. -An _installation_ refers to any user or organization account that has installed the app. Even if someone installs the app on more than one repository, it only counts as one installation because it's within the same account. +An _installation_ refers to any user or organization account that has installed the app. Even if someone grants the app access to more than one repository on that account, it only counts as one installation because it's within the same account. ```ruby # Instantiate an Octokit client authenticated as an installation of a @@ -668,9 +669,9 @@ The following steps will show you how to test that the code works, and that it s 1. In the repository where you installed your app, create a new pull request. 1. In the pull request you just created, navigate to the **Checks** tab. You should see a check run with the name "Octo RuboCop," or whichever name you chose earlier for the check run. -If you see other apps in the **Checks** tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. +If you see other apps in the **Checks** tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. It may also mean that you have {% data variables.product.prodname_actions %} workflows on the repository that are triggered by the `pull_request` or `pull_request_target` event. -So far you've told {% data variables.product.prodname_dotcom %} to create a check run. The check run status in the pull request is set to queued with a yellow icon. Next, you'll want to wait for {% data variables.product.prodname_dotcom %} to create the check run and update its status. +So far you've told {% data variables.product.prodname_dotcom %} to create a check run. The check run status in the pull request is set to queued with a yellow icon. In the next step, you will wait for {% data variables.product.prodname_dotcom %} to create the check run and update its status. ## Step 1.3. Update a check run @@ -785,9 +786,9 @@ These are the steps you'll complete in this section: ## Step 2.1. Add a Ruby file -You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. RuboCop only checks Ruby code. You'll need to add a Ruby file in your repository that contains errors for RuboCop to find. +You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. RuboCop only checks Ruby code. To test your {% data variables.product.prodname_github_app %}, you'll need to add a Ruby file in your repository that contains errors for RuboCop to find. -1. On {% data variables.product.prodname_dotcom %}, navigate to the repository where your app is installed. +1. When you installed the app on your account, you granted the app access to one or more repositories. Navigate to one of those repositories. 2. Create a new file named `myfile.rb`. For more information, see "[AUTOTITLE](/repositories/working-with-files/managing-files/creating-new-files)." 3. Add the following content to `myfile.rb`: @@ -837,7 +838,7 @@ Next you'll need to update your {% data variables.product.prodname_github_app %} ### Add code to clone a repository -To clone a repository, the code will use your {% data variables.product.prodname_github_app %}'s permissions and the Octokit SDK to create an installation token for your app (`x-access-token:`) and use it in the following clone command: +To clone a repository, the code will use your {% data variables.product.prodname_github_app %}'s permissions and the Octokit SDK to create an installation token for your app (`x-access-token:TOKEN`) and use it in the following clone command: ```shell git clone https://x-access-token:TOKEN@github.com/OWNER/REPO.git @@ -921,7 +922,7 @@ The following steps will show you how to test that the code works and view the e ruby server.rb ``` -2. In the repository where you installed your app, create a new pull request. +2. In the repository where you added the `myfile.rb` file, create a new pull request. 3. In your terminal tab where the server is running, you should see debug output that contains linting errors. The linting errors are printed without any formatting. You can copy and paste your debug output into a web tool like [JSON formatter](https://jsonformatter.org/), to format your JSON output like the following example: ```json @@ -982,7 +983,7 @@ The following steps will show you how to test that the code works and view the e The `@output` variable contains the parsed JSON results of the RuboCop report. As shown in the example output in the previous step, the results contain a `summary` section that your code can use to quickly determine if there are any errors. The following code will set the check run conclusion to `success` when there are no reported errors. RuboCop reports errors for each file in the `files` array, so if there are errors, you'll need to extract some data from the file object. -The REST API checks endpoints allow you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this tutorial you will update the check run with annotations, using the "[Update a check run](/rest/checks/runs#update-a-check-run)" endpoint. +The REST API endpoints to manage check runs allow you to create annotations for specific lines of code. When you create or update a check run, you can add annotations. In this tutorial you will update the check run with annotations, using the "[Update a check run](/rest/checks/runs#update-a-check-run)" endpoint. The API limits the number of annotations to a maximum of 50 per request. To create more than 50 annotations, you will have to make multiple requests to the "Update a check run" endpoint. For example, to create 105 annotations you would need to make three separate requests to the API. The first two requests would each have 50 annotations, and the third request would include the five remaining annotations. Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. From d3f1d2751169b6e0c951c3fc7ed310893746575d Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:04:41 -0600 Subject: [PATCH 31/48] Apply suggestions from code review Co-authored-by: Sarah Edwards --- .../building-ci-checks-with-a-github-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index c0624c9e5571..7cd088bcc8b2 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -666,7 +666,7 @@ The following steps will show you how to test that the code works, and that it s ruby server.rb ``` -1. In the repository where you installed your app, create a new pull request. +1. When you installed the app on your account, you granted the app access to one or more repositories. In one of those repositories, create a new pull request. 1. In the pull request you just created, navigate to the **Checks** tab. You should see a check run with the name "Octo RuboCop," or whichever name you chose earlier for the check run. If you see other apps in the **Checks** tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. It may also mean that you have {% data variables.product.prodname_actions %} workflows on the repository that are triggered by the `pull_request` or `pull_request_target` event. From 46bbb35d7d2530892545d584ae0af6f2ec6d4b50 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:02:59 -0600 Subject: [PATCH 32/48] Update code spacing --- .../building-ci-checks-with-a-github-app.md | 372 +++++++++--------- 1 file changed, 185 insertions(+), 187 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 7cd088bcc8b2..fd79fb40ccda 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -606,17 +606,15 @@ Open the `server.rb` file that you created in "[Add code for your {% data variab Under `post '/event_handler' do`, where it says `# ADD EVENT HANDLING HERE #`, add the following code. This route will handle the `check_suite` event. ```ruby copy -# Get the event type from the HTTP_X_GITHUB_EVENT header -case request.env['HTTP_X_GITHUB_EVENT'] - -when 'check_suite' - # A new check_suite has been created. Create a new check run with status queued - if @payload['action'] == 'requested' || @payload['action'] == 'rerequested' - create_check_run - end - - # ADD CHECK_RUN METHOD HERE # -end + # Get the event type from the HTTP_X_GITHUB_EVENT header + case request.env['HTTP_X_GITHUB_EVENT'] + when 'check_suite' + # A new check_suite has been created. Create a new check run with status queued + if @payload['action'] == 'requested' || @payload['action'] == 'rerequested' + create_check_run + end + # ADD CHECK_RUN METHOD HERE # + end ``` Every event that {% data variables.product.prodname_dotcom %} sends includes a request header called `HTTP_X_GITHUB_EVENT`, which indicates the type of event in the `POST` request. Right now, you're only interested in events of type `check_suite`, which are emitted when a new check suite is created. Each event has an additional `action` field that indicates the type of action that triggered the events. For `check_suite`, the `action` field can be `requested`, `rerequested`, or `completed`. @@ -630,20 +628,20 @@ You'll add this new method as a [Sinatra helper](https://github.com/sinatra/sina Under `helpers do`, where it says `# ADD CREATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: ```ruby copy -# Create a new check run with status "queued" -def create_check_run - @installation_client.create_check_run( - # [String, Integer, Hash, Octokit Repository object] A GitHub repository. - @payload['repository']['full_name'], - # [String] The name of your check run. - 'Octo RuboCop', - # [String] The SHA of the commit to check - # The payload structure differs depending on whether a check run or a check suite event occurred. - @payload['check_run'].nil? ? @payload['check_suite']['head_sha'] : @payload['check_run']['head_sha'], - # [Hash] 'Accept' header option, to avoid a warning about the API not being ready for production use. - accept: 'application/vnd.github+json' - ) -end + # Create a new check run with status "queued" + def create_check_run + @installation_client.create_check_run( + # [String, Integer, Hash, Octokit Repository object] A GitHub repository. + @payload['repository']['full_name'], + # [String] The name of your check run. + 'Octo RuboCop', + # [String] The SHA of the commit to check + # The payload structure differs depending on whether a check run or a check suite event occurred. + @payload['check_run'].nil? ? @payload['check_suite']['head_sha'] : @payload['check_run']['head_sha'], + # [Hash] 'Accept' header option, to avoid a warning about the API not being ready for production use. + accept: 'application/vnd.github+json' + ) + end ``` This code calls the "[AUTOTITLE](/rest/checks#create-a-check-run)" endpoint using the Octokit [create_check_run method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#create_check_run-instance_method). @@ -682,17 +680,17 @@ You'll update your event handler to look for the `created` action. While you're Under `post '/event_handler' do`, where it says `# ADD CHECK_RUN METHOD HERE #`, add the following code: ```ruby copy -when 'check_run' - # Check that the event is being sent to this app - if @payload['check_run']['app']['id'].to_s === APP_IDENTIFIER - case @payload['action'] - when 'created' - initiate_check_run - when 'rerequested' - create_check_run - # ADD REQUESTED_ACTION METHOD HERE # - end - end + when 'check_run' + # Check that the event is being sent to this app + if @payload['check_run']['app']['id'].to_s === APP_IDENTIFIER + case @payload['action'] + when 'created' + initiate_check_run + when 'rerequested' + create_check_run + # ADD REQUESTED_ACTION METHOD HERE # + end + end ``` {% data variables.product.prodname_dotcom %} sends all events for `created` check runs to every app installed on a repository that has the necessary checks permissions. That means that your app will receive check runs created by other apps. A `created` check run is a little different from a `requested` or `rerequested` check suite, which {% data variables.product.prodname_dotcom %} sends only to apps that are being requested to run a check. The code above looks for the check run's application ID. This filters out all check runs for other apps on the repository. @@ -706,31 +704,31 @@ Let's create the `initiate_check_run` method and update the status of the check Under `helpers do`, where it says `# ADD INITIATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: ```ruby copy -# 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' - ) + # 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. -end + @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 ``` The code above calls the "[Update a check run](/rest/checks#update-a-check-run)" endpoint using the [`update_check_run` Octokit method](https://msp-greg.github.io/octokit/Octokit/Client/Checks.html#update_check_run-instance_method), and updates the check run that you already created. @@ -851,20 +849,20 @@ After your app clones the repository, it needs to pull the latest code changes a Open your `server.rb` file. Under `helpers do`, where it says `# ADD CLONE_REPOSITORY HELPER METHOD HERE #`, add the following code: ```ruby copy -# Clones the repository to the current working directory, updates the -# contents using Git pull, and checks out the ref. -# -# full_repo_name - The owner and repo. Ex: octocat/hello-world -# repository - The repository name -# ref - The branch, commit SHA, or tag to check out -def clone_repository(full_repo_name, repository, ref) - @git = Git.clone("https://x-access-token:#{@installation_token.to_s}@github.com/#{full_repo_name}.git", repository) - pwd = Dir.getwd() - Dir.chdir(repository) - @git.pull - @git.checkout(ref) - Dir.chdir(pwd) -end + # Clones the repository to the current working directory, updates the + # contents using Git pull, and checks out the ref. + # + # full_repo_name - The owner and repo. Ex: octocat/hello-world + # repository - The repository name + # ref - The branch, commit SHA, or tag to check out + def clone_repository(full_repo_name, repository, ref) + @git = Git.clone("https://x-access-token:#{@installation_token.to_s}@github.com/#{full_repo_name}.git", repository) + pwd = Dir.getwd() + Dir.chdir(repository) + @git.pull + @git.checkout(ref) + Dir.chdir(pwd) + end ``` The code above uses the `ruby-git` gem to clone the repository using the app's installation token. It clones the code in the same directory as `server.rb`. To run Git commands in the repository, the code needs to change into the repository directory. Before changing directories, the code stores the current working directory in a variable (`pwd`) to remember where to return before exiting the `clone_repository` method. @@ -876,13 +874,13 @@ Now you've got a method that clones a repository and checks out a ref. Next, you Under `helpers do`, in the `initiate_check_run` helper method where it says `# ***** RUN A CI TEST *****`, add the following code: ```ruby copy -full_repo_name = @payload['repository']['full_name'] -repository = @payload['repository']['name'] -head_sha = @payload['check_run']['head_sha'] + 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) + clone_repository(full_repo_name, repository, head_sha) -# ADD CODE HERE TO RUN RUBOCOP # + # ADD CODE HERE TO RUN RUBOCOP # ``` The code above gets the full repository name and the head SHA of the commit from the `check_run` webhook payload. @@ -896,13 +894,13 @@ First, you'll add code to run RuboCop and save the style code errors in JSON for Under `clone_repository`, where it says `# ADD CODE HERE TO RUN RUBOCOP #`, add the following code: ```ruby copy -# Run RuboCop on all files in the repository -@report = `rubocop '#{repository}' --format json` -logger.debug @report -`rm -rf #{repository}` -@output = JSON.parse @report + # 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 # + # ADD ANNOTATIONS CODE HERE # ``` The code above runs RuboCop on all files in the repository's directory. The option `--format json` saves a copy of the linting results in a machine-parsable format. For more information, and an example of the JSON format, see "[JSON Formatter](https://docs.rubocop.org/rubocop/formatters.html#json-formatter)" in the RuboCop docs. This code also parses the JSON so you can easily access the keys and values in your {% data variables.product.prodname_github_app %} using the `@output` variable. @@ -994,58 +992,58 @@ Now you'll add code to extract the required information from RuboCop that's need Under the code you added in the previous step, where it says `# ADD ANNOTATIONS CODE HERE #`, add the following code: ```ruby copy -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 = [] + # 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) + annotations.push(annotation) + end + end end - end -end -# ADD CODE HERE TO UPDATE CHECK RUN SUMMARY # + # ADD CODE HERE TO UPDATE CHECK RUN SUMMARY # ``` This code limits the total number of annotations to 50. But you can modify this code to update the check run for each batch of 50 annotations. The code above includes the variable `max_annotations` that sets the limit to 50, which is used in the loop that iterates through the offenses. @@ -1067,9 +1065,9 @@ For the `summary`, this example uses the summary information from RuboCop and ad Under the code you added in the previous step, where it says `# ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #`, add the following code: ``` ruby copy -# Updated check run summary and text parameters -summary = "Octo RuboCop summary\n-Offense count: #{@output['summary']['offense_count']}\n-File count: #{@output['summary']['target_file_count']}\n-Target file count: #{@output['summary']['inspected_file_count']}" -text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}" + # Updated check run summary and text parameters + summary = "Octo RuboCop summary\n-Offense count: #{@output['summary']['offense_count']}\n-File count: #{@output['summary']['target_file_count']}\n-Target file count: #{@output['summary']['inspected_file_count']}" + text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}" ``` Now your code should have all the information it needs to update your check run. In "[Step 1.3. Update a check run](#step-13-update-a-check-run)," you added code to set the status of the check run to `success`. You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). Here's the code you added previously to your `server.rb` file: @@ -1088,25 +1086,25 @@ Now your code should have all the information it needs to update your check run. Replace that code with the following code: ```ruby copy -# Mark the check run as complete! And if there are warnings, share them. -@installation_client.update_check_run( - @payload['repository']['full_name'], - @payload['check_run']['id'], - status: 'completed', - conclusion: conclusion, - output: { - title: 'Octo RuboCop', - summary: summary, - text: text, - annotations: annotations - }, - actions: [{ - label: 'Fix this', - description: 'Automatically fix all linter notices.', - identifier: 'fix_rubocop_notices' - }], - accept: 'application/vnd.github+json' -) + # Mark the check run as complete! And if there are warnings, share them. + @installation_client.update_check_run( + @payload['repository']['full_name'], + @payload['check_run']['id'], + status: 'completed', + conclusion: conclusion, + output: { + title: 'Octo RuboCop', + summary: summary, + text: text, + annotations: annotations + }, + actions: [{ + label: 'Fix this', + description: 'Automatically fix all linter notices.', + identifier: 'fix_rubocop_notices' + }], + accept: 'application/vnd.github+json' + ) ``` Now that your code sets a conclusion based on the status of the CI test, and adds the output from the RuboCop results, you've created a CI test. @@ -1169,8 +1167,8 @@ end After the `rerequested` case, where it says `# ADD REQUESTED_ACTION METHOD HERE #`, add the following code: ```ruby copy -when 'requested_action' - take_requested_action + when 'requested_action' + take_requested_action ``` This code calls a new method that will handle all `requested_action` events for your app. @@ -1178,36 +1176,36 @@ This code calls a new method that will handle all `requested_action` events for Under `helpers do`, where it says `# ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #`, add the following helper method: ```ruby copy -# 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' + # 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 - Dir.chdir(pwd) - `rm -rf '#{repository}'` - end -end ``` The code above clones a repository, just like the code you added in "[Step 2.2. Clone the repository](#step-22-clone-the-repository)." An `if` statement checks that the requested action's identifier matches the RuboCop button identifier (`fix_rubocop_notices`). When they match, the code clones the repository, sets the Git username and email, and runs RuboCop with the option `--auto-correct`. The `--auto-correct` option applies the changes to the local CI server files automatically. From 3cb39956dd0d9d959ac47d3a0decf10eb91bb8c8 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:09:35 -0600 Subject: [PATCH 33/48] Clarify pull request step in test the code procedure --- .../building-ci-checks-with-a-github-app.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index fd79fb40ccda..54f74ae2eb90 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -749,7 +749,7 @@ The following steps will show you how to test that the code works, and that the ruby server.rb ``` -1. In the repository where you installed your app, create a new pull request. +1. When you installed the app on your account, you granted the app access to one or more repositories. In one of those repositories, create a new pull request. 1. In the pull request you just created, navigate to the **Checks** tab. You should see a "Re-run all" button. 1. Click the "Re-run all" button in the upper right corner. The test should run again, and end with `success`. @@ -1123,7 +1123,7 @@ The following steps will show you how to test that the code works and view the C ruby server.rb ``` -2. In the repository where you installed your app, create a new pull request. +2. When you installed the app on your account, you granted the app access to one or more repositories. In one of those repositories, create a new pull request. 3. In the pull request you just created, navigate to the **Checks** tab. You should see annotations for each of the errors that RuboCop found. Also notice the "Fix this" button that you created by adding a requested action. ## Step 2.6. Automatically fix RuboCop errors @@ -1224,7 +1224,7 @@ The following steps will show you how to test that the code works, and that Rubo ruby server.rb ``` -1. In the repository where you installed your app, create a new pull request. +1. When you installed the app on your account, you granted the app access to one or more repositories. In one of those repositories, create a new pull request. 1. In the new pull request you created, navigate to the **Checks** tab, and click the "Fix this" button to automatically fix the errors RuboCop found. 1. Navigate to the **Commits** tab. You should see a new commit by the username you set in your Git configuration. You may need to refresh your browser to see the update. 1. Navigate to the **Checks** tab. You should see a new check suite for Octo RuboCop. But this time there should be no errors, because RuboCop fixed them all. From 11f89ac90955fd2a6c7d1c9e67552926bd20bdef Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:24:51 -0600 Subject: [PATCH 34/48] More specific wording to locate current location in code --- .../building-ci-checks-with-a-github-app.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 54f74ae2eb90..180cb92a2419 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -410,7 +410,7 @@ Sinatra uses before filters that allow you to execute code before the route hand Each of these helper methods are defined later in the code, in the code block that starts with `helpers do`. For more information, see "[Define the helper methods](#define-the-helper-methods)." -Under `verify_webhook_signature`, the code that starts with `unless @payload` is a security measure. If a repository name is provided with a webhook payload, this code validates that the repository name contains only Latin alphabetic characters, hyphens, and underscores. This helps ensure that a bad actor isn't attempting to execute arbitrary commands or inject false repository names. Later in the code, under `helpers do`, the `verify_webhook_signature` helper method also validates incoming webhook payloads as an additional security measure. +Under `verify_webhook_signature`, the code that starts with `unless @payload` is a security measure. If a repository name is provided with a webhook payload, this code validates that the repository name contains only Latin alphabetic characters, hyphens, and underscores. This helps ensure that a bad actor isn't attempting to execute arbitrary commands or inject false repository names. Later, in the code block that starts with `helpers do`, the `verify_webhook_signature` helper method also validates incoming webhook payloads as an additional security measure. #### Define a route handler @@ -625,7 +625,7 @@ The `requested` action requests a check run each time code is pushed to the repo You'll add this new method as a [Sinatra helper](https://github.com/sinatra/sinatra#helpers) in case you want other routes to use it too. -Under `helpers do`, where it says `# ADD CREATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: +In the code block that starts with `helpers do`, where it says `# ADD CREATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: ```ruby copy # Create a new check run with status "queued" @@ -677,7 +677,7 @@ When your `create_check_run` method runs, it asks {% data variables.product.prod You'll update your event handler to look for the `created` action. While you're updating the event handler, you can add a conditional for the `rerequested` action. When someone re-runs a single test on {% data variables.product.prodname_dotcom %} by clicking the "Re-run" button, {% data variables.product.prodname_dotcom %} sends the `rerequested` check run event to your app. When a check run is `rerequested`, you'll start the process all over and create a new check run. To do that, you'll include a condition for the `check_run` event in the `post '/event_handler'` route. -Under `post '/event_handler' do`, where it says `# ADD CHECK_RUN METHOD HERE #`, add the following code: +In the code block that starts with `post '/event_handler' do`, where it says `# ADD CHECK_RUN METHOD HERE #`, add the following code: ```ruby copy when 'check_run' @@ -701,7 +701,7 @@ In this section, you're not going to kick off the CI test yet, but you'll walk t Let's create the `initiate_check_run` method and update the status of the check run. -Under `helpers do`, where it says `# ADD INITIATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: +In the code block that starts with `helpers do`, where it says `# ADD INITIATE_CHECK_RUN HELPER METHOD HERE #`, add the following code: ```ruby copy # Start the CI process @@ -846,7 +846,7 @@ The command above clones a repository over HTTP. It requires the full repository After your app clones the repository, it needs to pull the latest code changes and check out a specific Git ref. The code to do all of this will fit nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. -Open your `server.rb` file. Under `helpers do`, where it says `# ADD CLONE_REPOSITORY HELPER METHOD HERE #`, add the following code: +Open your `server.rb` file. In the code block that starts with `helpers do`, where it says `# ADD CLONE_REPOSITORY HELPER METHOD HERE #`, add the following code: ```ruby copy # Clones the repository to the current working directory, updates the @@ -871,7 +871,7 @@ From the repository directory, this code fetches and merges the latest changes ( Now you've got a method that clones a repository and checks out a ref. Next, you need to add code to get the required input parameters and call the new `clone_repository` method. -Under `helpers do`, in the `initiate_check_run` helper method where it says `# ***** RUN A CI TEST *****`, add the following code: +In the code block that starts with `helpers do`, in the `initiate_check_run` helper method where it says `# ***** RUN A CI TEST *****`, add the following code: ```ruby copy full_repo_name = @payload['repository']['full_name'] @@ -891,7 +891,7 @@ So far, your code clones the repository and creates check runs using your CI ser First, you'll add code to run RuboCop and save the style code errors in JSON format. -Under `clone_repository`, where it says `# ADD CODE HERE TO RUN RUBOCOP #`, add the following code: +In the code block that starts with `helpers do`, find the `initiate_check_run` helper method. Inside that helper method, under `clone_repository(full_repo_name, repository, head_sha)`, where it says `# ADD CODE HERE TO RUN RUBOCOP #`, add the following code: ```ruby copy # Run RuboCop on all files in the repository @@ -1173,7 +1173,7 @@ After the `rerequested` case, where it says `# ADD REQUESTED_ACTION METHOD HERE This code calls a new method that will handle all `requested_action` events for your app. -Under `helpers do`, where it says `# ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #`, add the following helper method: +In the code block that starts with `helpers do`, where it says `# ADD TAKE_REQUESTED_ACTION HELPER METHOD HERE #`, add the following helper method: ```ruby copy # Handles the check run `requested_action` event From 34b3cd46868047a0d2066c33e4d31fff342b946a Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:26:02 -0600 Subject: [PATCH 35/48] Use backticks around before filter --- .../building-ci-checks-with-a-github-app.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 180cb92a2419..23b5787ceafb 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -355,7 +355,7 @@ The next code you'll see is the `class GHApp < Sinatra::Application` declaration - [Read the environment variables](#read-the-environment-variables) - [Turn on logging](#turn-on-logging) -- [Define a before filter](#define-a-before-filter) +- [Define a `before` filter](#define-a-before-filter) - [Define the route handler](#define-a-route-handler) - [Define the helper methods](#define-the-helper-methods) @@ -388,7 +388,7 @@ end #### Define a `before` filter -Sinatra uses before filters that allow you to execute code before the route handler. The `before` block in the template calls four helper methods: `get_payload_request`, `verify_webhook_signature`, `authenticate_app`, and `authenticate_installation`. For more information, see "[Filters](https://github.com/sinatra/sinatra#filters)" and "[Helpers](https://github.com/sinatra/sinatra#helpers)" in the Sinatra documentation. +Sinatra uses `before` filters that allow you to execute code before the route handler. The `before` block in the template calls four helper methods: `get_payload_request`, `verify_webhook_signature`, `authenticate_app`, and `authenticate_installation`. For more information, see "[Filters](https://github.com/sinatra/sinatra#filters)" and "[Helpers](https://github.com/sinatra/sinatra#helpers)" in the Sinatra documentation. ```ruby # Executed before each request to the `/event_handler` route @@ -907,7 +907,7 @@ The code above runs RuboCop on all files in the repository's directory. The opti After running RuboCop and saving the linting results, this code runs the command `rm -rf` to remove the checkout of the repository. Because the code stores the RuboCop results in a `@report` variable, it can safely remove the checkout of the repository. -The `rm -rf` command cannot be undone. To keep your app secure, the code in this tutorial checks incoming webhooks for injected malicious commands that could be used to remove a different directory than intended by your app. For example, if a bad actor sent a webhook with the repository name `./`, your app would remove the root directory. The `verify_webhook_signature` method validates the sender of the webhook. The `verify_webhook_signature` event handler also checks that the repository name is valid. For more information, see "[Define a before filter](#define-a-before-filter)." +The `rm -rf` command cannot be undone. To keep your app secure, the code in this tutorial checks incoming webhooks for injected malicious commands that could be used to remove a different directory than intended by your app. For example, if a bad actor sent a webhook with the repository name `./`, your app would remove the root directory. The `verify_webhook_signature` method validates the sender of the webhook. The `verify_webhook_signature` event handler also checks that the repository name is valid. For more information, see "[Define a `before` filter](#define-a-before-filter)." ### Test the code From a82a277b11bd0a265e9962f7c406c8143131c7fc Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:38:02 -0600 Subject: [PATCH 36/48] Modify the title of part 2 for clarity --- .../building-ci-checks-with-a-github-app.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 23b5787ceafb..40a2385a026c 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -27,7 +27,7 @@ This tutorial uses Ruby, but you can use any programming language that you can r This tutorial is broken into two parts: - In part one, you'll learn how to set up the framework for a CI server using {% data variables.product.prodname_dotcom %}'s REST API, create new check runs for CI tests when a repository receives newly pushed commits, and re-run check runs when a user requests that action on {% data variables.product.prodname_dotcom %}. -- In part two, you'll add a linter test to your CI server, create annotations that are displayed in the **Checks** and **Files Changed** tab of a pull request, and automatically fix linter recommendations by exposing a "Fix this" button in the **Checks** tab of the pull request. +- In part two, you'll add functionality to your CI test, by adding a linter test to your CI server. You'll also create annotations that are displayed in the **Checks** and **Files Changed** tab of a pull request, and automatically fix linter recommendations by exposing a "Fix this" button in the **Checks** tab of the pull request. ### About continuous integration (CI) @@ -753,7 +753,11 @@ The following steps will show you how to test that the code works, and that the 1. In the pull request you just created, navigate to the **Checks** tab. You should see a "Re-run all" button. 1. Click the "Re-run all" button in the upper right corner. The test should run again, and end with `success`. -## Part 2. Creating the Octo RuboCop CI test +## Part 2. Creating a CI test + +Now that you've got the interface created to receive API events and create check runs, you can create a check run that implements a CI test. + +Your app will run RuboCop on the CI server, and create check runs (CI tests in this case) that report the results that RuboCop reports to {% data variables.product.prodname_dotcom %}. RuboCop is a Ruby code linter and formatter. It checks Ruby code to ensure that it complies with the Ruby Style Guide. For more information, see the [RuboCop documentation](https://rubocop.readthedocs.io/en/latest/). @@ -763,11 +767,7 @@ RuboCop has three primary functions: * Code formatting * Replaces the native Ruby linting capabilities using `ruby -w` -Now that you've got the interface created to receive API events and create check runs, you can create a check run that implements a CI test. - -Your app will run RuboCop on the CI server, and create check runs (CI tests in this case) that report the results that RuboCop reports to {% data variables.product.prodname_dotcom %}. - -The checks API endpoints allow you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. +The REST API allows you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. For example, you could show that information as a comment, error, or warning on a specific line of code. This tutorial uses annotations to visualize RuboCop errors. From 267c2a238d1beb99af124eda0bd64f3efb617c0a Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:39:09 -0600 Subject: [PATCH 37/48] Move rubocop para back to where it was --- .../building-ci-checks-with-a-github-app.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 40a2385a026c..557340429059 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -757,8 +757,6 @@ The following steps will show you how to test that the code works, and that the Now that you've got the interface created to receive API events and create check runs, you can create a check run that implements a CI test. -Your app will run RuboCop on the CI server, and create check runs (CI tests in this case) that report the results that RuboCop reports to {% data variables.product.prodname_dotcom %}. - RuboCop is a Ruby code linter and formatter. It checks Ruby code to ensure that it complies with the Ruby Style Guide. For more information, see the [RuboCop documentation](https://rubocop.readthedocs.io/en/latest/). RuboCop has three primary functions: @@ -767,6 +765,8 @@ RuboCop has three primary functions: * Code formatting * Replaces the native Ruby linting capabilities using `ruby -w` +Your app will run RuboCop on the CI server, and create check runs (CI tests in this case) that report the results that RuboCop reports to {% data variables.product.prodname_dotcom %}. + The REST API allows you to report rich details about each check run, including statuses, images, summaries, annotations, and requested actions. Annotations are information about specific lines of code in a repository. An annotation allows you to pinpoint and visualize the exact parts of the code you'd like to show additional information for. For example, you could show that information as a comment, error, or warning on a specific line of code. This tutorial uses annotations to visualize RuboCop errors. From 5dfe0482e9eb6df815d1f40f1ce046929fe55f19 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:44:55 -0600 Subject: [PATCH 38/48] Incorporate feedback from @skedwards88 --- .../building-ci-checks-with-a-github-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 557340429059..8cec6c402035 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -784,7 +784,7 @@ These are the steps you'll complete in this section: ## Step 2.1. Add a Ruby file -You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. RuboCop only checks Ruby code. To test your {% data variables.product.prodname_github_app %}, you'll need to add a Ruby file in your repository that contains errors for RuboCop to find. +You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. RuboCop only checks Ruby code. To test your {% data variables.product.prodname_github_app %}, you'll need to add a Ruby file in your repository that contains errors for RuboCop to find. After adding the following Ruby file to your repository, you will update your CI check to run RuboCop on the code. 1. When you installed the app on your account, you granted the app access to one or more repositories. Navigate to one of those repositories. 2. Create a new file named `myfile.rb`. For more information, see "[AUTOTITLE](/repositories/working-with-files/managing-files/creating-new-files)." From 97ab2cad9cd7d8dd93143ca03c0f6347baf6f34d Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:48:59 -0600 Subject: [PATCH 39/48] Apply suggestions from code review Co-authored-by: Sarah Edwards --- .../building-ci-checks-with-a-github-app.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 8cec6c402035..23730e2d54a8 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -812,6 +812,8 @@ You can pass specific files or entire directories for RuboCop to check. In this m.display ``` +1. Commit the file to your repository. + ## Step 2.2. Clone the repository RuboCop is available as a command-line utility. That means your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To do that, your code will need to be able to run Git operations, and your {% data variables.product.prodname_github_app %} will need to have the correct permissions to clone a repository. From 4048bd2f6b1bdc312e4ef6168ae49036164132d7 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:03:41 -0600 Subject: [PATCH 40/48] Incorporate review feedback from @skedwards88 --- .../building-ci-checks-with-a-github-app.md | 4 +--- data/reusables/apps/accept_new_permissions_steps.md | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 23730e2d54a8..0b932c7309d2 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -846,8 +846,6 @@ git clone https://x-access-token:TOKEN@github.com/OWNER/REPO.git The command above clones a repository over HTTP. It requires the full repository name, which includes the repository owner (user or organization) and the repository name. For example, the [octocat Hello-World](https://github.com/octocat/Hello-World) repository has a full name of `octocat/hello-world`. -After your app clones the repository, it needs to pull the latest code changes and check out a specific Git ref. The code to do all of this will fit nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. - Open your `server.rb` file. In the code block that starts with `helpers do`, where it says `# ADD CLONE_REPOSITORY HELPER METHOD HERE #`, add the following code: ```ruby copy @@ -869,7 +867,7 @@ Open your `server.rb` file. In the code block that starts with `helpers do`, whe The code above uses the `ruby-git` gem to clone the repository using the app's installation token. It clones the code in the same directory as `server.rb`. To run Git commands in the repository, the code needs to change into the repository directory. Before changing directories, the code stores the current working directory in a variable (`pwd`) to remember where to return before exiting the `clone_repository` method. -From the repository directory, this code fetches and merges the latest changes (`@git.pull`), checks out the ref (`@git.checkout(ref)`), then changes the directory back to the original working directory (`pwd`). +From the repository directory, this code fetches and merges the latest changes (`@git.pull`), and checks out the specifig Git ref (`@git.checkout(ref)`). The code to do all of this fits nicely into its own method. To perform these operations, the method needs the name and full name of the repository and the ref to checkout. The ref can be a commit SHA, branch, or tag. When it's done, the code changes the directory back to the original working directory (`pwd`). Now you've got a method that clones a repository and checks out a ref. Next, you need to add code to get the required input parameters and call the new `clone_repository` method. diff --git a/data/reusables/apps/accept_new_permissions_steps.md b/data/reusables/apps/accept_new_permissions_steps.md index 7fd674922865..e8205b5859c2 100644 --- a/data/reusables/apps/accept_new_permissions_steps.md +++ b/data/reusables/apps/accept_new_permissions_steps.md @@ -1,2 +1,2 @@ 1. Click **Save changes** at the bottom of the page. -1. If you've installed the app on your account, check your email and follow the link to accept the new permissions. Any time you change your app's permissions or webhooks, users who have installed the app (including yourself) will need to accept the new permissions before the changes take effect. You can also accept the new permissions by navigating to your [installations page](https://github.com/settings/installations) and clicking on "Configure" next to your app. You'll see a banner at the top of the page letting you know that the app is requesting different permissions. Click "Details" and click "Accept new permissions." +1. If you've installed the app on your account, check your email and follow the link to accept the new permissions. Any time you change your app's permissions or webhooks, users who have installed the app (including yourself) will need to accept the new permissions before the changes take effect. You can also accept the new permissions by navigating to your [installations page](https://github.com/settings/installations). You'll see a link under the app name, letting you know that the app is requesting different permissions. Click "Review request", and then click "Accept new permissions." From 61044952f47d4c4e798712bf3ea2acd40d8ad25a Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:04:50 -0600 Subject: [PATCH 41/48] Update content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md Co-authored-by: Sarah Edwards --- .../building-ci-checks-with-a-github-app.md | 1 - 1 file changed, 1 deletion(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 0b932c7309d2..4daaddf13c13 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -1307,7 +1307,6 @@ class GHAapp < Sinatra::Application take_requested_action end end - end 200 # success status From 1034e2ab91671bb74e74b81eb24d4ba3f2c244b7 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 29 Jun 2023 10:08:58 -0600 Subject: [PATCH 42/48] Update content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md Co-authored-by: Jake Wilkins --- .../building-ci-checks-with-a-github-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 4daaddf13c13..9d0e65d4b50a 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -193,7 +193,7 @@ This section will show you how to add some basic template code for your {% data Add the following template code to your `server.rb` file: ```ruby copy -require 'sinatra' # Use the Sinatra web framework +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 From 8519f221780ee0fddcc20b2f9984e7c850f4778e Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 29 Jun 2023 10:28:46 -0600 Subject: [PATCH 43/48] Update content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md Co-authored-by: Sarah Edwards --- .../building-ci-checks-with-a-github-app.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 9d0e65d4b50a..2bdc933fcd73 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -1581,15 +1581,14 @@ This tutorial used your computer or codespace as a server. Once the app is ready Once you have a server that is set up to receive webhook traffic from {% data variables.product.company_short %}, update the webhook URL in your app settings. You should not use Smee.io to forward your webhooks in production. -#### Update the `port` and `host` constants +#### Update the `:port` setting -When you deploy your app, you will want to change the host and port where your server is listening. +When you deploy your app, you will want to change the port where your server is listening. The code already tells your server to listen to all available network interfaces by setting `:bind` to `0.0.0.0`. -For example, you can set a `PORT` environment variable on your server to indicate the port where your server should listen. You can set a `NODE_ENV` environment variable on your server to `production`. Then, you can update the place where your code defines the `port` and `host` constants so that your server listens to all available network interfaces (`0.0.0.0`) instead of the local network interface (`localhost`) on your deployment port: +For example, you can set a `PORT` variable in your `.env` file on your server to indicate the port where your server should listen. Then, you can update the place where your code sets `:port` so that your server listens on your deployment port: ```javascript copy -const port = process.env.PORT || 3000; -const host = process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost'; +set :port, ENV['PORT'] ``` #### Secure your app's credentials From bd0dcac5fc8cba1191e8472325d67517a3a9c4ab Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 29 Jun 2023 10:21:13 -0600 Subject: [PATCH 44/48] Incoporate feedback from @jakewilkins --- .../building-ci-checks-with-a-github-app.md | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index 2bdc933fcd73..a8f550ffadba 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -193,17 +193,14 @@ This section will show you how to add some basic template code for your {% data Add the following template code to your `server.rb` file: ```ruby copy -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 - -set :port, 3000 -set :bind, '0.0.0.0' +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. @@ -211,6 +208,10 @@ set :bind, '0.0.0.0' 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")) @@ -347,21 +348,30 @@ The rest of this section will explain what the template code does. There aren't ### Understand the template code -Open up the `server.rb` file in a text editor. You'll see comments throughout the file that provide additional context for the template code. We recommend reading those comments carefully and even adding your own comments to accompany new code you write. - -At the top of the file you'll see `set :port 3000`, which sets the port used when starting the web server to match the port you redirected your webhook payloads to in "[Get a Webhook Proxy URL](#get-a-webhook-proxy-url)." +Open the `server.rb` file in a text editor. You'll see comments throughout the file that provide additional context for the template code. We recommend reading those comments carefully and even adding your own comments to accompany new code you write. -The next code you'll see is the `class GHApp < Sinatra::Application` declaration. You'll write all of the code for your {% data variables.product.prodname_github_app %} inside this class. The following sections explain in detail what the code does inside this class. +Below the list of required files, the first code you'll see is the `class GHApp < Sinatra::Application` declaration. You'll write all of the code for your {% data variables.product.prodname_github_app %} inside this class. The following sections explain in detail what the code does inside this class. +- [Set the port](#set-the-port) - [Read the environment variables](#read-the-environment-variables) - [Turn on logging](#turn-on-logging) - [Define a `before` filter](#define-a-before-filter) - [Define the route handler](#define-a-route-handler) - [Define the helper methods](#define-the-helper-methods) +#### Set the port + +The first thing you'll see inside the `class GHApp < Sinatra::Application` declaration is `set :port 3000`. This sets the port used when starting the web server, to match the port you redirected your webhook payloads to in "[Get a Webhook Proxy URL](#get-a-webhook-proxy-url)." + +```ruby + # Sets the port that's used when starting the web server. + set :port, 3000 + set :bind, '0.0.0.0' +``` + #### Read the environment variables -First, this class reads the three environment variables you set in "[Store your app's identifying information and credentials](#store-your-apps-identifying-information-and-credentials)," and stores them in variables to use later. +Next, this class reads the three environment variables you set in "[Store your app's identifying information and credentials](#store-your-apps-identifying-information-and-credentials)," and stores them in variables to use later. ```ruby # Expects the private key in PEM format. Converts the newlines. @@ -1234,18 +1244,14 @@ The following steps will show you how to test that the code works, and that Rubo This is what the final code in `server.rb` should look like, after you've followed all of the steps in this tutorial. There are also comments throughout the code that provide additional context. ```ruby copy -require 'sinatra' # 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 -require 'git' - -set :port, 3000 -set :bind, '0.0.0.0' +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. @@ -1253,6 +1259,10 @@ set :bind, '0.0.0.0' 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")) From b15d93730df8faf7f827a2dcb22d0b7050b0753b Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 29 Jun 2023 10:24:07 -0600 Subject: [PATCH 45/48] Incorporate feedback from @skedwards88 --- .../building-ci-checks-with-a-github-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index a8f550ffadba..de92a2257f42 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -826,7 +826,7 @@ You can pass specific files or entire directories for RuboCop to check. In this ## Step 2.2. Clone the repository -RuboCop is available as a command-line utility. That means your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To do that, your code will need to be able to run Git operations, and your {% data variables.product.prodname_github_app %} will need to have the correct permissions to clone a repository. +RuboCop is available as a command-line utility. That means, if you want to run RuboCop on a repository, your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To do that, your code will need to be able to run Git operations, and your {% data variables.product.prodname_github_app %} will need to have the correct permissions to clone a repository. ### Allow Git operations From d606b1a10f1edee09fea6c86c5e558d6b8000031 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 29 Jun 2023 10:29:53 -0600 Subject: [PATCH 46/48] Ruby not javascript --- .../building-ci-checks-with-a-github-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index de92a2257f42..fe6a6beba14e 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -1597,7 +1597,7 @@ When you deploy your app, you will want to change the port where your server is For example, you can set a `PORT` variable in your `.env` file on your server to indicate the port where your server should listen. Then, you can update the place where your code sets `:port` so that your server listens on your deployment port: -```javascript copy +```ruby copy set :port, ENV['PORT'] ``` From 9bf8ad8adbafd3c1d0109171bab3654a57503d33 Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:06:28 -0600 Subject: [PATCH 47/48] Update punctuation and quotes for new style rule plus some tiny misc fixes --- .../building-ci-checks-with-a-github-app.md | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index fe6a6beba14e..c9891f66eb76 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -108,7 +108,7 @@ In order to develop your app locally, you can use a webhook proxy URL to forward ``` 1. In your browser, navigate to https://smee.io/. 1. Click **Start a new channel**. -1. Copy the full URL under "Webhook Proxy URL." +1. Copy the full URL under "Webhook Proxy URL". 1. In the terminal, run the following command to start the Smee client. Replace `YOUR_DOMAIN` with the Webhook Proxy URL you copied in the previous step. ```shell copy smee --url YOUR_DOMAIN --path /event_handler --port 3000 @@ -138,15 +138,15 @@ The following steps will guide you through configuring a {% data variables.produ {% data reusables.user-settings.developer_settings %} {% data reusables.user-settings.github_apps %} 1. Click **New GitHub App**. -1. Under "GitHub App name," enter a name for your app. For example, `USERNAME-ci-test-app` where `USERNAME` is your {% data variables.product.company_short %} username. -1. Under "Homepage URL," enter a URL for your app. For example, you can use the URL of the repository that you created to store the code for your app. +1. Under "GitHub App name", enter a name for your app. For example, `USERNAME-ci-test-app` where `USERNAME` is your {% data variables.product.company_short %} username. +1. Under "Homepage URL", enter a URL for your app. For example, you can use the URL of the repository that you created to store the code for your app. 1. Skip the "Identifying and authorizing users" and "Post installation" sections for this tutorial. -1. Make sure that **Active** is selected under "Webhooks." -1. Under "Webhook URL," enter your webhook proxy URL from earlier. For more information, see "[Get a webhook proxy URL](#get-a-webhook-proxy-url)." -1. Under "Webhook secret," enter a random string. This secret is used to verify that webhooks are sent by {% data variables.product.prodname_dotcom %}. Save this string; you will use it later. -1. Under "Repository permissions," next to "Checks," select **Read & write**. -1. Under "Subscribe to events," select **Check suite** and **Check run**. -1. Under "Where can this GitHub App be installed?," select **Only on this account**. You can change this later if you want to publish your app. +1. Make sure that **Active** is selected under "Webhooks". +1. Under "Webhook URL", enter your webhook proxy URL from earlier. For more information, see "[Get a webhook proxy URL](#get-a-webhook-proxy-url)." +1. Under "Webhook secret", enter a random string. This secret is used to verify that webhooks are sent by {% data variables.product.prodname_dotcom %}. Save this string; you will use it later. +1. Under "Repository permissions", next to "Checks", select **Read & write**. +1. Under "Subscribe to events", select **Check suite** and **Check run**. +1. Under "Where can this GitHub App be installed?", select **Only on this account**. You can change this later if you want to publish your app. 1. Click **Create GitHub App**. ### Store your app's identifying information and credentials @@ -167,10 +167,10 @@ Make sure that you are on a secure machine before performing these steps, since ``` 1. {% data reusables.apps.navigate-to-app-settings-page %} -1. On your app's settings page, next to "App ID," find the app ID for your app. +1. On your app's settings page, next to "App ID", find the app ID for your app. 1. In your `.env` file, replace `YOUR_APP_ID` with the app ID of your app. -1. In your `.env` file, replace `YOUR_WEBHOOK_SECRET` with the webhook secret for your app. If you have forgotten your webhook secret, under "Webhook secret (optional)," click **Change secret**. Enter a new secret, then click **Save changes**. -1. On your app's settings page, under "Private keys," click **Generate a private key**. You will see a private key `.pem` file downloaded to your computer. +1. In your `.env` file, replace `YOUR_WEBHOOK_SECRET` with the webhook secret for your app. If you have forgotten your webhook secret, under "Webhook secret (optional)", click **Change secret**. Enter a new secret, then click **Save changes**. +1. On your app's settings page, under "Private keys", click **Generate a private key**. You will see a private key `.pem` file downloaded to your computer. 1. Open the `.pem` file with a text editor, or use the following command on the command line to display the contents of the file: `cat PATH/TO/YOUR/private-key.pem`. 1. Copy and paste the entire contents of the file into your `.env` file as the value of `GITHUB_PRIVATE_KEY`, and add double quotes around the entire value. @@ -613,7 +613,7 @@ Open the `server.rb` file that you created in "[Add code for your {% data variab end ``` -Under `post '/event_handler' do`, where it says `# ADD EVENT HANDLING HERE #`, add the following code. This route will handle the `check_suite` event. +In the code block that starts with `post '/event_handler' do`, where it says `# ADD EVENT HANDLING HERE #`, add the following code. This route will handle the `check_suite` event. ```ruby copy # Get the event type from the HTTP_X_GITHUB_EVENT header @@ -822,7 +822,7 @@ You can pass specific files or entire directories for RuboCop to check. In this m.display ``` -1. Commit the file to your repository. +1. If you created the file locally, make sure you commit and push the file to your repository on {% data variables.product.prodname_dotcom %}. ## Step 2.2. Clone the repository @@ -843,7 +843,7 @@ require 'git' Next you'll need to update your {% data variables.product.prodname_github_app %}'s permissions. Your app will need read permission for "Contents" to clone a repository. And later in this tutorial, it will need write permission to push contents to {% data variables.product.prodname_dotcom %}. To update your app's permissions: 1. Select your app from the [app settings page](https://github.com/settings/apps), and click **Permissions & events** in the sidebar. -1. Under "Repository permissions," next to "Contents," select **Read & write**. +1. Under "Repository permissions", next to "Contents", select **Read & write**. {% data reusables.apps.accept_new_permissions_steps %} ### Add code to clone a repository @@ -904,13 +904,13 @@ First, you'll add code to run RuboCop and save the style code errors in JSON for In the code block that starts with `helpers do`, find the `initiate_check_run` helper method. Inside that helper method, under `clone_repository(full_repo_name, repository, head_sha)`, where it says `# ADD CODE HERE TO RUN RUBOCOP #`, add the following code: ```ruby copy - # Run RuboCop on all files in the repository - @report = `rubocop '#{repository}' --format json` - logger.debug @report - `rm -rf #{repository}` - @output = JSON.parse @report + # 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 # + # ADD ANNOTATIONS CODE HERE # ``` The code above runs RuboCop on all files in the repository's directory. The option `--format json` saves a copy of the linting results in a machine-parsable format. For more information, and an example of the JSON format, see "[JSON Formatter](https://docs.rubocop.org/rubocop/formatters.html#json-formatter)" in the RuboCop docs. This code also parses the JSON so you can easily access the keys and values in your {% data variables.product.prodname_github_app %} using the `@output` variable. @@ -1075,9 +1075,9 @@ For the `summary`, this example uses the summary information from RuboCop and ad Under the code you added in the previous step, where it says `# ADD CODE HERE TO UPDATE CHECK RUN SUMMARY #`, add the following code: ``` ruby copy - # Updated check run summary and text parameters - summary = "Octo RuboCop summary\n-Offense count: #{@output['summary']['offense_count']}\n-File count: #{@output['summary']['target_file_count']}\n-Target file count: #{@output['summary']['inspected_file_count']}" - text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}" + # Updated check run summary and text parameters + summary = "Octo RuboCop summary\n-Offense count: #{@output['summary']['offense_count']}\n-File count: #{@output['summary']['target_file_count']}\n-Target file count: #{@output['summary']['inspected_file_count']}" + text = "Octo RuboCop version: #{@output['metadata']['rubocop_version']}" ``` Now your code should have all the information it needs to update your check run. In "[Step 1.3. Update a check run](#step-13-update-a-check-run)," you added code to set the status of the check run to `success`. You'll need to update that code to use the `conclusion` variable you set based on the RuboCop results (to `success` or `neutral`). Here's the code you added previously to your `server.rb` file: @@ -1096,25 +1096,25 @@ Now your code should have all the information it needs to update your check run. Replace that code with the following code: ```ruby copy - # Mark the check run as complete! And if there are warnings, share them. - @installation_client.update_check_run( - @payload['repository']['full_name'], - @payload['check_run']['id'], - status: 'completed', - conclusion: conclusion, - output: { - title: 'Octo RuboCop', - summary: summary, - text: text, - annotations: annotations - }, - actions: [{ - label: 'Fix this', - description: 'Automatically fix all linter notices.', - identifier: 'fix_rubocop_notices' - }], - accept: 'application/vnd.github+json' - ) + # Mark the check run as complete! And if there are warnings, share them. + @installation_client.update_check_run( + @payload['repository']['full_name'], + @payload['check_run']['id'], + status: 'completed', + conclusion: conclusion, + output: { + title: 'Octo RuboCop', + summary: summary, + text: text, + annotations: annotations + }, + actions: [{ + label: 'Fix this', + description: 'Automatically fix all linter notices.', + identifier: 'fix_rubocop_notices' + }], + accept: 'application/vnd.github+json' + ) ``` Now that your code sets a conclusion based on the status of the CI test, and adds the output from the RuboCop results, you've created a CI test. From f59a574bcc2418ff9d889daa3c8448f80d1cb70f Mon Sep 17 00:00:00 2001 From: Jess Hosman <1183847+jhosman@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:39:20 -0600 Subject: [PATCH 48/48] Use a separate test repo to test the app --- .../building-ci-checks-with-a-github-app.md | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md index c9891f66eb76..a979af385e7d 100644 --- a/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md +++ b/content/apps/creating-github-apps/writing-code-for-a-github-app/building-ci-checks-with-a-github-app.md @@ -567,9 +567,10 @@ Your app doesn't do anything yet, but at this point, you can get it running on t ## Test that the server is listening to your app -You can test that the server is listening to your app by triggering an event for it to receive. You'll do that by installing the app on your {% data variables.product.prodname_dotcom %} account, which will send the [`installation` event](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) to your app. If the app receives it, you should see output in the terminal tab where you're running `server.rb`. +You can test that the server is listening to your app by triggering an event for it to receive. You'll do that by installing the app on a test repository, which will send the [`installation` event](/webhooks-and-events/webhooks/webhook-events-and-payloads#installation) to your app. If the app receives it, you should see output in the terminal tab where you're running `server.rb`. -1. Install the {% data variables.product.prodname_github_app %} on your account. For more information, see "[AUTOTITLE](/apps/using-github-apps/installing-your-own-github-app#installing-your-own-github-app)." You can choose to install it on all of your repositories, or just one. For example, you could install it on the repository you created for this tutorial. +1. Create a new repository to use for testing your tutorial code. For more information, see "[AUTOTITLE](/repositories/creating-and-managing-repositories/creating-a-new-repository)." +1. Install the {% data variables.product.prodname_github_app %} on the repository you just created. For more information, see "[AUTOTITLE](/apps/using-github-apps/installing-your-own-github-app#installing-your-own-github-app)." During the installation process, choose **Only select repositories**, and select the repository you created in the previous step. 2. After you click **Install**, look at the output in the terminal tab where you're running `server.rb`. You should see something like this: ```shell @@ -674,7 +675,7 @@ The following steps will show you how to test that the code works, and that it s ruby server.rb ``` -1. When you installed the app on your account, you granted the app access to one or more repositories. In one of those repositories, create a new pull request. +1. Create a pull request in the test repository you created in "[Test that the server is listening to your app](#test-that-the-server-is-listening-to-your-app)." This is the repository that you granted the app access to. 1. In the pull request you just created, navigate to the **Checks** tab. You should see a check run with the name "Octo RuboCop," or whichever name you chose earlier for the check run. If you see other apps in the **Checks** tab, it means you have other apps installed on your repository that have **Read & write** access to checks and are subscribed to **Check suite** and **Check run** events. It may also mean that you have {% data variables.product.prodname_actions %} workflows on the repository that are triggered by the `pull_request` or `pull_request_target` event. @@ -759,7 +760,7 @@ The following steps will show you how to test that the code works, and that the ruby server.rb ``` -1. When you installed the app on your account, you granted the app access to one or more repositories. In one of those repositories, create a new pull request. +1. Create a pull request in the test repository you created in "[Test that the server is listening to your app](#test-that-the-server-is-listening-to-your-app)." This is the repository that you granted the app access to. 1. In the pull request you just created, navigate to the **Checks** tab. You should see a "Re-run all" button. 1. Click the "Re-run all" button in the upper right corner. The test should run again, and end with `success`. @@ -796,9 +797,9 @@ These are the steps you'll complete in this section: You can pass specific files or entire directories for RuboCop to check. In this tutorial, you'll run RuboCop on an entire directory. RuboCop only checks Ruby code. To test your {% data variables.product.prodname_github_app %}, you'll need to add a Ruby file in your repository that contains errors for RuboCop to find. After adding the following Ruby file to your repository, you will update your CI check to run RuboCop on the code. -1. When you installed the app on your account, you granted the app access to one or more repositories. Navigate to one of those repositories. -2. Create a new file named `myfile.rb`. For more information, see "[AUTOTITLE](/repositories/working-with-files/managing-files/creating-new-files)." -3. Add the following content to `myfile.rb`: +1. Navigate to the test repository you created in "[Test that the server is listening to your app](#test-that-the-server-is-listening-to-your-app)." This is the repository that you granted the app access to. +1. Create a new file named `myfile.rb`. For more information, see "[AUTOTITLE](/repositories/working-with-files/managing-files/creating-new-files)." +1. Add the following content to `myfile.rb`: ```ruby copy # frozen_string_literal: true @@ -824,7 +825,7 @@ You can pass specific files or entire directories for RuboCop to check. In this 1. If you created the file locally, make sure you commit and push the file to your repository on {% data variables.product.prodname_dotcom %}. -## Step 2.2. Clone the repository +## Step 2.2. Allow RuboCop to clone the test repository RuboCop is available as a command-line utility. That means, if you want to run RuboCop on a repository, your {% data variables.product.prodname_github_app %} will need to clone a local copy of the repository on the CI server so RuboCop can parse the files. To do that, your code will need to be able to run Git operations, and your {% data variables.product.prodname_github_app %} will need to have the correct permissions to clone a repository. @@ -930,8 +931,8 @@ The following steps will show you how to test that the code works and view the e ruby server.rb ``` -2. In the repository where you added the `myfile.rb` file, create a new pull request. -3. In your terminal tab where the server is running, you should see debug output that contains linting errors. The linting errors are printed without any formatting. You can copy and paste your debug output into a web tool like [JSON formatter](https://jsonformatter.org/), to format your JSON output like the following example: +1. In the repository where you added the `myfile.rb` file, create a new pull request. +1. In your terminal tab where the server is running, you should see debug output that contains linting errors. The linting errors are printed without any formatting. You can copy and paste your debug output into a web tool like [JSON formatter](https://jsonformatter.org/), to format your JSON output like the following example: ```json { @@ -1133,8 +1134,8 @@ The following steps will show you how to test that the code works and view the C ruby server.rb ``` -2. When you installed the app on your account, you granted the app access to one or more repositories. In one of those repositories, create a new pull request. -3. In the pull request you just created, navigate to the **Checks** tab. You should see annotations for each of the errors that RuboCop found. Also notice the "Fix this" button that you created by adding a requested action. +1. In the repository where you added the `myfile.rb` file, create a new pull request. +1. In the pull request you just created, navigate to the **Checks** tab. You should see annotations for each of the errors that RuboCop found. Also notice the "Fix this" button that you created by adding a requested action. ## Step 2.6. Automatically fix RuboCop errors @@ -1234,7 +1235,7 @@ The following steps will show you how to test that the code works, and that Rubo ruby server.rb ``` -1. When you installed the app on your account, you granted the app access to one or more repositories. In one of those repositories, create a new pull request. +1. In the repository where you added the `myfile.rb` file, create a new pull request. 1. In the new pull request you created, navigate to the **Checks** tab, and click the "Fix this" button to automatically fix the errors RuboCop found. 1. Navigate to the **Commits** tab. You should see a new commit by the username you set in your Git configuration. You may need to refresh your browser to see the update. 1. Navigate to the **Checks** tab. You should see a new check suite for Octo RuboCop. But this time there should be no errors, because RuboCop fixed them all.