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 3 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
2 changes: 2 additions & 0 deletions aws-ts-lambda-slack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin/
/node_modules/
10 changes: 10 additions & 0 deletions aws-ts-lambda-slack/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: aws-ts-lambda-slack
runtime:
name: nodejs
options:
packagemanager: npm
description: A minimal TypeScript Pulumi program
config:
pulumi:tags:
value:
pulumi:template: typescript
40 changes: 40 additions & 0 deletions aws-ts-lambda-slack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 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

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

```bash
$ 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
```
98 changes: 98 additions & 0 deletions aws-ts-lambda-slack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@

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

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

// 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,
});

const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
desteves marked this conversation as resolved.
Show resolved Hide resolved
if (!slackWebhookUrl) {
throw new Error("SLACK_WEBHOOK_URL environment variable is required");
}

// 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