Skip to content

Commit ad96293

Browse files
committed
Move Lambda resources out of stack
1 parent aa70d27 commit ad96293

File tree

2 files changed

+161
-118
lines changed

2 files changed

+161
-118
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {Construct} from "constructs"
2+
import {LambdaFunction} from "../constructs/LambdaFunction"
3+
import {Role, PolicyStatement} from "aws-cdk-lib/aws-iam"
4+
import * as ssm from "aws-cdk-lib/aws-ssm"
5+
import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager"
6+
7+
const RAG_MODEL_ID = "anthropic.claude-3-sonnet-20240229-v1:0"
8+
const SLACK_SLASH_COMMAND = "/ask-eps"
9+
const BEDROCK_KB_DATA_SOURCE = "eps-assist-kb-ds"
10+
const LAMBDA_MEMORY_SIZE = "265"
11+
12+
export interface FunctionsProps {
13+
stackName: string
14+
version: string
15+
commitId: string
16+
logRetentionInDays: number
17+
logLevel: string
18+
createIndexFunctionRole: Role
19+
slackBotTokenParameter: ssm.StringParameter
20+
slackSigningSecretParameter: ssm.StringParameter
21+
guardrailId: string
22+
guardrailVersion: string
23+
collectionId: string
24+
knowledgeBaseId: string
25+
region: string
26+
account: string
27+
slackBotTokenSecret: secretsmanager.Secret
28+
slackBotSigningSecret: secretsmanager.Secret
29+
}
30+
31+
export class Functions extends Construct {
32+
public readonly functions: {[key: string]: LambdaFunction}
33+
34+
constructor(scope: Construct, id: string, props: FunctionsProps) {
35+
super(scope, id)
36+
37+
const createIndexFunction = new LambdaFunction(this, "CreateIndexFunction", {
38+
stackName: props.stackName,
39+
functionName: `${props.stackName}-CreateIndexFunction`,
40+
packageBasePath: "packages/createIndexFunction",
41+
entryPoint: "app.py",
42+
logRetentionInDays: props.logRetentionInDays,
43+
logLevel: props.logLevel,
44+
environmentVariables: {"INDEX_NAME": props.collectionId},
45+
additionalPolicies: [],
46+
role: props.createIndexFunctionRole
47+
})
48+
49+
const slackBotLambda = new LambdaFunction(this, "SlackBotLambda", {
50+
stackName: props.stackName,
51+
functionName: `${props.stackName}-SlackBotFunction`,
52+
packageBasePath: "packages/slackBotFunction",
53+
entryPoint: "app.py",
54+
logRetentionInDays: props.logRetentionInDays,
55+
logLevel: props.logLevel,
56+
additionalPolicies: [],
57+
environmentVariables: {
58+
"RAG_MODEL_ID": RAG_MODEL_ID,
59+
"SLACK_SLASH_COMMAND": SLACK_SLASH_COMMAND,
60+
"KNOWLEDGEBASE_ID": props.knowledgeBaseId,
61+
"BEDROCK_KB_DATA_SOURCE": BEDROCK_KB_DATA_SOURCE,
62+
"LAMBDA_MEMORY_SIZE": LAMBDA_MEMORY_SIZE,
63+
"SLACK_BOT_TOKEN_PARAMETER": props.slackBotTokenParameter.parameterName,
64+
"SLACK_SIGNING_SECRET_PARAMETER": props.slackSigningSecretParameter.parameterName,
65+
"GUARD_RAIL_ID": props.guardrailId,
66+
"GUARD_RAIL_VERSION": props.guardrailVersion
67+
}
68+
})
69+
70+
// Create Lambda policies
71+
const lambdaBedrockModelPolicy = new PolicyStatement()
72+
lambdaBedrockModelPolicy.addActions("bedrock:InvokeModel")
73+
lambdaBedrockModelPolicy.addResources(`arn:aws:bedrock:${props.region}::foundation-model/${RAG_MODEL_ID}`)
74+
75+
const lambdaBedrockKbPolicy = new PolicyStatement()
76+
lambdaBedrockKbPolicy.addActions("bedrock:Retrieve")
77+
lambdaBedrockKbPolicy.addActions("bedrock:RetrieveAndGenerate")
78+
lambdaBedrockKbPolicy.addResources(
79+
`arn:aws:bedrock:${props.region}:${props.account}:knowledge-base/${props.knowledgeBaseId}`
80+
)
81+
82+
const lambdaSSMPolicy = new PolicyStatement()
83+
lambdaSSMPolicy.addActions("ssm:GetParameter")
84+
lambdaSSMPolicy.addResources(
85+
`arn:aws:ssm:${props.region}:${props.account}:parameter${props.slackBotTokenParameter.parameterName}`)
86+
lambdaSSMPolicy.addResources(
87+
`arn:aws:ssm:${props.region}:${props.account}:parameter${props.slackSigningSecretParameter.parameterName}`)
88+
89+
const lambdaReinvokePolicy = new PolicyStatement()
90+
lambdaReinvokePolicy.addActions("lambda:InvokeFunction")
91+
lambdaReinvokePolicy.addResources(`arn:aws:lambda:${props.region}:${props.account}:function:*`)
92+
93+
const lambdaGRinvokePolicy = new PolicyStatement()
94+
lambdaGRinvokePolicy.addActions("bedrock:ApplyGuardrail")
95+
lambdaGRinvokePolicy.addResources(`arn:aws:bedrock:${props.region}:${props.account}:guardrail/*`)
96+
97+
// Grant secrets access and attach policies
98+
props.slackBotTokenSecret.grantRead(slackBotLambda.function)
99+
props.slackBotSigningSecret.grantRead(slackBotLambda.function)
100+
101+
slackBotLambda.function.addToRolePolicy(lambdaBedrockModelPolicy)
102+
slackBotLambda.function.addToRolePolicy(lambdaBedrockKbPolicy)
103+
slackBotLambda.function.addToRolePolicy(lambdaReinvokePolicy)
104+
slackBotLambda.function.addToRolePolicy(lambdaGRinvokePolicy)
105+
slackBotLambda.function.addToRolePolicy(lambdaSSMPolicy)
106+
107+
this.functions = {
108+
createIndex: createIndexFunction,
109+
slackBot: slackBotLambda
110+
}
111+
}
112+
}

packages/cdk/stacks/EpsAssistMeStack.ts

Lines changed: 49 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
CfnKnowledgeBase,
1919
CfnDataSource
2020
} from "aws-cdk-lib/aws-bedrock"
21-
import {LambdaFunction} from "../constructs/LambdaFunction"
2221
import {PolicyStatement} from "aws-cdk-lib/aws-iam"
2322
import * as cdk from "aws-cdk-lib"
2423
import * as iam from "aws-cdk-lib/aws-iam"
@@ -28,15 +27,12 @@ import * as ssm from "aws-cdk-lib/aws-ssm"
2827
import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager"
2928
import {nagSuppressions} from "../nagSuppressions"
3029
import {Apis} from "../resources/Apis"
30+
import {Functions} from "../resources/Functions"
3131

32-
const RAG_MODEL_ID = "anthropic.claude-3-sonnet-20240229-v1:0"
3332
const EMBEDDING_MODEL = "amazon.titan-embed-text-v2:0"
34-
const SLACK_SLASH_COMMAND = "/ask-eps"
3533
const COLLECTION_NAME = "eps-assist-vector-db"
3634
const VECTOR_INDEX_NAME = "eps-assist-os-index"
3735
const BEDROCK_KB_NAME = "eps-assist-kb"
38-
const BEDROCK_KB_DATA_SOURCE = "eps-assist-kb-ds"
39-
const LAMBDA_MEMORY_SIZE = "265"
4036

4137
export interface EpsAssistMeStackProps extends StackProps {
4238
readonly stackName: string
@@ -281,17 +277,51 @@ export class EpsAssistMeStack extends Stack {
281277
effect: iam.Effect.ALLOW
282278
}))
283279

284-
// Define a lambda function to create an opensearch serverless index
285-
const createIndexFunction = new LambdaFunction(this, "CreateIndexFunction", {
280+
const endpoint = `${osCollection.attrId}.${region}.aoss.amazonaws.com`
281+
282+
// Define a Bedrock knowledge base with type opensearch serverless and titan for embedding model
283+
const bedrockkb = new CfnKnowledgeBase(this, "EpsKb", {
284+
name: BEDROCK_KB_NAME,
285+
description: "EPS Assist Knowledge Base",
286+
roleArn: bedrockExecutionRole.roleArn,
287+
knowledgeBaseConfiguration: {
288+
type: "VECTOR",
289+
vectorKnowledgeBaseConfiguration: {
290+
embeddingModelArn: `arn:aws:bedrock:${region}::foundation-model/${EMBEDDING_MODEL}`
291+
}
292+
},
293+
storageConfiguration: {
294+
type: "OPENSEARCH_SERVERLESS",
295+
opensearchServerlessConfiguration: {
296+
collectionArn: osCollection.attrArn,
297+
fieldMapping: {
298+
vectorField: "bedrock-knowledge-base-default-vector",
299+
textField: "AMAZON_BEDROCK_TEXT_CHUNK",
300+
metadataField: "AMAZON_BEDROCK_METADATA"
301+
},
302+
vectorIndexName: VECTOR_INDEX_NAME
303+
}
304+
}
305+
})
306+
307+
// Create Functions construct
308+
const functions = new Functions(this, "Functions", {
286309
stackName: props.stackName,
287-
functionName: `${props.stackName}-CreateIndexFunction`,
288-
packageBasePath: "packages/createIndexFunction",
289-
entryPoint: "app.py",
310+
version: props.version,
311+
commitId: props.commitId,
290312
logRetentionInDays,
291313
logLevel,
292-
environmentVariables: {"INDEX_NAME": osCollection.attrId},
293-
additionalPolicies: [],
294-
role: createIndexFunctionRole
314+
createIndexFunctionRole,
315+
slackBotTokenParameter,
316+
slackSigningSecretParameter,
317+
guardrailId: GUARD_RAIL_ID,
318+
guardrailVersion: GUARD_RAIL_VERSION,
319+
collectionId: osCollection.attrId,
320+
knowledgeBaseId: bedrockkb.attrKnowledgeBaseId,
321+
region,
322+
account,
323+
slackBotTokenSecret,
324+
slackBotSigningSecret
295325
})
296326

297327
// Define OpenSearchServerless access policy to access the index and collection
@@ -307,23 +337,20 @@ export class EpsAssistMeStack extends Stack {
307337
// Add principal of bedrock execution role and lambda execution role
308338
Principal: [
309339
bedrockExecutionRole.roleArn,
310-
createIndexFunction.function.role?.roleArn,
340+
functions.functions.createIndex.function.role?.roleArn,
311341
`arn:aws:iam::${account}:root`
312342
]
313343
}])
314344
})
315-
//this.serverlessCollection = osCollection;
316345
osCollection.addDependency(aossAccessPolicy)
317346

318-
const endpoint = `${osCollection.attrId}.${region}.aoss.amazonaws.com`
319-
320347
const vectorIndex = new cr.AwsCustomResource(this, "VectorIndex", {
321348
installLatestAwsSdk: true,
322349
onCreate: {
323350
service: "Lambda",
324351
action: "invoke",
325352
parameters: {
326-
FunctionName: createIndexFunction.function.functionName,
353+
FunctionName: functions.functions.createIndex.function.functionName,
327354
InvocationType: "RequestResponse",
328355
Payload: JSON.stringify({
329356
RequestType: "Create",
@@ -338,7 +365,7 @@ export class EpsAssistMeStack extends Stack {
338365
service: "Lambda",
339366
action: "invoke",
340367
parameters: {
341-
FunctionName: createIndexFunction.function.functionName,
368+
FunctionName: functions.functions.createIndex.function.functionName,
342369
InvocationType: "RequestResponse",
343370
Payload: JSON.stringify({
344371
RequestType: "Delete",
@@ -347,12 +374,11 @@ export class EpsAssistMeStack extends Stack {
347374
Endpoint: endpoint
348375
})
349376
}
350-
//physicalResourceId: cr.PhysicalResourceId.of('vectorIndexResource'),
351377
},
352378
policy: cr.AwsCustomResourcePolicy.fromStatements([
353379
new iam.PolicyStatement({
354380
actions: ["lambda:InvokeFunction"],
355-
resources: [createIndexFunction.function.functionArn]
381+
resources: [functions.functions.createIndex.function.functionArn]
356382
})
357383
]),
358384
timeout: cdk.Duration.seconds(60)
@@ -361,33 +387,9 @@ export class EpsAssistMeStack extends Stack {
361387
// Ensure vectorIndex depends on collection
362388
vectorIndex.node.addDependency(osCollection)
363389

364-
// Define a Bedrock knowledge base with type opensearch serverless and titan for embedding model
365-
const bedrockkb = new CfnKnowledgeBase(this, "EpsKb", {
366-
name: BEDROCK_KB_NAME,
367-
description: "EPS Assist Knowledge Base",
368-
roleArn: bedrockExecutionRole.roleArn,
369-
knowledgeBaseConfiguration: {
370-
type: "VECTOR",
371-
vectorKnowledgeBaseConfiguration: {
372-
embeddingModelArn: `arn:aws:bedrock:${region}::foundation-model/${EMBEDDING_MODEL}`
373-
}
374-
},
375-
storageConfiguration: {
376-
type: "OPENSEARCH_SERVERLESS",
377-
opensearchServerlessConfiguration: {
378-
collectionArn: osCollection.attrArn,
379-
fieldMapping: {
380-
vectorField: "bedrock-knowledge-base-default-vector",
381-
textField: "AMAZON_BEDROCK_TEXT_CHUNK",
382-
metadataField: "AMAZON_BEDROCK_METADATA"
383-
},
384-
vectorIndexName: VECTOR_INDEX_NAME
385-
}
386-
}
387-
})
388390
// add a dependency for bedrock kb on the custom resource. Enables vector index to be created before KB
389391
bedrockkb.node.addDependency(vectorIndex)
390-
bedrockkb.node.addDependency(createIndexFunction)
392+
bedrockkb.node.addDependency(functions.functions.createIndex)
391393
bedrockkb.node.addDependency(osCollection)
392394
bedrockkb.node.addDependency(bedrockExecutionRole)
393395

@@ -403,84 +405,13 @@ export class EpsAssistMeStack extends Stack {
403405
}
404406
})
405407

406-
// Create an IAM policy to allow the lambda to invoke models in Amazon Bedrock
407-
const lambdaBedrockModelPolicy = new PolicyStatement()
408-
lambdaBedrockModelPolicy.addActions("bedrock:InvokeModel")
409-
lambdaBedrockModelPolicy.addResources(`arn:aws:bedrock:${region}::foundation-model/${RAG_MODEL_ID}`)
410-
411-
// Create an IAM policy to allow the lambda to call Retrieve and Retrieve and Generate on a Bedrock Knowledge Base
412-
const lambdaBedrockKbPolicy = new PolicyStatement()
413-
lambdaBedrockKbPolicy.addActions("bedrock:Retrieve")
414-
lambdaBedrockKbPolicy.addActions("bedrock:RetrieveAndGenerate")
415-
lambdaBedrockKbPolicy.addResources(
416-
`arn:aws:bedrock:${region}:${account}:knowledge-base/${bedrockkb.attrKnowledgeBaseId}`
417-
)
418-
419-
// Create an IAM policy to allow the lambda to call SSM
420-
const lambdaSSMPolicy = new PolicyStatement()
421-
lambdaSSMPolicy.addActions("ssm:GetParameter")
422-
//lambdaSSMPolicy.addActions("ssm:GetParameters");
423-
// lambdaSSMPolicy.addResources("slackBotTokenParameter.parameterArn");
424-
// lambdaSSMPolicy.addResources("slackBotSigningSecret.parameterArn");
425-
lambdaSSMPolicy.addResources(
426-
`arn:aws:ssm:${region}:${account}:parameter${slackBotTokenParameter.parameterName}`)
427-
lambdaSSMPolicy.addResources(
428-
`arn:aws:ssm:${region}:${account}:parameter${slackSigningSecretParameter.parameterName}`)
429-
430-
//arn:aws:ssm:us-east-1:859498851685:parameter/slack/bot-token/parameter
431-
//"arn:aws:ssm:us-east-2:123456789012:parameter/prod-*"
432-
//(`arn:aws:bedrock:${region}:${account}:knowledge-base/${bedrockkb.attrKnowledgeBaseId}`);
433-
434-
const lambdaReinvokePolicy = new PolicyStatement()
435-
lambdaReinvokePolicy.addActions("lambda:InvokeFunction")
436-
lambdaReinvokePolicy.addResources(`arn:aws:lambda:${region}:${account}:function:*`)
437-
438-
const lambdaGRinvokePolicy = new PolicyStatement()
439-
lambdaGRinvokePolicy.addActions("bedrock:ApplyGuardrail")
440-
lambdaGRinvokePolicy.addResources(`arn:aws:bedrock:${region}:${account}:guardrail/*`)
441-
442-
// Create the SlackBot (slash command) integration to Amazon Bedrock Knowledge base responses.
443-
const slackBotLambda = new LambdaFunction(this, "SlackBotLambda", {
444-
stackName: props.stackName,
445-
functionName: `${props.stackName}-SlackBotFunction`,
446-
packageBasePath: "packages/slackBotFunction",
447-
entryPoint: "app.py",
448-
logRetentionInDays,
449-
logLevel,
450-
additionalPolicies: [],
451-
environmentVariables: {
452-
"RAG_MODEL_ID": RAG_MODEL_ID,
453-
"SLACK_SLASH_COMMAND": SLACK_SLASH_COMMAND,
454-
"KNOWLEDGEBASE_ID": bedrockkb.attrKnowledgeBaseId,
455-
"BEDROCK_KB_DATA_SOURCE": BEDROCK_KB_DATA_SOURCE,
456-
"LAMBDA_MEMORY_SIZE": LAMBDA_MEMORY_SIZE,
457-
// "SLACK_BOT_TOKEN": SLACK_BOT_TOKEN,
458-
// "SLACK_SIGNING_SECRET": SLACK_SIGNING_SECRET,
459-
"SLACK_BOT_TOKEN_PARAMETER": slackBotTokenParameter.parameterName,
460-
"SLACK_SIGNING_SECRET_PARAMETER": slackSigningSecretParameter.parameterName,
461-
"GUARD_RAIL_ID": GUARD_RAIL_ID,
462-
"GUARD_RAIL_VERSION": GUARD_RAIL_VERSION
463-
}
464-
})
465-
466-
// Grant the Lambda function permission to read the secrets
467-
slackBotTokenSecret.grantRead(slackBotLambda.function)
468-
slackBotSigningSecret.grantRead(slackBotLambda.function)
469-
470-
// Attach listed IAM policies to the Lambda functions Execution role
471-
slackBotLambda.function.addToRolePolicy(lambdaBedrockModelPolicy)
472-
slackBotLambda.function.addToRolePolicy(lambdaBedrockKbPolicy)
473-
slackBotLambda.function.addToRolePolicy(lambdaReinvokePolicy)
474-
slackBotLambda.function.addToRolePolicy(lambdaGRinvokePolicy)
475-
slackBotLambda.function.addToRolePolicy(lambdaSSMPolicy)
476-
477408
// Create Apis and pass the Lambda function
478409
const apis = new Apis(this, "Apis", {
479410
stackName: props.stackName,
480411
logRetentionInDays,
481412
enableMutalTls: false,
482413
functions: {
483-
slackBot: slackBotLambda
414+
slackBot: functions.functions.slackBot
484415
}
485416
})
486417

0 commit comments

Comments
 (0)