Skip to content

Commit

Permalink
Move examples more off of 'cloud' onto aws/awsx. (pulumi#354)
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Aug 1, 2019
1 parent 52dccd4 commit 733582d
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 48 deletions.
93 changes: 53 additions & 40 deletions aws-ts-pulumi-webhooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.

import * as pulumi from "@pulumi/pulumi";
import * as cloud from "@pulumi/cloud";
import * as awsx from "@pulumi/awsx";

import * as crypto from "crypto";

Expand All @@ -18,62 +20,73 @@ const stackConfig = {
slackChannel: config.require("slackChannel"),
};

// Just logs information aincomming webhook request.
async function logRequest(req: cloud.Request, _: cloud.Response, next: () => void) {
// Just logs information from an incoming webhook request.
function logRequest(req: awsx.apigateway.Request) {
const webhookID = req.headers["pulumi-webhook-id"];
const webhookKind = req.headers["pulumi-webhook-kind"];
console.log(`Received webhook from Pulumi ${webhookID} [${webhookKind}]`);
next();
}

// Webhooks can optionally be configured with a shared secret, so that webhook handlers like this app can authenticate
// message integrity. Rejects any incomming requests that don't have a valid "pulumi-webhook-signature" header.
async function authenticateRequest(req: cloud.Request, res: cloud.Response, next: () => void) {
const webhookSig = req.headers["pulumi-webhook-signature"] as string; // headers[] returns (string | string[]).
// message integrity. Rejects any incoming requests that don't have a valid "pulumi-webhook-signature" header.
function authenticateRequest(req: awsx.apigateway.Request): awsx.apigateway.Response | undefined {
const webhookSig = req.headers["pulumi-webhook-signature"];
if (!stackConfig.sharedSecret || !webhookSig) {
next();
return;
return undefined;
}

const payload = req.body.toString();
const payload = req.body!.toString();
const hmacAlg = crypto.createHmac("sha256", stackConfig.sharedSecret);
const hmac = hmacAlg.update(payload).digest("hex");

const result = crypto.timingSafeEqual(Buffer.from(webhookSig), Buffer.from(hmac));
if (!result) {
console.log(`Mismatch between expected signature and HMAC: '${webhookSig}' vs. '${hmac}'.`);
res.status(400).end("Unable to authenticate message: Mismatch between signature and HMAC");
return;
}
next();
}

const webhookHandler = new cloud.HttpEndpoint("pulumi-webhook-handler");

webhookHandler.get("/", async (_, res) => {
res.status(200).end("🍹 Pulumi Webhook Responder🍹\n");
});

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

const client = new slack.WebClient(stackConfig.slackToken);

const fallbackText = `Pulumi Service Webhook (\`${webhookKind}\`)\n` + "```\n" + prettyPrintedPayload + "```\n";
const messageArgs: slack.ChatPostMessageArguments = {
channel: stackConfig.slackChannel,
text: fallbackText,
as_user: true,
return { statusCode: 400, body: "Unable to authenticate message: Mismatch between signature and HMAC" };
}

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

await client.chat.postMessage(formattedMessageArgs);
res.status(200).end(`posted to Slack channel ${stackConfig.slackChannel}\n`);
const webhookHandler = new awsx.apigateway.API("pulumi-webhook-handler", {
routes: [{
path: "/",
method: "GET",
eventHandler: async () => ({
statusCode: 200,
body: "🍹 Pulumi Webhook Responder🍹\n"
}),
}, {
path: "/",
method: "POST",
eventHandler: async (req) => {
logRequest(req);
const authenticateResult = authenticateRequest(req);
if (authenticateResult) {
return authenticateResult;
}

const webhookKind = req.headers["pulumi-webhook-kind"];
const payload = req.body!.toString();
const parsedPayload = JSON.parse(payload);
const prettyPrintedPayload = JSON.stringify(parsedPayload, null, 2);

const client = new slack.WebClient(stackConfig.slackToken);

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, parsedPayload, messageArgs);

await client.chat.postMessage(formattedMessageArgs);
return { statusCode: 200, body: `posted to Slack channel ${stackConfig.slackChannel}\n` };
},
}],
});

export const url = webhookHandler.publish().url;
export const url = webhookHandler.url;
3 changes: 2 additions & 1 deletion aws-ts-pulumi-webhooks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
"@types/node": "latest"
},
"dependencies": {
"@pulumi/cloud-aws": "latest",
"@pulumi/pulumi": "latest",
"@pulumi/aws": "latest",
"@pulumi/awsx": "latest",
"@slack/client": "latest"
}
}
51 changes: 44 additions & 7 deletions aws-ts-url-shortener-cache-http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
import * as cloud from "@pulumi/cloud-aws";
import * as cache from "./cache";
import * as express from "express";
import * as fs from "fs";
import * as mime from "mime-types";

type AsyncRequestHandler = (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<void>;

Expand All @@ -18,7 +15,36 @@ const asyncMiddleware = (fn: AsyncRequestHandler) => {
}

// Create a table `urls`, with `name` as primary key.
let urlTable = new cloud.Table("urls", "name");
let urlTable = new aws.dynamodb.Table("urls", {
attributes: [{
name: "name",
type: "S",
}],
hashKey: "name",
billingMode: "PAY_PER_REQUEST",
})

async function scanTable() {
const items: any[] = [];
const db = new aws.sdk.DynamoDB.DocumentClient();
let params = {
TableName: urlTable.name.get(),
ConsistentRead: true,
ExclusiveStartKey: undefined,
};

do {
const result = await db.scan(params).promise();
if (result.Items) {
items.push(...result.Items);
}

params.ExclusiveStartKey = <any>result.LastEvaluatedKey;
}
while (params.ExclusiveStartKey !== undefined);

return items;
}

// Create a cache of frequently accessed urls.
let urlCache = new cache.Cache("urlcache");
Expand All @@ -30,7 +56,7 @@ let httpServer = new cloud.HttpServer("urlshortener", () => {
// GET /url lists all URLs currently registered.
app.get("/url", asyncMiddleware(async (req, res) => {
try {
let items = await urlTable.scan();
let items = await scanTable();
res.status(200).json(items);
console.log(`GET /url retrieved ${items.length} items`);
} catch (err) {
Expand All @@ -51,7 +77,14 @@ let httpServer = new cloud.HttpServer("urlshortener", () => {
}
else {
// If we didn't find it in the cache, consult the table.
let value = await urlTable.get({name});
const db = new aws.sdk.DynamoDB.DocumentClient();
const result = await db.get({
TableName: urlTable.name.get(),
Key: {name},
ConsistentRead: true,
}).promise();

let value = result.Item;
url = value && value.url;
if (url) {
urlCache.set(name, url); // cache it for next time.
Expand Down Expand Up @@ -81,7 +114,11 @@ let httpServer = new cloud.HttpServer("urlshortener", () => {
const url = <string>req.query["url"];
const name = <string>req.query["name"];
try {
await urlTable.insert({ name, url });
const db = new aws.sdk.DynamoDB.DocumentClient();
await db.put({
TableName: urlTable.name.get(),
Item: { name, url },
}).promise();
await urlCache.set(name, url);
res.json({ shortenedURLName: name });
console.log(`POST /url/${name} => ${url}`);
Expand Down

0 comments on commit 733582d

Please sign in to comment.