Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Slack integration for Google Cloud Build #2863

Merged
merged 1 commit into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions scripts/gcb2slack/.gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore

node_modules
1 change: 1 addition & 0 deletions scripts/gcb2slack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/node_modules/
43 changes: 43 additions & 0 deletions scripts/gcb2slack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Slack Notifications for Google Cloud Build

> This is not an officially supported Google product.
This Google Cloud Function sends a notification to a Slack channel whenever a
Google Cloud Build completes.

![Success notification](success.png)
![Failure notification](failure.png)

## Installation

### Create a Slack Webhook

Follow
[Slack's "Getting Started with Incoming Webhooks" instructions](https://api.slack.com/messaging/webhooks)
to create a Slack app and incoming webhook URL. This URL will be required in the
next installation step.

### Deploy Google Cloud Function

If you don't already have a Google Cloud project, [create one now](https://cloud.google.com/resource-manager/docs/creating-managing-projects). Ensure that you have the [Google Cloud SDK](https://cloud.google.com/sdk/) installed as well.

Run the following command to deploy this Google Cloud Function, after changing
`$PROJECT` to your
[Google Cloud project ID](https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects)
and `$SLACK_WEBHOOK_URL` to the webhook URL you created in the previous
installation step. You may also customise `$FUNC_NAME` and `$STAGE_BUCKET`.

```shell
# Required parameters
$ PROJECT="YOUR-GOOGLE-CLOUD-PROJECT-ID"
$ SLACK_WEBHOOK_URL="YOUR-SLACK-WEBHOOK-URL"
# Customisable parameters
$ FUNC_NAME="cloudbuild2slack"
$ STAGE_BUCKET="${PROJECT}-cloudbuild2slack"

$ gcloud --project="${PROJECT}" functions deploy "${FUNC_NAME}" \
--stage-bucket="${STAGE_BUCKET}" \
--trigger-topic="cloud-builds" \
--runtime="nodejs10" \
--set-env-vars="SLACK_WEBHOOK_URL=${SLACK_WEBHOOK_URL}"
```
Binary file added scripts/gcb2slack/failure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
112 changes: 112 additions & 0 deletions scripts/gcb2slack/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
const {
IncomingWebhook
} = require('@slack/webhook');

const {
auth
} = require('google-auth-library');

const webhook = new IncomingWebhook(process.env.SLACK_WEBHOOK_URL);

// Add additional statues to list if you'd like:
// QUEUED, WORKING, CANCELLED
const interestingStatuses = ['SUCCESS', 'FAILURE', 'INTERNAL_ERROR', 'TIMEOUT'];

// cloudbuildToSlack reads from a Google Cloud Build Pub/Sub topic and writes
// to a Slack webhook.
module.exports.cloudbuildToSlack = async (pubSubEvent, context) => {
if (!pubSubEvent.data) {
console.info("No `data` field in pubSubEvent");
return;
}
const build = eventToBuild(pubSubEvent.data);

// Skip if the current status is not in the status list.
if (interestingStatuses.indexOf(build.status) === -1) {
console.log("Build status %s is ignored", build.status);
return;
}

// Send message to Slack.
const message = await createSlackMessage(build);
await webhook.send(message);
};

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

const getTrigger = async (triggerId) => {
const client = await auth.getClient({
scopes: 'https://www.googleapis.com/auth/cloud-platform',
});
const projectId = await auth.getProjectId();
const res = await client.request({
url: `https://cloudbuild.googleapis.com/v1/projects/${projectId}/triggers/${triggerId}`,
});
return res.data;
}

// createSlackMessage creates a message from a build object.
const createSlackMessage = async (build) => {
const buildStatus = build.statusDetail || build.status;
const trigger = await getTrigger(build.buildTriggerId);

let {
REPO_NAME: repo,
BRANCH_NAME: branch,
} = build.substitutions;

// If there was no substitution containing the repository name,
// take it from build.source. Unfortunately, this name will be the name
// used by Google Cloud Source Repositories, not the name on GitHub.
if (!repo && build.source.repoSource) {
repo = build.source.repoSource.repoName;
}

let messagePrefix, color;
switch (build.status) {
case 'TIMEOUT':
messagePrefix = "Timeout of ";
color = 'danger';
break;
case 'FAILURE':
messagePrefix = "Failed to ";
color = 'danger';
break;
case 'INTERNAL_ERROR':
messagePrefix = "Internal error while trying to ";
color = 'warning';
break;
case 'SUCCESS':
messagePrefix = "Completed ";
color = 'good';
break;
}

let message = {
text: `${messagePrefix}'${trigger.description}' for ${repo} repo`,
attachments: [{
fallback: buildStatus,
color: color,
fields: [{
title: 'Status',
value: buildStatus,
short: true,
}, {
title: 'Build Logs',
value: build.logUrl,
}],
}],
};

if (branch) {
message.attachments[0].fields.push({
title: 'Branch',
value: branch,
})
}

return message
}
Loading