From eff6d644a28e2ddfbcfb82cf0e85723616012fd1 Mon Sep 17 00:00:00 2001 From: Matthew Bonig Date: Tue, 23 Jun 2020 17:38:29 -0600 Subject: [PATCH] Adding ability to provide a dynamodb table. --- .../aws-lambda-dynamodb/lib/index.ts | 23 +- .../integ.use-existing-table.expected.json | 396 ++++++++++++++++++ .../test/integ.use-existing-table.ts | 35 ++ .../test/lambda-dynamodb.test.ts | 74 +++- 4 files changed, 508 insertions(+), 20 deletions(-) create mode 100644 source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-table.expected.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-table.ts diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/lib/index.ts index 82dedfabc..5650f8e39 100644 --- a/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/lib/index.ts @@ -42,6 +42,13 @@ export interface LambdaToDynamoDBProps { * @default - Default props are used */ readonly lambdaFunctionProps?: lambda.FunctionProps, + /** + * Existing instance of dynamodb table object. + * If this is set then the dynamoTableProps is ignore. + * + * @default - None + */ + readonly existingTableObj?: dynamodb.Table, /** * Optional user provided props to override the default props * @@ -71,14 +78,20 @@ export class LambdaToDynamoDB extends Construct { lambdaFunctionProps: props.lambdaFunctionProps }); - // Set the default props for DynamoDB table - if (props.dynamoTableProps) { - const dynamoTableProps = overrideProps(defaults.DefaultTableProps, props.dynamoTableProps); - this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', dynamoTableProps); + + if (!props.existingTableObj) { + // Set the default props for DynamoDB table + if (props.dynamoTableProps) { + const dynamoTableProps = overrideProps(defaults.DefaultTableProps, props.dynamoTableProps); + this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', dynamoTableProps); + } else { + this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableProps); + } } else { - this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableProps); + this.dynamoTable = props.existingTableObj; } + this.lambdaFunction.addEnvironment('DDB_TABLE_NAME', this.dynamoTable.tableName); this.dynamoTable.grantReadWriteData(this.lambdaFunction.grantPrincipal); diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-table.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-table.expected.json new file mode 100644 index 000000000..b696ade6e --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-table.expected.json @@ -0,0 +1,396 @@ +{ + "Resources": { + "existingtableE51CCC93": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "pk", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "pk", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "aws:cdk:path": "test-lambda-dynamodb-stack/existing-table/Resource" + } + }, + "testlambdadynamodbstackLambdaFunctionServiceRole758347A1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRolePolicy" + } + ] + }, + "Metadata": { + "aws:cdk:path": "test-lambda-dynamodb-stack/test-lambda-dynamodb-stack/LambdaFunctionServiceRole/Resource" + } + }, + "testlambdadynamodbstackLambdaFunctionServiceRoleDefaultPolicy547FB7F4": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "existingtableE51CCC93", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdadynamodbstackLambdaFunctionServiceRoleDefaultPolicy547FB7F4", + "Roles": [ + { + "Ref": "testlambdadynamodbstackLambdaFunctionServiceRole758347A1" + } + ] + }, + "Metadata": { + "aws:cdk:path": "test-lambda-dynamodb-stack/test-lambda-dynamodb-stack/LambdaFunctionServiceRole/DefaultPolicy/Resource" + } + }, + "testlambdadynamodbstackLambdaFunction5DDB3E8D": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "testlambdadynamodbstackLambdaFunctionServiceRole758347A1", + "Arn" + ] + }, + "Runtime": "python3.6", + "Environment": { + "Variables": { + "DDB_TABLE_NAME": { + "Ref": "existingtableE51CCC93" + } + } + } + }, + "DependsOn": [ + "testlambdadynamodbstackLambdaFunctionServiceRoleDefaultPolicy547FB7F4", + "testlambdadynamodbstackLambdaFunctionServiceRole758347A1" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with more tighter permissions." + } + ] + } + } + }, + "CDKMetadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Modules": "aws-cdk=1.46.0,@aws-cdk/assets=1.46.0,@aws-cdk/aws-apigateway=1.46.0,@aws-cdk/aws-applicationautoscaling=1.46.0,@aws-cdk/aws-autoscaling-common=1.46.0,@aws-cdk/aws-certificatemanager=1.46.0,@aws-cdk/aws-cloudfront=1.46.0,@aws-cdk/aws-cloudwatch=1.46.0,@aws-cdk/aws-cognito=1.46.0,@aws-cdk/aws-dynamodb=1.46.0,@aws-cdk/aws-ec2=1.46.0,@aws-cdk/aws-elasticsearch=1.46.0,@aws-cdk/aws-events=1.46.0,@aws-cdk/aws-iam=1.46.0,@aws-cdk/aws-kinesis=1.46.0,@aws-cdk/aws-kinesisanalytics=1.46.0,@aws-cdk/aws-kms=1.46.0,@aws-cdk/aws-lambda=1.46.0,@aws-cdk/aws-logs=1.46.0,@aws-cdk/aws-s3=1.46.0,@aws-cdk/aws-s3-assets=1.46.0,@aws-cdk/aws-sns=1.46.0,@aws-cdk/aws-sqs=1.46.0,@aws-cdk/aws-ssm=1.46.0,@aws-cdk/aws-stepfunctions=1.46.0,@aws-cdk/cdk-assets-schema=1.46.0,@aws-cdk/cloud-assembly-schema=1.46.0,@aws-cdk/core=1.46.0,@aws-cdk/custom-resources=1.46.0,@aws-cdk/cx-api=1.46.0,@aws-cdk/region-info=1.46.0,@aws-solutions-constructs/aws-lambda-dynamodb=1.46.0,@aws-solutions-constructs/core=1.46.0,jsii-runtime=node.js/v12.16.1" + }, + "Condition": "CDKMetadataAvailable" + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + }, + "Conditions": { + "CDKMetadataAvailable": { + "Fn::Or": [ + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-northwest-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-1" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "sa-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-2" + ] + } + ] + } + ] + } + } +} diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-table.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-table.ts new file mode 100644 index 000000000..a5e96c0b1 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-table.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToDynamoDB } from "../lib"; +import { AttributeType, Table } from "@aws-cdk/aws-dynamodb"; +import * as lambda from "@aws-cdk/aws-lambda"; + +const app = new App(); +const stack = new Stack(app, 'test-lambda-dynamodb-stack'); + +const table = new Table(stack, 'existing-table', {partitionKey: {name: 'pk', type: AttributeType.STRING}}); + +new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + existingTableObj: table +}); + +app.synth(); diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts index 03c9d799a..d42a15503 100644 --- a/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts @@ -15,6 +15,7 @@ import { SynthUtils } from '@aws-cdk/assert'; import { LambdaToDynamoDB, LambdaToDynamoDBProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; +import { AttributeType, Table } from '@aws-cdk/aws-dynamodb'; import * as cdk from "@aws-cdk/core"; import '@aws-cdk/assert/jest'; @@ -22,9 +23,9 @@ function deployNewFunc(stack: cdk.Stack) { const props: LambdaToDynamoDBProps = { deployLambda: true, lambdaFunctionProps: { - code: lambda.Code.asset(`${__dirname}/lambda`), - runtime: lambda.Runtime.NODEJS_10_X, - handler: 'index.handler' + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' }, }; @@ -46,10 +47,27 @@ function useExistingFunc(stack: cdk.Stack) { readCapacity: 3, writeCapacity: 3, partitionKey: { - name: 'id', - type: dynamodb.AttributeType.STRING + name: 'id', + type: dynamodb.AttributeType.STRING } - }, + } + }; + + return new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', props); +} + +function useExistingTable(stack: cdk.Stack) { + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + const existingTableObj = new Table(stack, 'table', {partitionKey: {type: AttributeType.STRING, name: 'pk'}}); + + const props: LambdaToDynamoDBProps = { + deployLambda: true, + lambdaFunctionProps, + existingTableObj }; return new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', props); @@ -79,7 +97,7 @@ test('check lambda function properties for deploy: true', () => { Variables: { AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1", DDB_TABLE_NAME: { - Ref: "testlambdadynamodbstackDynamoTable8138E93B" + Ref: "testlambdadynamodbstackDynamoTable8138E93B" } } } @@ -208,14 +226,14 @@ test('check lambda function properties for deploy: false', () => { useExistingFunc(stack); expect(stack).toHaveResource('AWS::Lambda::Function', { - Handler: "index.handler", - Role: { - "Fn::GetAtt": [ - "MyExistingFunctionServiceRoleF9E14BFD", - "Arn" - ] - }, - Runtime: "python3.6" + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "MyExistingFunctionServiceRoleF9E14BFD", + "Arn" + ] + }, + Runtime: "python3.6" }); }); test('check iot lambda function role for deploy: false', () => { @@ -263,6 +281,7 @@ test('check properties', () => { test('check exception for Missing existingObj from props for deploy = false', () => { const stack = new cdk.Stack(); + const props: LambdaToDynamoDBProps = { deployLambda: true }; @@ -305,3 +324,28 @@ test('check deploy = true and no prop', () => { } }); }); + +test('check table properties for when existingTableObj is provided', () => { + const stack = new cdk.Stack(); + + useExistingTable(stack); + + expect(stack).toHaveResource('AWS::DynamoDB::Table', { + "KeySchema": [ + { + "AttributeName": "pk", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "pk", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }); +});