From 4e1fff4101e5411a79441a61f5728146ff3a8189 Mon Sep 17 00:00:00 2001 From: diana esteves Date: Mon, 23 Sep 2024 12:05:24 -0500 Subject: [PATCH 1/4] init --- aws-ts-lambda-github/.gitignore | 2 ++ aws-ts-lambda-github/Pulumi.yaml | 10 ++++++++++ aws-ts-lambda-github/index.ts | 1 + aws-ts-lambda-github/package.json | 11 +++++++++++ aws-ts-lambda-github/tsconfig.json | 18 ++++++++++++++++++ 5 files changed, 42 insertions(+) create mode 100644 aws-ts-lambda-github/.gitignore create mode 100644 aws-ts-lambda-github/Pulumi.yaml create mode 100644 aws-ts-lambda-github/index.ts create mode 100644 aws-ts-lambda-github/package.json create mode 100644 aws-ts-lambda-github/tsconfig.json diff --git a/aws-ts-lambda-github/.gitignore b/aws-ts-lambda-github/.gitignore new file mode 100644 index 000000000..c6958891d --- /dev/null +++ b/aws-ts-lambda-github/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/node_modules/ diff --git a/aws-ts-lambda-github/Pulumi.yaml b/aws-ts-lambda-github/Pulumi.yaml new file mode 100644 index 000000000..635c68893 --- /dev/null +++ b/aws-ts-lambda-github/Pulumi.yaml @@ -0,0 +1,10 @@ +name: aws-ts-lambda-github +runtime: + name: nodejs + options: + packagemanager: npm +description: A minimal TypeScript Pulumi program +config: + pulumi:tags: + value: + pulumi:template: typescript diff --git a/aws-ts-lambda-github/index.ts b/aws-ts-lambda-github/index.ts new file mode 100644 index 000000000..aa412e4fd --- /dev/null +++ b/aws-ts-lambda-github/index.ts @@ -0,0 +1 @@ +import * as pulumi from "@pulumi/pulumi"; diff --git a/aws-ts-lambda-github/package.json b/aws-ts-lambda-github/package.json new file mode 100644 index 000000000..45025776c --- /dev/null +++ b/aws-ts-lambda-github/package.json @@ -0,0 +1,11 @@ +{ + "name": "aws-ts-lambda-github", + "main": "index.ts", + "devDependencies": { + "@types/node": "^22", + "typescript": "^5.6.2" + }, + "dependencies": { + "@pulumi/pulumi": "^3.133.0" + } +} diff --git a/aws-ts-lambda-github/tsconfig.json b/aws-ts-lambda-github/tsconfig.json new file mode 100644 index 000000000..f960d5171 --- /dev/null +++ b/aws-ts-lambda-github/tsconfig.json @@ -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" + ] +} From dc50b396fa20c84911b9a252984c2af88a68aa22 Mon Sep 17 00:00:00 2001 From: diana esteves Date: Mon, 23 Sep 2024 15:27:26 -0500 Subject: [PATCH 2/4] tested with gha --- aws-ts-lambda-github/index.ts | 1 - .../.gitignore | 0 .../Pulumi.yaml | 2 +- aws-ts-lambda-slack/README.md | 40 ++++++++ aws-ts-lambda-slack/index.ts | 95 +++++++++++++++++++ aws-ts-lambda-slack/lambda/index.js | 53 +++++++++++ .../package.json | 3 +- .../tsconfig.json | 0 8 files changed, 191 insertions(+), 3 deletions(-) delete mode 100644 aws-ts-lambda-github/index.ts rename {aws-ts-lambda-github => aws-ts-lambda-slack}/.gitignore (100%) rename {aws-ts-lambda-github => aws-ts-lambda-slack}/Pulumi.yaml (86%) create mode 100644 aws-ts-lambda-slack/README.md create mode 100644 aws-ts-lambda-slack/index.ts create mode 100644 aws-ts-lambda-slack/lambda/index.js rename {aws-ts-lambda-github => aws-ts-lambda-slack}/package.json (72%) rename {aws-ts-lambda-github => aws-ts-lambda-slack}/tsconfig.json (100%) diff --git a/aws-ts-lambda-github/index.ts b/aws-ts-lambda-github/index.ts deleted file mode 100644 index aa412e4fd..000000000 --- a/aws-ts-lambda-github/index.ts +++ /dev/null @@ -1 +0,0 @@ -import * as pulumi from "@pulumi/pulumi"; diff --git a/aws-ts-lambda-github/.gitignore b/aws-ts-lambda-slack/.gitignore similarity index 100% rename from aws-ts-lambda-github/.gitignore rename to aws-ts-lambda-slack/.gitignore diff --git a/aws-ts-lambda-github/Pulumi.yaml b/aws-ts-lambda-slack/Pulumi.yaml similarity index 86% rename from aws-ts-lambda-github/Pulumi.yaml rename to aws-ts-lambda-slack/Pulumi.yaml index 635c68893..296c80e79 100644 --- a/aws-ts-lambda-github/Pulumi.yaml +++ b/aws-ts-lambda-slack/Pulumi.yaml @@ -1,4 +1,4 @@ -name: aws-ts-lambda-github +name: aws-ts-lambda-slack runtime: name: nodejs options: diff --git a/aws-ts-lambda-slack/README.md b/aws-ts-lambda-slack/README.md new file mode 100644 index 000000000..97ea339e3 --- /dev/null +++ b/aws-ts-lambda-slack/README.md @@ -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 +``` diff --git a/aws-ts-lambda-slack/index.ts b/aws-ts-lambda-slack/index.ts new file mode 100644 index 000000000..80ea24a93 --- /dev/null +++ b/aws-ts-lambda-slack/index.ts @@ -0,0 +1,95 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as aws from "@pulumi/aws"; + +// 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 +new aws.iam.RolePolicyAttachment("lambdaRolePolicy", { + role: lambdaRole.name, + policyArn: aws.iam.ManagedPolicies.AWSLambdaBasicExecutionRole, +}); + +const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL; +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}`; diff --git a/aws-ts-lambda-slack/lambda/index.js b/aws-ts-lambda-slack/lambda/index.js new file mode 100644 index 000000000..4e078938f --- /dev/null +++ b/aws-ts-lambda-slack/lambda/index.js @@ -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(); + }); +}; diff --git a/aws-ts-lambda-github/package.json b/aws-ts-lambda-slack/package.json similarity index 72% rename from aws-ts-lambda-github/package.json rename to aws-ts-lambda-slack/package.json index 45025776c..433ed82c2 100644 --- a/aws-ts-lambda-github/package.json +++ b/aws-ts-lambda-slack/package.json @@ -1,11 +1,12 @@ { - "name": "aws-ts-lambda-github", + "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" } } diff --git a/aws-ts-lambda-github/tsconfig.json b/aws-ts-lambda-slack/tsconfig.json similarity index 100% rename from aws-ts-lambda-github/tsconfig.json rename to aws-ts-lambda-slack/tsconfig.json From c417a05f49f3f7dbdc98c286472ced404e7421bd Mon Sep 17 00:00:00 2001 From: diana esteves Date: Mon, 23 Sep 2024 15:36:54 -0500 Subject: [PATCH 3/4] lint --- aws-ts-lambda-slack/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aws-ts-lambda-slack/index.ts b/aws-ts-lambda-slack/index.ts index 80ea24a93..f07dc4a0f 100644 --- a/aws-ts-lambda-slack/index.ts +++ b/aws-ts-lambda-slack/index.ts @@ -1,5 +1,8 @@ -import * as pulumi from "@pulumi/pulumi"; + +// 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", { @@ -16,7 +19,7 @@ const lambdaRole = new aws.iam.Role("lambdaRole", { }); // Attach a policy to the role to allow Lambda to log to CloudWatch -new aws.iam.RolePolicyAttachment("lambdaRolePolicy", { +const rpa = new aws.iam.RolePolicyAttachment("lambdaRolePolicy", { role: lambdaRole.name, policyArn: aws.iam.ManagedPolicies.AWSLambdaBasicExecutionRole, }); From 9d9dff377e8470ecb667e3e0cf1345bc841edcdf Mon Sep 17 00:00:00 2001 From: diana esteves Date: Mon, 23 Sep 2024 22:22:50 -0500 Subject: [PATCH 4/4] template --- aws-ts-lambda-slack/.gitignore | 1 + aws-ts-lambda-slack/Pulumi.yaml | 21 +++++++++++---------- aws-ts-lambda-slack/README.md | 11 +++++++++++ aws-ts-lambda-slack/index.ts | 8 +++----- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/aws-ts-lambda-slack/.gitignore b/aws-ts-lambda-slack/.gitignore index c6958891d..71a733f9d 100644 --- a/aws-ts-lambda-slack/.gitignore +++ b/aws-ts-lambda-slack/.gitignore @@ -1,2 +1,3 @@ /bin/ /node_modules/ +Pulumi.dev.yaml \ No newline at end of file diff --git a/aws-ts-lambda-slack/Pulumi.yaml b/aws-ts-lambda-slack/Pulumi.yaml index 296c80e79..a882648cd 100644 --- a/aws-ts-lambda-slack/Pulumi.yaml +++ b/aws-ts-lambda-slack/Pulumi.yaml @@ -1,10 +1,11 @@ -name: aws-ts-lambda-slack -runtime: - name: nodejs - options: - packagemanager: npm -description: A minimal TypeScript Pulumi program -config: - pulumi:tags: - value: - pulumi:template: typescript +name: ${PROJECT} +description: ${DESCRIPTION} +runtime: nodejs + +template: + description: Deploy a Slack webhook Lambda function. + config: + aws:region: + description: AWS Region + default: us-west-2 + \ No newline at end of file diff --git a/aws-ts-lambda-slack/README.md b/aws-ts-lambda-slack/README.md index 97ea339e3..3c71d8b9d 100644 --- a/aws-ts-lambda-slack/README.md +++ b/aws-ts-lambda-slack/README.md @@ -19,12 +19,23 @@ Last update: September 2024 ## ๐Ÿ‘ฉโ€๐Ÿซ 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! diff --git a/aws-ts-lambda-slack/index.ts b/aws-ts-lambda-slack/index.ts index f07dc4a0f..e7f3fabd0 100644 --- a/aws-ts-lambda-slack/index.ts +++ b/aws-ts-lambda-slack/index.ts @@ -4,6 +4,9 @@ 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: { @@ -24,11 +27,6 @@ const rpa = new aws.iam.RolePolicyAttachment("lambdaRolePolicy", { policyArn: aws.iam.ManagedPolicies.AWSLambdaBasicExecutionRole, }); -const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL; -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