Scripting with Github Octokit in Google Cloud Build

Peter Malina
Google Cloud - Community
5 min readDec 30, 2020

It’s not been so long I’ve written how to post Terraform plan in Pull Requests with Google Cloud Build. In case you’re looking for the simplest way to post a comment into a PR, that’s still the preferred way.

But let’s be honest. Using Personal Access Tokens inside organizations? I bet you’ll have a scary security officer after you 3 seconds after you click the “generate” button.

The solution: a private Github App. Using a Github App comes with plenty of shiny stuff:

  • Permissions are granular for every API. For example, you can only allow Pull Request APIs for your app, instead of giving it full repo access
  • You can use the APIs directly via officially supported Octokit
  • You can choose which repositories the app should have access to
  • The Github App is secured via a .pem certificate, and Octokit creates short-living tokens (security++)

One more thing: You can use JavaScript to script this builder!

Installing the Builder

Edit: the contribution was already merged. installation steps below are updated (PR#472):

git clone https://github.com/GoogleCloudPlatform/cloud-builders-community.git
cd cloud-builders-community/octokit
gcloud builds submit --config=cloudbuild.yaml .

Requirements

Before we continue, make sure you have the following:

  • A Github App (either personal or in an organization). Use the official docs to create a new one
  • A generated certificate for the Github App. Smash some buttons in Github
  • Your working repository selected for access in the Github App (otherwise you won’t be able to access it)

Securing the Certificate inside GCP

First things first, once you generated the .pem file for your Github App, open your Secret Manager, and create a new Secret. Either use the Upload file functionality or open the file and copy its contents into the Secret value. Everything else may stay default unless you want to tweak it.

Creating a Secret in the Secret Manager

Preparing the Builder

The setup is pretty simple. The builder automatically authenticates your provided Github App. You’ll need:

  • App ID — Head to the General tab of your Github App, you’ll see the ID under the About section
  • Installation ID — Open settings of your user or organization (wherever you installed the Github App), Installed Github Apps > “Configure” (on your app). The installation ID can be found as the last route segment of your browser URL
Installation ID can be found in the browser URL during Configuration

There’s plenty of use-cases to cover, from getting more metadata directly from Github to creating custom checks in a single build. You may want to report granular checks for different types of tests or modules that are running in a single build.

Cloud Build nowadays doesn't support creating custom build summaries by default, so your default build summary will most likely look like the one below:

Cloud Build Summary as seen in Github

Let’s make a nice one of our own with terraform apply (in case you are wondering, there's no way to do this with the Github CLI).

Scripting the Octokit Builder

Before the fun begins, please read this:

> The builder uses eval() to run your scripts. Make sure you are not running the builder for public PRs or with untrusted sources

The cloudbuild.yaml will look like the one below. The first step applies the terraform (e.g. when merged into master, or on manual invocation). The second step sends this check to Github and makes it available in its summary.

steps:
- name: 'gcr.io/$PROJECT_ID/terraform'
entrypoint: 'bash'
args: [
'-c',
'terraform init && terraform apply -auto-approve -no-color | tee apply.txt'
]
- name: 'gcr.io/$PROJECT_ID/octokit'
env:
- "APP_ID=<app-id>"
- "INSTALLATION_ID=<installation-id>"
- "INSTALLATION_PK=sm://<link-to-your-secret>"
args:
- |-
const applyContent = fs.readFileSync('./apply.txt', 'utf-8', 'r+');

await octokit.checks.create({
owner: '$_GITHUB_USER',
repo: '$REPO_NAME',
name: 'terraform-apply',
head_sha: '$COMMIT_SHA',
conclusion: 'success',
output: {
title: 'terraform apply',
summary: `\`\`\`\n$${applyContent}\n\`\`\``
}
});
substitutions:
_GITHUB_USER: <your-user-or-org>

Comments to make sense of it:

  • The script is executed within an async function, so you want to use await on Promises. (If you don’t the step may exit and not execute your Promise)
  • You can use the fs module directly (already included) and octokit as an initialized, authenticated Octokit/rest object
  • You can require anything from Node.js core libs if you need to

Once you submit the configuration into your repository and invoke it. You’ll shortly see the build for your commit in the repository with the summary as in the image below:

Github Build summary after the build passed

Minimal Setup with a File

In case you don’t like inline scripts, you can use this snippet to run your script from a file instead. The builder checks the first argument for file presence.

This approach is best in two main scenarios:

  • The script is large and you don’t want it to bloat the build script
  • You want to repeat the same script multiple times

Parametrizing such script can be done using the env variables.

> You can’t pass any flags or further arguments into the script.

steps:
- name: 'gcr.io/$PROJECT_ID/octokit'
env:
- "APP_ID=<app-id>"
- "INSTALLATION_ID=<installation-id>"
- "INSTALLATION_PK=sm://<link-to-your-secret>"
args:
- script.js

Bonus: Running Terraform Plan on PRs

In case you came from my previous article about Github CLI builder, I am leaving a snippet that creates a new PR Review using the approach with Octokit. Remember to:

  • Make sure you are running gcr.io/$PROJECT_ID/octokit
  • Add necessary env variables
const planContent = fs.readFileSync('./plan.txt', 'utf-8', 'r+');

await octokit.pulls.createReview({
owner: '$_GITHUB_USER',
repo: '$REPO_NAME',
pull_number: '$_PR_NUMBER',
event: 'COMMENT',
body: `<details>
<summary>Terraform Plan Results</summary>

\`\`\`
$${planContent}
\`\`\`
</details>`
});

That’s it, folks! Enjoy your new Github scripting experience inside Cloud Build.

PS: yes, the builder can be used in any container-native CI (even Jenkins). You will only need to find a way to securely inject the .pem file from Github.

If you have any questions, hit me up on my Twitter

--

--