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

Adds aws-ts-lambda-slack example #1709

Merged
merged 4 commits into from
Sep 26, 2024
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
3 changes: 3 additions & 0 deletions aws-ts-lambda-slack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/bin/
/node_modules/
Pulumi.dev.yaml
11 changes: 11 additions & 0 deletions aws-ts-lambda-slack/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: ${PROJECT}
description: ${DESCRIPTION}
runtime: nodejs

template:
description: Deploy a Slack webhook Lambda function.
config:
aws:region:
description: AWS Region
default: us-west-2

51 changes: 51 additions & 0 deletions aws-ts-lambda-slack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# AWS Lambda for Slack Notification

A Pulumi example to:

- Creates an AWS Lambda function to post a message on Slack via a Webhook URL.
- Adds an AWS API Gateway so the Lambda can be invoked externally, e.g, via GitHub Webhooks.
- Uses a Pulumi ESC Environment to dynamically retrieve AWS OIDC Credentials and the Slack URL from AWS Secrets Manager.

Last update: September 2024

## 📋 Pre-requisites

- AWS OIDC configured in an Pulumi ESC Environment
- AWS Secrets Manager with a Slack Webhook URL secret
- A properly configured Slack Webhook URL
- [Pulumi CLI](https://www.pulumi.com/docs/get-started/install/)
- [Pulumi Cloud account](https://app.pulumi.com/signup)
- [npm](https://www.npmjs.com/get-npm)

## 👩‍🏫 Get started

This Pulumi example is written as a template. It is meant to be copied via `pulumi new`

```bash
# login to your Pulumi Cloud if you haven't already
$ pulumi login

# create a new dir and cd to it
$ mkdir my-slack-demo
$ cd my-slack-demo

# start your pulumi project
$ pulumi new https://github.com/pulumi/examples/aws-ts-lambda-slack
```

```bash
# Add your Pulumi ESC Environment
$ pulumi config env add YOUR_ESC_ENV --yes --non-interactive
$ pulumi up
# select 'yes' to confirm the expected changes
# 🎉 Ta-Da!
```

## 🧹 Clean up

To clean up your infrastructure, run:

```bash
$ pulumi destroy
# select 'yes' to confirm the expected changes
```
96 changes: 96 additions & 0 deletions aws-ts-lambda-slack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@

// Copyright 2024, Pulumi Corporation. All rights reserved.

import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();
const slackWebhookUrl = config.requireSecret("slackWebhookUrl");

// Create an IAM role for the Lambda function
const lambdaRole = new aws.iam.Role("lambdaRole", {
assumeRolePolicy: {
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Principal: {
Service: "lambda.amazonaws.com",
},
Effect: "Allow",
}],
},
});

// Attach a policy to the role to allow Lambda to log to CloudWatch
const rpa = new aws.iam.RolePolicyAttachment("lambdaRolePolicy", {
role: lambdaRole.name,
policyArn: aws.iam.ManagedPolicies.AWSLambdaBasicExecutionRole,
});

// Create the Lambda function
const lambdaFunction = new aws.lambda.Function("myLambda", {
// https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-supported
runtime: "nodejs20.x",
role: lambdaRole.arn,
handler: "index.handler",
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./lambda"),
}),
environment: {
variables: {
"SLACK_WEBHOOK_URL": slackWebhookUrl,
},
},
memorySize: 128,
timeout: 30,
tags: {
"Environment": "dev",
},
});

// Export the Lambda function name
// export const lambdaFunctionName = lambdaFunction.name;

// Create an API Gateway
const api = new aws.apigateway.RestApi("myApi", {
description: "API Gateway for Lambda function",
});


// Create a root resource
const rootResource = api.rootResourceId;

// Create a method for the root resource
const rootMethod = new aws.apigateway.Method("rootMethod", {
restApi: api.id,
resourceId: rootResource,
httpMethod: "ANY",
authorization: "NONE",
});

// Integrate the Lambda function with the root method
const rootIntegration = new aws.apigateway.Integration("rootIntegration", {
restApi: api.id,
resourceId: rootResource,
httpMethod: rootMethod.httpMethod,
integrationHttpMethod: "POST",
type: "AWS_PROXY",
uri: lambdaFunction.invokeArn,
});

// Grant API Gateway permission to invoke the Lambda function
const lambdaPermission = new aws.lambda.Permission("apiGatewayPermission", {
action: "lambda:InvokeFunction",
function: lambdaFunction.arn,
principal: "apigateway.amazonaws.com",
sourceArn: pulumi.interpolate`${api.executionArn}/*/*`,
});

// Deploy the API
const deployment = new aws.apigateway.Deployment("myDeployment", {
restApi: api.id,
stageName: "dev",
}, { dependsOn: [rootIntegration] });

// Export the URL of the API
export const url = pulumi.interpolate`${deployment.invokeUrl}`;
53 changes: 53 additions & 0 deletions aws-ts-lambda-slack/lambda/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const https = require('https');

exports.handler = async (event) => {
// Get the Slack webhook URL from environment variables
const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;

// Define the message payload
const slackMessage = JSON.stringify({
text: event.body
});

// Slack Webhook URL parsed
const url = new URL(slackWebhookUrl);

const options = {
hostname: url.hostname,
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': slackMessage.length
}
};

// Create a promise to post the message
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let response = '';

res.on('data', (chunk) => {
response += chunk;
});

res.on('end', () => {
resolve({
statusCode: res.statusCode,
body: response
});
});
});

req.on('error', (error) => {
reject({
statusCode: 500,
body: JSON.stringify(error)
});
});

// Send the Slack message
req.write(slackMessage);
req.end();
});
};
12 changes: 12 additions & 0 deletions aws-ts-lambda-slack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "aws-ts-lambda-slack",
"main": "index.ts",
"devDependencies": {
"@types/node": "^22",
"typescript": "^5.6.2"
},
"dependencies": {
"@pulumi/aws": "^6.52.0",
"@pulumi/pulumi": "^3.133.0"
}
}
18 changes: 18 additions & 0 deletions aws-ts-lambda-slack/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2020",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}
Loading