Edit on GitHub
Report issue
Page history

Connect Container Builder to GitHub Through Cloud Functions

Author(s): @cbuchacher ,   Published: 2018-05-14


This tutorial demonstrates how to use Cloud Container Builder as a continuous integration service for GitHub repositories. You will implement a Cloud Function which listens to build events and updates the build status in GitHub using the Statuses API. The function is implemented in Node.js.

Prerequisites

  1. Create a project in the Google Cloud Platform Console.
  2. Enable billing for your project.
  3. Install the Cloud SDK.

Set up automated builds

You will use Cloud Container Builder and the Build Triggers feature to upload your website automatically every time you push a new git commit to the source repository.

Head over to the Container Registry → Build Triggers section on Google Cloud Platform Console and click Add trigger:

Add build trigger on Container Registry section

Then select GitHub as the source of your repository. You may need to authorize access to your GitHub account so that Cloud Source Repositories can mirror and create commit hooks on your GitHub repositories.

Select GitHub as the source

Then, pick your repository from the list. If you forked the sample repository above, pick it here:

Select the Git repository

For the Trigger settings:

  • choose Trigger Type Branch
  • choose Build Configuration cloudbuild.yaml
  • Set the file location to cloudbuild.yaml

Create build trigger

Now, create a cloudbuild.yaml file with the following contents in your repository. Note that you can add files to your repository on GitHub’s website, or by cloning the repository on your development machine:

steps:
  - name: gcr.io/cloud-builders/git
    args: ["show", "README.md"]

This YAML file declares a build step with the git show command. It prints the contents of README.md to the build logs.

After saving the file, commit and push the changes:

$ git add cloudbuild.yaml
$ git commit -m 'Add build configuration'
$ git push

Trigger the first build

Once you push the cloudbuild.yaml file to your repository and create the Build Trigger, you can kick off the first build manually. Head over to the Google Cloud Platform Console Build Triggers section, click Run trigger and choose the the branch (i.e. master) to build.

Trigger the first build manually

Now click Build history on the left and watch the build job execute and succeed:

Build history shows the executing or completed builds

Remember that after now, every commit pushed to any branch of your GitHub repository will trigger a new build. If you need to change which git branches or tags you use for publishing, you can update the Build Trigger configuration.

Preparing the Cloud Function

  1. Run the following commands in a new empty directory.
  2. Create a package.json file by running the following command:

    $ npm init
    
  3. Run the following command to install the dependencies that the function uses to make REST calls to the GitHub API:

    $ npm install --save --save-exact @octokit/rest@15.2.6
    

Writing the Function Code

Create a file named index.js with the following contents:

'use strict';

const octokit = require('@octokit/rest')();
const GITHUB_ACCESS_TOKEN = '[TOKEN]';

/**
 * Background Cloud Function to be triggered by cloud-builds Pub/Sub topic.
 *
 * @param {object} event The Cloud Functions event.
 */
exports.gcb_github = (event) => {
  const build = eventToBuild(event.data.data);
  return postBuildStatus(build);
};

// eventToBuild transforms pubsub event message to a build object.
const eventToBuild = (data) => {
  return JSON.parse(new Buffer(data, 'base64').toString());
}

function postBuildStatus(build) {
  octokit.authenticate({
    type: 'token',
    token: GITHUB_ACCESS_TOKEN
  });

  let repo = getRepo(build);
  if (repo === null || repo.site !== 'github') {
    return Promise.resolve();
  }
  let [ state, description ] = buildToGithubStatus(build);
  return octokit.repos.createStatus({
    owner: repo.user,
    repo: repo.name,
    sha: build.sourceProvenance.resolvedRepoSource.commitSha,
    state: state,
    description: description,
    context: 'gcb',
    target_url: build.logUrl
  });
}

function getRepo(build) {
  let repoNameRe = /^([^-]*)-([^-]*)-(.*)$/;
  let repoName = build.source.repoSource.repoName;
  let match = repoNameRe.exec(repoName);
  if (!match) {
    console.error(`Cannot parse repoName: ${repoName}`);
    return null;
  }
  return {
    site: match[1],
    user: match[2],
    name: match[3]
  };
}

function buildToGithubStatus(build) {
  let map = {
    QUEUED: ['pending', 'Build is queued'],
    WORKING: ['pending', 'Build is being executed'],
    FAILURE: ['error', 'Build failed'],
    INTERNAL_ERROR: ['failure', 'Internal builder error'],
    CANCELLED: ['failure', 'Build cancelled by user'],
    TIMEOUT: ['failure', 'Build timed out'],
    SUCCESS: ['success', 'Build finished successfully']
  }
  return map[build.status];
}

The container builder publishes messages in the cloud-builds Pub/Sub topic. Each message triggers our cloud function with the message contents passed as input data. The message contains the build response. Here is an example of a build response, reduced to the most relevant fields:

{
  "id": "3e3c9152-6ba9-b562-e9c6-a49651c076ce",
  "projectId": "my-google-cloud-project-1234",
  "status": "WORKING",
  "source": {
      "repoSource": {
          "projectId": "my-google-cloud-project-1234",
          "repoName": "github-myowner-myreponame",
          "branchName": "master"
      }
  },
  "sourceProvenance": {
    "resolvedRepoSource": {
      "commitSha": "29d39613fa9958598228f7f162b3f03ab4189ece"
    }
  },
  "logUrl": "https://console.cloud.google.com/gcr/builds/3e3c9152-6ba9-b562-e9c6-a49651c076ce?project=1234567890"
};

The function then determines the GitHub repository owner and name, maps the build status (WORKING, FAILURE, SUCCESS) to a commit state (pending, error, success), and POST's to the Statuses API.

Generating a GitHub Personal Access Token

  1. Read about Creating a personal access token for the command line.
  2. Select repo:status from the scopes.
  3. Substitute the generated token for [TOKEN] in index.js.

Deploying the Cloud Function

  1. Read about deploying Cloud Functions.
  2. Run the following command to deploy the function:

    $ gcloud beta functions deploy gcb_github --stage-bucket [YOUR_STAGE_BUCKET] --trigger-topic cloud-builds
    

    Replace [YOUR_STAGE_BUCKET] with your Cloud Functions staging bucket, e.g. gs://[PROJECT_ID]_cloudbuild.

Testing the integration

Trigger another build manually. The commit status is shown as a check mark or X mark in the commit log in GitHub. More details of the build status are shown in Pull Requests.

GitHub status check

You can check the logs for errors.

Credits

Section Set up automated builds is derived from @ahmetb's Automated Static Website Publishing with Cloud Container Builder tutorial.

Submit a Tutorial

Share step-by-step guides

SUBMIT A TUTORIAL

Request a Tutorial

Ask for community help

SUBMIT A REQUEST

GCP Tutorials

Tutorials published by GCP

VIEW TUTORIALS

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see our Site Policies. Java is a registered trademark of Oracle and/or its affiliates.