Skip to content

Commit

Permalink
Add some example webhook formatting (pulumi#214)
Browse files Browse the repository at this point in the history
This change adds a utility function to aws-ts-pulumi-webhooks in order to provide users with a few examples to work by in formatting their own messages

Signed-off-by: Christian Nunciato <[email protected]>
  • Loading branch information
cnunciato committed Feb 1, 2019
1 parent 3ba529d commit cde3ef8
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 9 deletions.
24 changes: 15 additions & 9 deletions aws-ts-pulumi-webhooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as crypto from "crypto";

import * as slack from "@slack/client";

import { formatSlackMessage } from "./util";

const config = new pulumi.Config();

const stackConfig = {
Expand Down Expand Up @@ -53,19 +55,23 @@ webhookHandler.get("/", async (_, res) => {
});

webhookHandler.post("/", logRequest, authenticateRequest, async (req, res) => {
const webhookKind = req.headers["pulumi-webhook-kind"];
const webhookKind = req.headers["pulumi-webhook-kind"] as string; // headers[] returns (string | string[]).
const payload = <string>req.body.toString();
const prettyPrintedPayload = JSON.stringify(JSON.parse(payload), null, 2);

const client = new slack.WebClient(stackConfig.slackToken);
await client.chat.postMessage(
{
channel: stackConfig.slackChannel,
text:
`Pulumi Service Webhook (\`${webhookKind}\`)\n` +
"```\n" + prettyPrintedPayload + "```\n",
as_user: true,
});

const fallbackText = `Pulumi Service Webhook (\`${webhookKind}\`)\n` + "```\n" + prettyPrintedPayload + "```\n";
const messageArgs: slack.ChatPostMessageArguments = {
channel: stackConfig.slackChannel,
text: fallbackText,
as_user: true,
}

// Format the Slack message based on the kind of webhook received.
const formattedMessageArgs = formatSlackMessage(webhookKind, payload, messageArgs);

await client.chat.postMessage(formattedMessageArgs);
res.status(200).end(`posted to Slack channel ${stackConfig.slackChannel}\n`);
});

Expand Down
152 changes: 152 additions & 0 deletions aws-ts-pulumi-webhooks/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { ChatPostMessageArguments } from "@slack/client";

// Return a formatted copy of the Slack message object, based on the kind of Pulumi webhook received.
// See the Pulumi and Slack webhook documentation for details.
// https://pulumi.io/reference/service/webhooks.html
// https://api.slack.com/docs/message-attachments
export function formatSlackMessage(kind: string, payload: any, messageArgs: ChatPostMessageArguments): ChatPostMessageArguments {
const cloned: ChatPostMessageArguments = Object.assign({}, messageArgs) as ChatPostMessageArguments;

switch (kind) {
case "stack":
return formatStack(payload, cloned);
case "team":
return formatTeam(payload, cloned);
case "stack_preview":
case "stack_update":
return formatUpdate(kind, payload, cloned);
}

return cloned;
}

function formatStack(payload: any, args: ChatPostMessageArguments): ChatPostMessageArguments {
const summary = `${payload.organization.githubLogin}/${payload.projectName}/${payload.stackName} ${payload.action}.`;
args.text = summary;
args.attachments = [
{
fallback: summary,
fields: [
{
title: "User",
value: `${payload.user.name} (${payload.user.githubLogin})`,
short: true,
},
{
title: "Action",
value: payload.action,
short: true,
},
],
},
];
return args
}

function formatTeam(payload: any, args: ChatPostMessageArguments): ChatPostMessageArguments {
const summary = `${payload.organization.githubLogin} team ${payload.action}.`;
args.text = summary;
args.attachments = [
{
fallback: summary,
fields: [
{
title: "User",
value: `${payload.user.name} (${payload.user.githubLogin})`,
short: true,
},
{
title: "Team",
value: payload.team.name,
short: true,
},
{
title: "Stack",
value: `${payload.organization.githubLogin}/${payload.stackName}`,
short: true,
},
{
title: "Action",
value: payload.action,
short: true,
},
{
title: "Members",
value: payload.team.members.map((m: any) => `• ${m.name} (${m.githubLogin})`).join("\n"),
short: false,
},
{
title: "Stacks",
value: payload.team.stacks.map((s: any) => `• ${s.stackName} (${s.permission})`).join("\n"),
short: false,
},
],
},
];
return args;
}

function formatUpdate(kind: "stack_preview" | "stack_update", payload: any, args: ChatPostMessageArguments): ChatPostMessageArguments {
const summary = `${payload.organization.githubLogin}/${payload.projectName}/${payload.stackName} ${payload.kind} ${payload.result}.`;
args.text = `${resultEmoji(payload.result)} ${summary}`;
args.attachments = [
{
fallback: `${summary}: ${payload.updateUrl}`,
color: resultColor(payload.result),
fields: [
{
title: "User",
value: `${payload.user.name} (${payload.user.githubLogin})`,
short: true,
},
{
title: "Stack",
value: `${payload.organization.githubLogin}/${payload.stackName}`,
short: true,
},
{
title: "Resource Changes",
value: Object.keys(payload.resourceChanges)
.map(key => `• ${titleCase(key)}: ${payload.resourceChanges[key]}`)
.join("\n"),
short: true,
},
{
title: "Kind",
value: titleCase(kind.replace("stack_", "")),
short: true,
},
{
title: "Permalink",
value: payload.updateUrl,
short: false,
},
],
},
];
return args;
}

function resultColor(result: string): string {
switch (result) {
case "succeeded":
return "#36a64f";
case "failed":
return "#e01563";
}
return "#e9a820";
}

function resultEmoji(result: string): string {
switch (result) {
case "succeeded":
return ":tropical_drink:";
case "failed":
return ":rotating_light:";
}
return "";
}

function titleCase(s: string): string {
return [s.charAt(0).toUpperCase(), s.substr(1)].join("");
}

0 comments on commit cde3ef8

Please sign in to comment.