Repository: cloudcomponents/cdk-constructs (language: python)

Issue:
Only one DynamoDB seeder possible per CDK stack
We encountered an issue in which multiple `DynamoDBSeeder` instances cause the stack deployment to fail. The problem seems to be missing permission to access the staged seed files on S3.

**I created a minimal stack to reproduce the issue: https://github.com/robdasilva/cdk-dynamo-db-seeder-s3-role-issue**

After looking into the synthesized stack output, it seems, that there is a policy attached to the `CustomServiceProvider` for each seed. However, they all reference the same resource—i.e. the seed file of the first seeder. The same behavior is seen for the `BatchWriteItem` policy of the respective DynamoDB tables: There is only **one** statement for the **first** table.

That causes the stack deployment to fail once it reaches any subsequent seeder, due to the `CustomResource` being unable to access the respective file on S3.


Discussion / hints:

Fix patch:
diff --git a/examples/dynamodb-seeder-example/package.json b/examples/dynamodb-seeder-example/package.json
index f9547451b..d4f7fe8a3 100644
--- a/examples/dynamodb-seeder-example/package.json
+++ b/examples/dynamodb-seeder-example/package.json
@@ -11,6 +11,7 @@
   "dependencies": {
     "@aws-cdk/aws-dynamodb": "^1.89.0",
     "@aws-cdk/aws-s3": "^1.89.0",
+    "@aws-cdk/aws-s3-deployment": "^1.89.0",
     "@aws-cdk/core": "^1.89.0",
     "@cloudcomponents/cdk-dynamodb-seeder": "^1.24.1",
     "source-map-support": "^0.5.19"
diff --git a/examples/dynamodb-seeder-example/s3-seeds/seeds1.json b/examples/dynamodb-seeder-example/s3-seeds/seeds1.json
new file mode 100644
index 000000000..0a3c1894d
--- /dev/null
+++ b/examples/dynamodb-seeder-example/s3-seeds/seeds1.json
@@ -0,0 +1,10 @@
+[
+  {
+    "id": 5,
+    "column": "foo"
+  },
+  {
+    "id": 6,
+    "column": "bar"
+  }
+]
\ No newline at end of file
diff --git a/examples/dynamodb-seeder-example/s3-seeds/seeds2.json b/examples/dynamodb-seeder-example/s3-seeds/seeds2.json
new file mode 100644
index 000000000..308eca047
--- /dev/null
+++ b/examples/dynamodb-seeder-example/s3-seeds/seeds2.json
@@ -0,0 +1,10 @@
+[
+  {
+    "id": 7,
+    "column": "foo"
+  },
+  {
+    "id": 8,
+    "column": "bar"
+  }
+]
\ No newline at end of file
diff --git a/examples/dynamodb-seeder-example/src/dynamodb-seeder-stack.ts b/examples/dynamodb-seeder-example/src/dynamodb-seeder-stack.ts
index 764c8f6fd..05cbb684a 100644
--- a/examples/dynamodb-seeder-example/src/dynamodb-seeder-stack.ts
+++ b/examples/dynamodb-seeder-example/src/dynamodb-seeder-stack.ts
@@ -1,7 +1,8 @@
 import * as path from 'path';
 import { Table, AttributeType } from '@aws-cdk/aws-dynamodb';
+import { Bucket } from '@aws-cdk/aws-s3';
+import { BucketDeployment, Source } from '@aws-cdk/aws-s3-deployment';
 import { Construct, Stack, StackProps, RemovalPolicy } from '@aws-cdk/core';
-//import { Bucket } from '@aws-cdk/aws-s3';
 import { DynamoDBSeeder, Seeds } from '@cloudcomponents/cdk-dynamodb-seeder';
 
 export class DynamoDBSeederStack extends Stack {
@@ -35,15 +36,28 @@ export class DynamoDBSeederStack extends Stack {
       ]),
     });
 
-    // const seedsBucket = Bucket.fromBucketName(
-    //   this,
-    //   'SeedsBucket',
-    //   'my-seeds-bucket',
-    // );
+    const seedsBucket = new Bucket(this, 'Bucket', {
+      autoDeleteObjects: true,
+      removalPolicy: RemovalPolicy.DESTROY,
+    });
+
+    const deploy = new BucketDeployment(this, 'DeployWebsite', {
+      sources: [Source.asset(path.join(__dirname, '..', 's3-seeds'))],
+      destinationBucket: seedsBucket,
+    });
+
+    const seeder1 = new DynamoDBSeeder(this, 'BucketSeeder1', {
+      table,
+      seeds: Seeds.fromBucket(seedsBucket, 'seeds1.json'),
+    });
+
+    seeder1.node.addDependency(deploy);
+
+    const seeder2 = new DynamoDBSeeder(this, 'BucketSeeder2', {
+      table,
+      seeds: Seeds.fromBucket(seedsBucket, 'seeds2.json'),
+    });
 
-    // new DynamoDBSeeder(this, 'JsonFileSeeder', {
-    //   table,
-    //   seeds: Seeds.fromBucket(seedsBucket, 'seeds.json'),
-    // });
+    seeder2.node.addDependency(deploy);
   }
 }
diff --git a/packages/cdk-dynamodb-seeder/lambda-file-sizes.json b/packages/cdk-dynamodb-seeder/lambda-file-sizes.json
index 8837d3312..7dc329462 100644
--- a/packages/cdk-dynamodb-seeder/lambda-file-sizes.json
+++ b/packages/cdk-dynamodb-seeder/lambda-file-sizes.json
@@ -1,1 +1,1 @@
-[{"timestamp":1606329554391,"files":[{"filename":"dynamodb-seeder/index.js","previous":5197,"size":5202,"diff":5}]},{"timestamp":1596457281579,"files":[{"filename":"dynamodb-seeder/index.js","previous":14163,"size":5197,"diff":-8966}]},{"timestamp":1596407673616,"files":[{"filename":"dynamodb-seeder/__entrypoint__.js","previous":6753,"size":0,"diff":-6753},{"filename":"dynamodb-seeder/index.js","previous":5684,"size":14163,"diff":8479}]}]
+[{"timestamp":1613679396981,"files":[{"filename":"dynamodb-seeder/index.js","previous":5202,"size":152769,"diff":147567}]},{"timestamp":1606329554391,"files":[{"filename":"dynamodb-seeder/index.js","previous":5197,"size":5202,"diff":5}]},{"timestamp":1596457281579,"files":[{"filename":"dynamodb-seeder/index.js","previous":14163,"size":5197,"diff":-8966}]},{"timestamp":1596407673616,"files":[{"filename":"dynamodb-seeder/__entrypoint__.js","previous":6753,"size":0,"diff":-6753},{"filename":"dynamodb-seeder/index.js","previous":5684,"size":14163,"diff":8479}]}]
diff --git a/packages/cdk-dynamodb-seeder/package.json b/packages/cdk-dynamodb-seeder/package.json
index 78037d6aa..365935dfc 100644
--- a/packages/cdk-dynamodb-seeder/package.json
+++ b/packages/cdk-dynamodb-seeder/package.json
@@ -60,6 +60,7 @@
   "peerDependencies": {
     "@aws-cdk/aws-dynamodb": "^1.89.0",
     "@aws-cdk/aws-iam": "^1.89.0",
+    "@aws-cdk/aws-lambda": "^1.89.0",
     "@aws-cdk/aws-s3": "^1.89.0",
     "@aws-cdk/aws-s3-assets": "^1.89.0",
     "@aws-cdk/core": "^1.89.0",
@@ -68,6 +69,7 @@
   "dependencies": {
     "@aws-cdk/aws-dynamodb": "^1.89.0",
     "@aws-cdk/aws-iam": "^1.89.0",
+    "@aws-cdk/aws-lambda": "^1.89.0",
     "@aws-cdk/aws-s3": "^1.89.0",
     "@aws-cdk/aws-s3-assets": "^1.89.0",
     "@aws-cdk/core": "^1.89.0"
@@ -76,6 +78,7 @@
     "@aws-cdk/assert": "^1.89.0",
     "@types/lodash.chunk": "^4.2.6",
     "aws-sdk": "^2.840.0",
+    "custom-resource-helper": "^1.0.15",
     "jest-cdk-snapshot": "^1.4.1",
     "lodash.chunk": "^4.2.0"
   },
diff --git a/packages/cdk-dynamodb-seeder/src/directories.ts b/packages/cdk-dynamodb-seeder/src/directories.ts
deleted file mode 100644
index 27fe92209..000000000
--- a/packages/cdk-dynamodb-seeder/src/directories.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import * as path from 'path';
-
-export const dynamodbSeederDir = path.join(__dirname, 'lambdas', 'dynamodb-seeder');
diff --git a/packages/cdk-dynamodb-seeder/src/dynamodb-seeder.ts b/packages/cdk-dynamodb-seeder/src/dynamodb-seeder.ts
index 5dab0552a..8603b7f2d 100644
--- a/packages/cdk-dynamodb-seeder/src/dynamodb-seeder.ts
+++ b/packages/cdk-dynamodb-seeder/src/dynamodb-seeder.ts
@@ -1,9 +1,10 @@
+import * as path from 'path';
 import { ITable } from '@aws-cdk/aws-dynamodb';
-import { Effect } from '@aws-cdk/aws-iam';
+import { Effect, PolicyStatement } from '@aws-cdk/aws-iam';
+import { SingletonFunction, Runtime, Code } from '@aws-cdk/aws-lambda';
 import { Bucket } from '@aws-cdk/aws-s3';
-import { Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, Duration } from '@aws-cdk/core';
+import { Construct, CustomResource, Duration } from '@aws-cdk/core';
 
-import { dynamodbSeederDir } from './directories';
 import { Seeds } from './seeds';
 
 export interface DynamoDBSeederProps {
@@ -25,31 +26,39 @@ export class DynamoDBSeeder extends Construct {
     super(scope, id);
 
     const seeds = props.seeds.bind(this);
-
     const seedsBucket = seeds.s3Location?.bucketName ? Bucket.fromBucketName(this, 'SeedsBucket', seeds.s3Location.bucketName) : undefined;
 
-    const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::DynamodbSeeder', {
-      codeDirectory: dynamodbSeederDir,
-      runtime: CustomResourceProviderRuntime.NODEJS_12,
+    const handler = new SingletonFunction(this, 'CustomResourceHandler', {
+      uuid: 'Custom::DynamodbSeeder',
+      runtime: Runtime.NODEJS_12_X,
+      code: Code.fromAsset(path.join(__dirname, 'lambdas', 'dynamodb-seeder')),
+      handler: 'index.handler',
+      lambdaPurpose: 'Custom::DynamodbSeeder',
       timeout: props.timeout ?? Duration.minutes(15),
-      policyStatements: [
-        {
-          Effect: Effect.ALLOW,
-          Action: ['dynamodb:BatchWriteItem'],
-          Resource: props.table.tableArn,
-        },
-        seedsBucket
-          ? {
-              Effect: Effect.ALLOW,
-              Action: ['s3:GetObject'],
-              Resource: `${seedsBucket.bucketArn}/${seeds.s3Location?.objectKey ?? '*'}`,
-            }
-          : undefined,
-      ],
     });
 
+    handler.addToRolePolicy(
+      new PolicyStatement({
+        effect: Effect.ALLOW,
+        actions: ['dynamodb:BatchWriteItem'],
+        resources: [props.table.tableArn],
+      }),
+    );
+
+    if (seedsBucket) {
+      const objectKey = seeds.s3Location?.objectKey ?? '*';
+
+      handler.addToRolePolicy(
+        new PolicyStatement({
+          effect: Effect.ALLOW,
+          actions: ['s3:GetObject'],
+          resources: [seedsBucket.arnForObjects(objectKey)],
+        }),
+      );
+    }
+
     new CustomResource(this, 'CustomResource', {
-      serviceToken,
+      serviceToken: handler.functionArn,
       resourceType: 'Custom::DynamodbSeeder',
       properties: {
         TableName: props.table.tableName,
diff --git a/packages/cdk-dynamodb-seeder/src/lambdas/dynamodb-seeder/index.ts b/packages/cdk-dynamodb-seeder/src/lambdas/dynamodb-seeder/index.ts
index 9613484cd..f4c0c67f8 100644
--- a/packages/cdk-dynamodb-seeder/src/lambdas/dynamodb-seeder/index.ts
+++ b/packages/cdk-dynamodb-seeder/src/lambdas/dynamodb-seeder/index.ts
@@ -1,5 +1,6 @@
-import type { CloudFormationCustomResourceEvent, CloudFormationCustomResourceCreateEvent } from 'aws-lambda';
+import type { CloudFormationCustomResourceEvent } from 'aws-lambda';
 import { DynamoDB, S3 } from 'aws-sdk';
+import { customResourceHelper, OnCreateHandler, ResourceHandler, ResourceHandlerReturn } from 'custom-resource-helper';
 import chunk from 'lodash.chunk';
 
 // DynamoDB has a 25 item limit in batch requests
@@ -31,7 +32,7 @@ const getProperties = (props: CloudFormationCustomResourceEvent['ResourcePropert
   },
 });
 
-const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise<void> => {
+const handleCreate: OnCreateHandler = async (event): Promise<ResourceHandlerReturn> => {
   const props = getProperties(event.ResourceProperties);
 
   const { inlineSeeds, ...s3Location } = props.seeds;
@@ -41,6 +42,10 @@ const onCreate = async (event: CloudFormationCustomResourceCreateEvent): Promise
   await writeSeeds(props.tableName, seeds);
 
   console.log(`Seed running complete for table ${props.tableName}`);
+
+  return {
+    physicalResourceId: event.RequestId,
+  };
 };
 
 const getSeedsFromS3 = async (s3Location: { s3Bucket?: string; s3Key?: string; s3ObjectVersion?: string }): Promise<Seeds> => {
@@ -89,17 +94,8 @@ const writeSeeds = async (tableName: string, seeds: Seeds): Promise<void> => {
   );
 };
 
-export const handler = async (event: CloudFormationCustomResourceEvent): Promise<void> => {
-  const requestType = event.RequestType;
-
-  switch (requestType) {
-    case 'Create':
-      return onCreate(event as CloudFormationCustomResourceCreateEvent);
-    case 'Update':
-      return;
-    case 'Delete':
-      return;
-    default:
-      throw new Error(`Invalid request type: ${requestType}`);
-  }
-};
+export const handler = customResourceHelper(
+  (): ResourceHandler => ({
+    onCreate: handleCreate,
+  }),
+);


Test patch:
diff --git a/packages/cdk-dynamodb-seeder/src/__tests__/__snapshots__/dynamodb-seeder.test.ts.snap b/packages/cdk-dynamodb-seeder/src/__tests__/__snapshots__/dynamodb-seeder.test.ts.snap
index a714855a1..225045ea1 100644
--- a/packages/cdk-dynamodb-seeder/src/__tests__/__snapshots__/dynamodb-seeder.test.ts.snap
+++ b/packages/cdk-dynamodb-seeder/src/__tests__/__snapshots__/dynamodb-seeder.test.ts.snap
@@ -3,28 +3,29 @@
 exports[`bucket 1`] = `
 Object {
   "Parameters": Object {
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0ArtifactHashD7FD838A": Object {
-      "Description": "Artifact hash for asset \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eArtifactHash79A71AFC": Object {
+      "Description": "Artifact hash for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3Bucket7A9635D5": Object {
-      "Description": "S3 bucket for asset \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE": Object {
+      "Description": "S3 bucket for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8": Object {
-      "Description": "S3 key for asset version \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358": Object {
+      "Description": "S3 key for asset version \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
   },
   "Resources": Object {
-    "CustomDynamodbSeederCustomResourceProviderHandler904BFFD4": Object {
+    "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10": Object {
       "DependsOn": Array [
-        "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2",
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
       ],
       "Properties": Object {
         "Code": Object {
           "S3Bucket": Object {
-            "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3Bucket7A9635D5",
+            "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE",
           },
           "S3Key": Object {
             "Fn::Join": Array [
@@ -37,7 +38,7 @@ Object {
                       "Fn::Split": Array [
                         "||",
                         Object {
-                          "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8",
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
                         },
                       ],
                     },
@@ -50,7 +51,7 @@ Object {
                       "Fn::Split": Array [
                         "||",
                         Object {
-                          "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8",
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
                         },
                       ],
                     },
@@ -60,11 +61,10 @@ Object {
             ],
           },
         },
-        "Handler": "__entrypoint__.handler",
-        "MemorySize": 128,
+        "Handler": "index.handler",
         "Role": Object {
           "Fn::GetAtt": Array [
-            "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2",
+            "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
             "Arn",
           ],
         },
@@ -73,7 +73,7 @@ Object {
       },
       "Type": "AWS::Lambda::Function",
     },
-    "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2": Object {
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8": Object {
       "Properties": Object {
         "AssumeRolePolicyDocument": Object {
           "Statement": Array [
@@ -89,52 +89,63 @@ Object {
         },
         "ManagedPolicyArns": Array [
           Object {
-            "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
-          },
-        ],
-        "Policies": Array [
-          Object {
-            "PolicyDocument": Object {
-              "Statement": Array [
-                Object {
-                  "Action": Array [
-                    "dynamodb:BatchWriteItem",
-                  ],
-                  "Effect": "Allow",
-                  "Resource": Object {
-                    "Fn::GetAtt": Array [
-                      "TableCD117FA1",
-                      "Arn",
-                    ],
-                  },
-                },
+            "Fn::Join": Array [
+              "",
+              Array [
+                "arn:",
                 Object {
-                  "Action": Array [
-                    "s3:GetObject",
-                  ],
-                  "Effect": "Allow",
-                  "Resource": Object {
-                    "Fn::Join": Array [
-                      "",
-                      Array [
-                        "arn:",
-                        Object {
-                          "Ref": "AWS::Partition",
-                        },
-                        ":s3:::my-seeds-bucket/seeds.json",
-                      ],
-                    ],
-                  },
+                  "Ref": "AWS::Partition",
                 },
+                ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
               ],
-              "Version": "2012-10-17",
-            },
-            "PolicyName": "Inline",
+            ],
           },
         ],
       },
       "Type": "AWS::IAM::Role",
     },
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C": Object {
+      "Properties": Object {
+        "PolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "dynamodb:BatchWriteItem",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::GetAtt": Array [
+                  "TableCD117FA1",
+                  "Arn",
+                ],
+              },
+            },
+            Object {
+              "Action": "s3:GetObject",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "arn:",
+                    Object {
+                      "Ref": "AWS::Partition",
+                    },
+                    ":s3:::my-seeds-bucket/seeds.json",
+                  ],
+                ],
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "PolicyName": "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "Roles": Array [
+          Object {
+            "Ref": "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Policy",
+    },
     "DynamoDBSeederCustomResourceEF4D5C60": Object {
       "DeletionPolicy": "Delete",
       "Properties": Object {
@@ -144,7 +155,7 @@ Object {
         },
         "ServiceToken": Object {
           "Fn::GetAtt": Array [
-            "CustomDynamodbSeederCustomResourceProviderHandler904BFFD4",
+            "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10",
             "Arn",
           ],
         },
@@ -185,28 +196,29 @@ Object {
 exports[`inline 1`] = `
 Object {
   "Parameters": Object {
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0ArtifactHashD7FD838A": Object {
-      "Description": "Artifact hash for asset \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eArtifactHash79A71AFC": Object {
+      "Description": "Artifact hash for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3Bucket7A9635D5": Object {
-      "Description": "S3 bucket for asset \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE": Object {
+      "Description": "S3 bucket for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8": Object {
-      "Description": "S3 key for asset version \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358": Object {
+      "Description": "S3 key for asset version \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
   },
   "Resources": Object {
-    "CustomDynamodbSeederCustomResourceProviderHandler904BFFD4": Object {
+    "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10": Object {
       "DependsOn": Array [
-        "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2",
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
       ],
       "Properties": Object {
         "Code": Object {
           "S3Bucket": Object {
-            "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3Bucket7A9635D5",
+            "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE",
           },
           "S3Key": Object {
             "Fn::Join": Array [
@@ -219,7 +231,7 @@ Object {
                       "Fn::Split": Array [
                         "||",
                         Object {
-                          "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8",
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
                         },
                       ],
                     },
@@ -232,7 +244,7 @@ Object {
                       "Fn::Split": Array [
                         "||",
                         Object {
-                          "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8",
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
                         },
                       ],
                     },
@@ -242,11 +254,10 @@ Object {
             ],
           },
         },
-        "Handler": "__entrypoint__.handler",
-        "MemorySize": 128,
+        "Handler": "index.handler",
         "Role": Object {
           "Fn::GetAtt": Array [
-            "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2",
+            "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
             "Arn",
           ],
         },
@@ -255,7 +266,7 @@ Object {
       },
       "Type": "AWS::Lambda::Function",
     },
-    "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2": Object {
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8": Object {
       "Properties": Object {
         "AssumeRolePolicyDocument": Object {
           "Statement": Array [
@@ -271,34 +282,47 @@ Object {
         },
         "ManagedPolicyArns": Array [
           Object {
-            "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
-          },
-        ],
-        "Policies": Array [
-          Object {
-            "PolicyDocument": Object {
-              "Statement": Array [
+            "Fn::Join": Array [
+              "",
+              Array [
+                "arn:",
                 Object {
-                  "Action": Array [
-                    "dynamodb:BatchWriteItem",
-                  ],
-                  "Effect": "Allow",
-                  "Resource": Object {
-                    "Fn::GetAtt": Array [
-                      "TableCD117FA1",
-                      "Arn",
-                    ],
-                  },
+                  "Ref": "AWS::Partition",
                 },
+                ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
               ],
-              "Version": "2012-10-17",
-            },
-            "PolicyName": "Inline",
+            ],
           },
         ],
       },
       "Type": "AWS::IAM::Role",
     },
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C": Object {
+      "Properties": Object {
+        "PolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "dynamodb:BatchWriteItem",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::GetAtt": Array [
+                  "TableCD117FA1",
+                  "Arn",
+                ],
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "PolicyName": "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "Roles": Array [
+          Object {
+            "Ref": "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Policy",
+    },
     "DynamoDBSeederCustomResourceEF4D5C60": Object {
       "DeletionPolicy": "Delete",
       "Properties": Object {
@@ -307,7 +331,7 @@ Object {
         },
         "ServiceToken": Object {
           "Fn::GetAtt": Array [
-            "CustomDynamodbSeederCustomResourceProviderHandler904BFFD4",
+            "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10",
             "Arn",
           ],
         },
@@ -348,16 +372,16 @@ Object {
 exports[`json file 1`] = `
 Object {
   "Parameters": Object {
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0ArtifactHashD7FD838A": Object {
-      "Description": "Artifact hash for asset \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eArtifactHash79A71AFC": Object {
+      "Description": "Artifact hash for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3Bucket7A9635D5": Object {
-      "Description": "S3 bucket for asset \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE": Object {
+      "Description": "S3 bucket for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
-    "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8": Object {
-      "Description": "S3 key for asset version \\"4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0\\"",
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358": Object {
+      "Description": "S3 key for asset version \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
       "Type": "String",
     },
     "AssetParametersd030fc4499f9b92060beabbe94d500033cde3dd8a61a1668fdfcee96b2019ffdArtifactHash584F00C3": Object {
@@ -374,14 +398,15 @@ Object {
     },
   },
   "Resources": Object {
-    "CustomDynamodbSeederCustomResourceProviderHandler904BFFD4": Object {
+    "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10": Object {
       "DependsOn": Array [
-        "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2",
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
       ],
       "Properties": Object {
         "Code": Object {
           "S3Bucket": Object {
-            "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3Bucket7A9635D5",
+            "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE",
           },
           "S3Key": Object {
             "Fn::Join": Array [
@@ -394,7 +419,7 @@ Object {
                       "Fn::Split": Array [
                         "||",
                         Object {
-                          "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8",
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
                         },
                       ],
                     },
@@ -407,7 +432,7 @@ Object {
                       "Fn::Split": Array [
                         "||",
                         Object {
-                          "Ref": "AssetParameters4c20502c8ee54d52fa9b2e8f0251e77c489e44933dcde320ab40ffdf3a0ad8b0S3VersionKeyD76530F8",
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
                         },
                       ],
                     },
@@ -417,11 +442,10 @@ Object {
             ],
           },
         },
-        "Handler": "__entrypoint__.handler",
-        "MemorySize": 128,
+        "Handler": "index.handler",
         "Role": Object {
           "Fn::GetAtt": Array [
-            "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2",
+            "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
             "Arn",
           ],
         },
@@ -430,7 +454,7 @@ Object {
       },
       "Type": "AWS::Lambda::Function",
     },
-    "CustomDynamodbSeederCustomResourceProviderRoleB3FCC5E2": Object {
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8": Object {
       "Properties": Object {
         "AssumeRolePolicyDocument": Object {
           "Statement": Array [
@@ -446,81 +470,92 @@ Object {
         },
         "ManagedPolicyArns": Array [
           Object {
-            "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
-          },
-        ],
-        "Policies": Array [
-          Object {
-            "PolicyDocument": Object {
-              "Statement": Array [
+            "Fn::Join": Array [
+              "",
+              Array [
+                "arn:",
                 Object {
-                  "Action": Array [
-                    "dynamodb:BatchWriteItem",
-                  ],
-                  "Effect": "Allow",
-                  "Resource": Object {
-                    "Fn::GetAtt": Array [
-                      "TableCD117FA1",
-                      "Arn",
-                    ],
-                  },
+                  "Ref": "AWS::Partition",
                 },
-                Object {
-                  "Action": Array [
-                    "s3:GetObject",
-                  ],
-                  "Effect": "Allow",
-                  "Resource": Object {
-                    "Fn::Join": Array [
-                      "",
-                      Array [
-                        "arn:",
-                        Object {
-                          "Ref": "AWS::Partition",
-                        },
-                        ":s3:::",
-                        Object {
-                          "Ref": "AssetParametersd030fc4499f9b92060beabbe94d500033cde3dd8a61a1668fdfcee96b2019ffdS3Bucket8B717ACC",
-                        },
-                        "/",
+                ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+              ],
+            ],
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Role",
+    },
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C": Object {
+      "Properties": Object {
+        "PolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "dynamodb:BatchWriteItem",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::GetAtt": Array [
+                  "TableCD117FA1",
+                  "Arn",
+                ],
+              },
+            },
+            Object {
+              "Action": "s3:GetObject",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "arn:",
+                    Object {
+                      "Ref": "AWS::Partition",
+                    },
+                    ":s3:::",
+                    Object {
+                      "Ref": "AssetParametersd030fc4499f9b92060beabbe94d500033cde3dd8a61a1668fdfcee96b2019ffdS3Bucket8B717ACC",
+                    },
+                    "/",
+                    Object {
+                      "Fn::Select": Array [
+                        0,
                         Object {
-                          "Fn::Select": Array [
-                            0,
+                          "Fn::Split": Array [
+                            "||",
                             Object {
-                              "Fn::Split": Array [
-                                "||",
-                                Object {
-                                  "Ref": "AssetParametersd030fc4499f9b92060beabbe94d500033cde3dd8a61a1668fdfcee96b2019ffdS3VersionKey88E97CF1",
-                                },
-                              ],
+                              "Ref": "AssetParametersd030fc4499f9b92060beabbe94d500033cde3dd8a61a1668fdfcee96b2019ffdS3VersionKey88E97CF1",
                             },
                           ],
                         },
+                      ],
+                    },
+                    Object {
+                      "Fn::Select": Array [
+                        1,
                         Object {
-                          "Fn::Select": Array [
-                            1,
+                          "Fn::Split": Array [
+                            "||",
                             Object {
-                              "Fn::Split": Array [
-                                "||",
-                                Object {
-                                  "Ref": "AssetParametersd030fc4499f9b92060beabbe94d500033cde3dd8a61a1668fdfcee96b2019ffdS3VersionKey88E97CF1",
-                                },
-                              ],
+                              "Ref": "AssetParametersd030fc4499f9b92060beabbe94d500033cde3dd8a61a1668fdfcee96b2019ffdS3VersionKey88E97CF1",
                             },
                           ],
                         },
                       ],
-                    ],
-                  },
-                },
-              ],
-              "Version": "2012-10-17",
+                    },
+                  ],
+                ],
+              },
             },
-            "PolicyName": "Inline",
+          ],
+          "Version": "2012-10-17",
+        },
+        "PolicyName": "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "Roles": Array [
+          Object {
+            "Ref": "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
           },
         ],
       },
-      "Type": "AWS::IAM::Role",
+      "Type": "AWS::IAM::Policy",
     },
     "DynamoDBSeederCustomResourceEF4D5C60": Object {
       "DeletionPolicy": "Delete",
@@ -565,7 +600,236 @@ Object {
         },
         "ServiceToken": Object {
           "Fn::GetAtt": Array [
-            "CustomDynamodbSeederCustomResourceProviderHandler904BFFD4",
+            "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10",
+            "Arn",
+          ],
+        },
+        "TableName": Object {
+          "Ref": "TableCD117FA1",
+        },
+      },
+      "Type": "Custom::DynamodbSeeder",
+      "UpdateReplacePolicy": "Delete",
+    },
+    "TableCD117FA1": Object {
+      "DeletionPolicy": "Retain",
+      "Properties": Object {
+        "AttributeDefinitions": Array [
+          Object {
+            "AttributeName": "id",
+            "AttributeType": "N",
+          },
+        ],
+        "KeySchema": Array [
+          Object {
+            "AttributeName": "id",
+            "KeyType": "HASH",
+          },
+        ],
+        "ProvisionedThroughput": Object {
+          "ReadCapacityUnits": 5,
+          "WriteCapacityUnits": 5,
+        },
+      },
+      "Type": "AWS::DynamoDB::Table",
+      "UpdateReplacePolicy": "Retain",
+    },
+  },
+}
+`;
+
+exports[`multiple seeders 1`] = `
+Object {
+  "Parameters": Object {
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eArtifactHash79A71AFC": Object {
+      "Description": "Artifact hash for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
+      "Type": "String",
+    },
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE": Object {
+      "Description": "S3 bucket for asset \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
+      "Type": "String",
+    },
+    "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358": Object {
+      "Description": "S3 key for asset version \\"9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4e\\"",
+      "Type": "String",
+    },
+  },
+  "Resources": Object {
+    "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10": Object {
+      "DependsOn": Array [
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
+      ],
+      "Properties": Object {
+        "Code": Object {
+          "S3Bucket": Object {
+            "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3Bucket77314CEE",
+          },
+          "S3Key": Object {
+            "Fn::Join": Array [
+              "",
+              Array [
+                Object {
+                  "Fn::Select": Array [
+                    0,
+                    Object {
+                      "Fn::Split": Array [
+                        "||",
+                        Object {
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
+                        },
+                      ],
+                    },
+                  ],
+                },
+                Object {
+                  "Fn::Select": Array [
+                    1,
+                    Object {
+                      "Fn::Split": Array [
+                        "||",
+                        Object {
+                          "Ref": "AssetParameters9aef665652bde9e695d1b39b9e6e66f873d205e83ceea61d4dd36edbf4716c4eS3VersionKey6DBEC358",
+                        },
+                      ],
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+        "Handler": "index.handler",
+        "Role": Object {
+          "Fn::GetAtt": Array [
+            "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
+            "Arn",
+          ],
+        },
+        "Runtime": "nodejs12.x",
+        "Timeout": 900,
+      },
+      "Type": "AWS::Lambda::Function",
+    },
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8": Object {
+      "Properties": Object {
+        "AssumeRolePolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "sts:AssumeRole",
+              "Effect": "Allow",
+              "Principal": Object {
+                "Service": "lambda.amazonaws.com",
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "ManagedPolicyArns": Array [
+          Object {
+            "Fn::Join": Array [
+              "",
+              Array [
+                "arn:",
+                Object {
+                  "Ref": "AWS::Partition",
+                },
+                ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+              ],
+            ],
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Role",
+    },
+    "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C": Object {
+      "Properties": Object {
+        "PolicyDocument": Object {
+          "Statement": Array [
+            Object {
+              "Action": "dynamodb:BatchWriteItem",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::GetAtt": Array [
+                  "TableCD117FA1",
+                  "Arn",
+                ],
+              },
+            },
+            Object {
+              "Action": "s3:GetObject",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "arn:",
+                    Object {
+                      "Ref": "AWS::Partition",
+                    },
+                    ":s3:::seeds-bucket-one/seeds-one.json",
+                  ],
+                ],
+              },
+            },
+            Object {
+              "Action": "s3:GetObject",
+              "Effect": "Allow",
+              "Resource": Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "arn:",
+                    Object {
+                      "Ref": "AWS::Partition",
+                    },
+                    ":s3:::seeds-bucket-two/seeds-two.json",
+                  ],
+                ],
+              },
+            },
+          ],
+          "Version": "2012-10-17",
+        },
+        "PolicyName": "CustomDynamodbSeederCustomDynamodbSeederServiceRoleDefaultPolicy6C04255C",
+        "Roles": Array [
+          Object {
+            "Ref": "CustomDynamodbSeederCustomDynamodbSeederServiceRole5FC6AEF8",
+          },
+        ],
+      },
+      "Type": "AWS::IAM::Policy",
+    },
+    "DynamoDBSeeder1CustomResource555E70AD": Object {
+      "DeletionPolicy": "Delete",
+      "Properties": Object {
+        "Seeds": Object {
+          "S3Bucket": "seeds-bucket-one",
+          "S3Key": "seeds-one.json",
+        },
+        "ServiceToken": Object {
+          "Fn::GetAtt": Array [
+            "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10",
+            "Arn",
+          ],
+        },
+        "TableName": Object {
+          "Ref": "TableCD117FA1",
+        },
+      },
+      "Type": "Custom::DynamodbSeeder",
+      "UpdateReplacePolicy": "Delete",
+    },
+    "DynamoDBSeeder2CustomResourceAC955965": Object {
+      "DeletionPolicy": "Delete",
+      "Properties": Object {
+        "Seeds": Object {
+          "S3Bucket": "seeds-bucket-two",
+          "S3Key": "seeds-two.json",
+        },
+        "ServiceToken": Object {
+          "Fn::GetAtt": Array [
+            "CustomDynamodbSeederCustomDynamodbSeeder3D3D7F10",
             "Arn",
           ],
         },
diff --git a/packages/cdk-dynamodb-seeder/src/__tests__/dynamodb-seeder.test.ts b/packages/cdk-dynamodb-seeder/src/__tests__/dynamodb-seeder.test.ts
index 728e211d3..29e6530b8 100644
--- a/packages/cdk-dynamodb-seeder/src/__tests__/dynamodb-seeder.test.ts
+++ b/packages/cdk-dynamodb-seeder/src/__tests__/dynamodb-seeder.test.ts
@@ -7,10 +7,6 @@ import 'jest-cdk-snapshot';
 import { DynamoDBSeeder } from '../dynamodb-seeder';
 import { Seeds } from '../seeds';
 
-jest.mock('../directories', () => ({
-  dynamodbSeederDir: path.join(__dirname, 'mocks', 'dynamodb-seeder'),
-}));
-
 test('inline', () => {
   // GIVEN
   const stack = new Stack();
@@ -125,3 +121,31 @@ test('bucket', () => {
   // THEN
   expect(stack).toMatchCdkSnapshot();
 });
+
+test('multiple seeders', () => {
+  // GIVEN
+  const stack = new Stack();
+  const table = new Table(stack, 'Table', {
+    partitionKey: {
+      name: 'id',
+      type: AttributeType.NUMBER,
+    },
+  });
+
+  const seedsBucket1 = Bucket.fromBucketName(stack, 'SeedsBucket1', 'seeds-bucket-one');
+  const seedsBucket2 = Bucket.fromBucketName(stack, 'SeedsBucket2', 'seeds-bucket-two');
+
+  // WHEN
+  new DynamoDBSeeder(stack, 'DynamoDBSeeder1', {
+    table,
+    seeds: Seeds.fromBucket(seedsBucket1, 'seeds-one.json'),
+  });
+  // WHEN
+  new DynamoDBSeeder(stack, 'DynamoDBSeeder2', {
+    table,
+    seeds: Seeds.fromBucket(seedsBucket2, 'seeds-two.json'),
+  });
+
+  // THEN
+  expect(stack).toMatchCdkSnapshot();
+});
diff --git a/packages/cdk-dynamodb-seeder/src/__tests__/mocks/dynamodb-seeder/__entrypoint__.js b/packages/cdk-dynamodb-seeder/src/__tests__/mocks/dynamodb-seeder/__entrypoint__.js
deleted file mode 100644
index 9c81dc3f5..000000000
--- a/packages/cdk-dynamodb-seeder/src/__tests__/mocks/dynamodb-seeder/__entrypoint__.js
+++ /dev/null
@@ -1,119 +0,0 @@
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.handler = exports.external = void 0;
-const https = require("https");
-const url = require("url");
-// for unit tests
-exports.external = {
-    sendHttpRequest: defaultSendHttpRequest,
-    log: defaultLog,
-    includeStackTraces: true,
-    userHandlerIndex: './index',
-};
-const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED';
-const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID';
-async function handler(event) {
-    exports.external.log(JSON.stringify(event, undefined, 2));
-    // ignore DELETE event when the physical resource ID is the marker that
-    // indicates that this DELETE is a subsequent DELETE to a failed CREATE
-    // operation.
-    if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) {
-        exports.external.log('ignoring DELETE event caused by a failed CREATE event');
-        await submitResponse('SUCCESS', event);
-        return;
-    }
-    try {
-        // invoke the user handler. this is intentionally inside the try-catch to
-        // ensure that if there is an error it's reported as a failure to
-        // cloudformation (otherwise cfn waits).
-        // eslint-disable-next-line @typescript-eslint/no-require-imports
-        const userHandler = require(exports.external.userHandlerIndex).handler;
-        const result = await userHandler(event);
-        // validate user response and create the combined event
-        const responseEvent = renderResponse(event, result);
-        // submit to cfn as success
-        await submitResponse('SUCCESS', responseEvent);
-    }
-    catch (e) {
-        const resp = {
-            ...event,
-            Reason: exports.external.includeStackTraces ? e.stack : e.message,
-        };
-        if (!resp.PhysicalResourceId) {
-            // special case: if CREATE fails, which usually implies, we usually don't
-            // have a physical resource id. in this case, the subsequent DELETE
-            // operation does not have any meaning, and will likely fail as well. to
-            // address this, we use a marker so the provider framework can simply
-            // ignore the subsequent DELETE.
-            if (event.RequestType === 'Create') {
-                exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored');
-                resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER;
-            }
-            else {
-                // otherwise, if PhysicalResourceId is not specified, something is
-                // terribly wrong because all other events should have an ID.
-                exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`);
-            }
-        }
-        // this is an actual error, fail the activity altogether and exist.
-        await submitResponse('FAILED', resp);
-    }
-}
-exports.handler = handler;
-function renderResponse(cfnRequest, handlerResponse = {}) {
-    var _a, _b;
-    // if physical ID is not returned, we have some defaults for you based
-    // on the request type.
-    const physicalResourceId = (_b = (_a = handlerResponse.PhysicalResourceId) !== null && _a !== void 0 ? _a : cfnRequest.PhysicalResourceId) !== null && _b !== void 0 ? _b : cfnRequest.RequestId;
-    // if we are in DELETE and physical ID was changed, it's an error.
-    if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) {
-        throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`);
-    }
-    // merge request event and result event (result prevails).
-    return {
-        ...cfnRequest,
-        ...handlerResponse,
-        PhysicalResourceId: physicalResourceId,
-    };
-}
-async function submitResponse(status, event) {
-    var _a;
-    const json = {
-        Status: status,
-        Reason: (_a = event.Reason) !== null && _a !== void 0 ? _a : status,
-        StackId: event.StackId,
-        RequestId: event.RequestId,
-        PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER,
-        LogicalResourceId: event.LogicalResourceId,
-        NoEcho: event.NoEcho,
-        Data: event.Data,
-    };
-    exports.external.log('submit response to cloudformation', json);
-    const responseBody = JSON.stringify(json);
-    const parsedUrl = url.parse(event.ResponseURL);
-    const req = {
-        hostname: parsedUrl.hostname,
-        path: parsedUrl.path,
-        method: 'PUT',
-        headers: { 'content-type': '', 'content-length': responseBody.length },
-    };
-    await exports.external.sendHttpRequest(req, responseBody);
-}
-async function defaultSendHttpRequest(options, responseBody) {
-    return new Promise((resolve, reject) => {
-        try {
-            const request = https.request(options, _ => resolve());
-            request.on('error', reject);
-            request.write(responseBody);
-            request.end();
-        }
-        catch (e) {
-            reject(e);
-        }
-    });
-}
-function defaultLog(fmt, ...params) {
-    // eslint-disable-next-line no-console
-    console.log(fmt, ...params);
-}
-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtEO0lBQzlFLGdCQUFRLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRWxELHVFQUF1RTtJQUN2RSx1RUFBdUU7SUFDdkUsYUFBYTtJQUNiLElBQUksS0FBSyxDQUFDLFdBQVcsS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLGtCQUFrQixLQUFLLGdDQUFnQyxFQUFFO1FBQ25HLGdCQUFRLENBQUMsR0FBRyxDQUFDLHVEQUF1RCxDQUFDLENBQUM7UUFDdEUsTUFBTSxjQUFjLENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ3ZDLE9BQU87S0FDUjtJQUVELElBQUk7UUFDRix5RUFBeUU7UUFDekUsaUVBQWlFO1FBQ2pFLHdDQUF3QztRQUN4QyxpRUFBaUU7UUFDakUsTUFBTSxXQUFXLEdBQVksT0FBTyxDQUFDLGdCQUFRLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxPQUFPLENBQUM7UUFDeEUsTUFBTSxNQUFNLEdBQUcsTUFBTSxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUM7UUFFeEMsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbERELDBCQWtEQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRzs7SUFFN0Msc0VBQXNFO0lBQ3RFLHVCQUF1QjtJQUN2QixNQUFNLGtCQUFrQixlQUFHLGVBQWUsQ0FBQyxrQkFBa0IsbUNBQUksVUFBVSxDQUFDLGtCQUFrQixtQ0FBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7O0lBQ3pFLE1BQU0sSUFBSSxHQUFtRDtRQUMzRCxNQUFNLEVBQUUsTUFBTTtRQUNkLE1BQU0sUUFBRSxLQUFLLENBQUMsTUFBTSxtQ0FBSSxNQUFNO1FBQzlCLE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTztRQUN0QixTQUFTLEVBQUUsS0FBSyxDQUFDLFNBQVM7UUFDMUIsa0JBQWtCLEVBQUUsS0FBSyxDQUFDLGtCQUFrQixJQUFJLDBCQUEwQjtRQUMxRSxpQkFBaUIsRUFBRSxLQUFLLENBQUMsaUJBQWlCO1FBQzFDLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTTtRQUNwQixJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUk7S0FDakIsQ0FBQztJQUVGLGdCQUFRLENBQUMsR0FBRyxDQUFDLG1DQUFtQyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBRXhELE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDMUMsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDL0MsTUFBTSxHQUFHLEdBQUc7UUFDVixRQUFRLEVBQUUsU0FBUyxDQUFDLFFBQVE7UUFDNUIsSUFBSSxFQUFFLFNBQVMsQ0FBQyxJQUFJO1FBQ3BCLE1BQU0sRUFBRSxLQUFLO1FBQ2IsT0FBTyxFQUFFLEVBQUUsY0FBYyxFQUFFLEVBQUUsRUFBRSxnQkFBZ0IsRUFBRSxZQUFZLENBQUMsTUFBTSxFQUFFO0tBQ3ZFLENBQUM7SUFFRixNQUFNLGdCQUFRLENBQUMsZUFBZSxDQUFDLEdBQUcsRUFBRSxZQUFZLENBQUMsQ0FBQztBQUNwRCxDQUFDO0FBRUQsS0FBSyxVQUFVLHNCQUFzQixDQUFDLE9BQTZCLEVBQUUsWUFBb0I7SUFDdkYsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtRQUNyQyxJQUFJO1lBQ0YsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQ3ZELE9BQU8sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUM7WUFDNUIsT0FBTyxDQUFDLEdBQUcsRUFBRSxDQUFDO1NBQ2Y7UUFBQyxPQUFPLENBQUMsRUFBRTtZQUNWLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztTQUNYO0lBQ0gsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDO0FBRUQsU0FBUyxVQUFVLENBQUMsR0FBVyxFQUFFLEdBQUcsTUFBYTtJQUMvQyxzQ0FBc0M7SUFDdEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxNQUFNLENBQUMsQ0FBQztBQUM5QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgaHR0cHMgZnJvbSAnaHR0cHMnO1xuaW1wb3J0ICogYXMgdXJsIGZyb20gJ3VybCc7XG5cbi8vIGZvciB1bml0IHRlc3RzXG5leHBvcnQgY29uc3QgZXh0ZXJuYWwgPSB7XG4gIHNlbmRIdHRwUmVxdWVzdDogZGVmYXVsdFNlbmRIdHRwUmVxdWVzdCxcbiAgbG9nOiBkZWZhdWx0TG9nLFxuICBpbmNsdWRlU3RhY2tUcmFjZXM6IHRydWUsXG4gIHVzZXJIYW5kbGVySW5kZXg6ICcuL2luZGV4Jyxcbn07XG5cbmNvbnN0IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6Q1JFQVRFX0ZBSUxFRCc7XG5jb25zdCBNSVNTSU5HX1BIWVNJQ0FMX0lEX01BUktFUiA9ICdBV1NDREs6OkN1c3RvbVJlc291cmNlUHJvdmlkZXJGcmFtZXdvcms6Ok1JU1NJTkdfUEhZU0lDQUxfSUQnO1xuXG5leHBvcnQgdHlwZSBSZXNwb25zZSA9IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiBIYW5kbGVyUmVzcG9uc2U7XG5leHBvcnQgdHlwZSBIYW5kbGVyID0gKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGV4dGVybmFsLmxvZyhKU09OLnN0cmluZ2lmeShldmVudCwgdW5kZWZpbmVkLCAyKSk7XG5cbiAgLy8gaWdub3JlIERFTEVURSBldmVudCB3aGVuIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBpcyB0aGUgbWFya2VyIHRoYXRcbiAgLy8gaW5kaWNhdGVzIHRoYXQgdGhpcyBERUxFVEUgaXMgYSBzdWJzZXF1ZW50IERFTEVURSB0byBhIGZhaWxlZCBDUkVBVEVcbiAgLy8gb3BlcmF0aW9uLlxuICBpZiAoZXZlbnQuUmVxdWVzdFR5cGUgPT09ICdEZWxldGUnICYmIGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCA9PT0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIpIHtcbiAgICBleHRlcm5hbC5sb2coJ2lnbm9yaW5nIERFTEVURSBldmVudCBjYXVzZWQgYnkgYSBmYWlsZWQgQ1JFQVRFIGV2ZW50Jyk7XG4gICAgYXdhaXQgc3VibWl0UmVzcG9uc2UoJ1NVQ0NFU1MnLCBldmVudCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICAvLyBpbnZva2UgdGhlIHVzZXIgaGFuZGxlci4gdGhpcyBpcyBpbnRlbnRpb25hbGx5IGluc2lkZSB0aGUgdHJ5LWNhdGNoIHRvXG4gICAgLy8gZW5zdXJlIHRoYXQgaWYgdGhlcmUgaXMgYW4gZXJyb3IgaXQncyByZXBvcnRlZCBhcyBhIGZhaWx1cmUgdG9cbiAgICAvLyBjbG91ZGZvcm1hdGlvbiAob3RoZXJ3aXNlIGNmbiB3YWl0cykuXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1yZXF1aXJlLWltcG9ydHNcbiAgICBjb25zdCB1c2VySGFuZGxlcjogSGFuZGxlciA9IHJlcXVpcmUoZXh0ZXJuYWwudXNlckhhbmRsZXJJbmRleCkuaGFuZGxlcjtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB1c2VySGFuZGxlcihldmVudCk7XG5cbiAgICAvLyB2YWxpZGF0ZSB1c2VyIHJlc3BvbnNlIGFuZCBjcmVhdGUgdGhlIGNvbWJpbmVkIGV2ZW50XG4gICAgY29uc3QgcmVzcG9uc2VFdmVudCA9IHJlbmRlclJlc3BvbnNlKGV2ZW50LCByZXN1bHQpO1xuXG4gICAgLy8gc3VibWl0IHRvIGNmbiBhcyBzdWNjZXNzXG4gICAgYXdhaXQgc3VibWl0UmVzcG9uc2UoJ1NVQ0NFU1MnLCByZXNwb25zZUV2ZW50KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnN0IHJlc3A6IFJlc3BvbnNlID0ge1xuICAgICAgLi4uZXZlbnQsXG4gICAgICBSZWFzb246IGV4dGVybmFsLmluY2x1ZGVTdGFja1RyYWNlcyA/IGUuc3RhY2sgOiBlLm1lc3NhZ2UsXG4gICAgfTtcblxuICAgIGlmICghcmVzcC5QaHlzaWNhbFJlc291cmNlSWQpIHtcbiAgICAgIC8vIHNwZWNpYWwgY2FzZTogaWYgQ1JFQVRFIGZhaWxzLCB3aGljaCB1c3VhbGx5IGltcGxpZXMsIHdlIHVzdWFsbHkgZG9uJ3RcbiAgICAgIC8vIGhhdmUgYSBwaHlzaWNhbCByZXNvdXJjZSBpZC4gaW4gdGhpcyBjYXNlLCB0aGUgc3Vic2VxdWVudCBERUxFVEVcbiAgICAgIC8vIG9wZXJhdGlvbiBkb2VzIG5vdCBoYXZlIGFueSBtZWFuaW5nLCBhbmQgd2lsbCBsaWtlbHkgZmFpbCBhcyB3ZWxsLiB0b1xuICAgICAgLy8gYWRkcmVzcyB0aGlzLCB3ZSB1c2UgYSBtYXJrZXIgc28gdGhlIHByb3ZpZGVyIGZyYW1ld29yayBjYW4gc2ltcGx5XG4gICAgICAvLyBpZ25vcmUgdGhlIHN1YnNlcXVlbnQgREVMRVRFLlxuICAgICAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnQ3JlYXRlJykge1xuICAgICAgICBleHRlcm5hbC5sb2coJ0NSRUFURSBmYWlsZWQsIHJlc3BvbmRpbmcgd2l0aCBhIG1hcmtlciBwaHlzaWNhbCByZXNvdXJjZSBpZCBzbyB0aGF0IHRoZSBzdWJzZXF1ZW50IERFTEVURSB3aWxsIGJlIGlnbm9yZWQnKTtcbiAgICAgICAgcmVzcC5QaHlzaWNhbFJlc291cmNlSWQgPSBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUjtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIG90aGVyd2lzZSwgaWYgUGh5c2ljYWxSZXNvdXJjZUlkIGlzIG5vdCBzcGVjaWZpZWQsIHNvbWV0aGluZyBpc1xuICAgICAgICAvLyB0ZXJyaWJseSB3cm9uZyBiZWNhdXNlIGFsbCBvdGhlciBldmVudHMgc2hvdWxkIGhhdmUgYW4gSUQuXG4gICAgICAgIGV4dGVybmFsLmxvZyhgRVJST1I6IE1hbGZvcm1lZCBldmVudC4gXCJQaHlzaWNhbFJlc291cmNlSWRcIiBpcyByZXF1aXJlZDogJHtKU09OLnN0cmluZ2lmeShldmVudCl9YCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gdGhpcyBpcyBhbiBhY3R1YWwgZXJyb3IsIGZhaWwgdGhlIGFjdGl2aXR5IGFsdG9nZXRoZXIgYW5kIGV4aXN0LlxuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdGQUlMRUQnLCByZXNwKTtcbiAgfVxufVxuXG5mdW5jdGlvbiByZW5kZXJSZXNwb25zZShcbiAgY2ZuUmVxdWVzdDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIHsgUGh5c2ljYWxSZXNvdXJjZUlkPzogc3RyaW5nIH0sXG4gIGhhbmRsZXJSZXNwb25zZTogdm9pZCB8IEhhbmRsZXJSZXNwb25zZSA9IHsgfSk6IFJlc3BvbnNlIHtcblxuICAvLyBpZiBwaHlzaWNhbCBJRCBpcyBub3QgcmV0dXJuZWQsIHdlIGhhdmUgc29tZSBkZWZhdWx0cyBmb3IgeW91IGJhc2VkXG4gIC8vIG9uIHRoZSByZXF1ZXN0IHR5cGUuXG4gIGNvbnN0IHBoeXNpY2FsUmVzb3VyY2VJZCA9IGhhbmRsZXJSZXNwb25zZS5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5SZXF1ZXN0SWQ7XG5cbiAgLy8gaWYgd2UgYXJlIGluIERFTEVURSBhbmQgcGh5c2ljYWwgSUQgd2FzIGNoYW5nZWQsIGl0J3MgYW4gZXJyb3IuXG4gIGlmIChjZm5SZXF1ZXN0LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBwaHlzaWNhbFJlc291cmNlSWQgIT09IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBERUxFVEU6IGNhbm5vdCBjaGFuZ2UgdGhlIHBoeXNpY2FsIHJlc291cmNlIElEIGZyb20gXCIke2NmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIHRvIFwiJHtoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIGR1cmluZyBkZWxldGlvbmApO1xuICB9XG5cbiAgLy8gbWVyZ2UgcmVxdWVzdCBldmVudCBhbmQgcmVzdWx0IGV2ZW50IChyZXN1bHQgcHJldmFpbHMpLlxuICByZXR1cm4ge1xuICAgIC4uLmNmblJlcXVlc3QsXG4gICAgLi4uaGFuZGxlclJlc3BvbnNlLFxuICAgIFBoeXNpY2FsUmVzb3VyY2VJZDogcGh5c2ljYWxSZXNvdXJjZUlkLFxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzdWJtaXRSZXNwb25zZShzdGF0dXM6ICdTVUNDRVNTJyB8ICdGQUlMRUQnLCBldmVudDogUmVzcG9uc2UpIHtcbiAgY29uc3QganNvbjogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VSZXNwb25zZSA9IHtcbiAgICBTdGF0dXM6IHN0YXR1cyxcbiAgICBSZWFzb246IGV2ZW50LlJlYXNvbiA/PyBzdGF0dXMsXG4gICAgU3RhY2tJZDogZXZlbnQuU3RhY2tJZCxcbiAgICBSZXF1ZXN0SWQ6IGV2ZW50LlJlcXVlc3RJZCxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCB8fCBNSVNTSU5HX1BIWVNJQ0FMX0lEX01BUktFUixcbiAgICBMb2dpY2FsUmVzb3VyY2VJZDogZXZlbnQuTG9naWNhbFJlc291cmNlSWQsXG4gICAgTm9FY2hvOiBldmVudC5Ob0VjaG8sXG4gICAgRGF0YTogZXZlbnQuRGF0YSxcbiAgfTtcblxuICBleHRlcm5hbC5sb2coJ3N1Ym1pdCByZXNwb25zZSB0byBjbG91ZGZvcm1hdGlvbicsIGpzb24pO1xuXG4gIGNvbnN0IHJlc3BvbnNlQm9keSA9IEpTT04uc3RyaW5naWZ5KGpzb24pO1xuICBjb25zdCBwYXJzZWRVcmwgPSB1cmwucGFyc2UoZXZlbnQuUmVzcG9uc2VVUkwpO1xuICBjb25zdCByZXEgPSB7XG4gICAgaG9zdG5hbWU6IHBhcnNlZFVybC5ob3N0bmFtZSxcbiAgICBwYXRoOiBwYXJzZWRVcmwucGF0aCxcbiAgICBtZXRob2Q6ICdQVVQnLFxuICAgIGhlYWRlcnM6IHsgJ2NvbnRlbnQtdHlwZSc6ICcnLCAnY29udGVudC1sZW5ndGgnOiByZXNwb25zZUJvZHkubGVuZ3RoIH0sXG4gIH07XG5cbiAgYXdhaXQgZXh0ZXJuYWwuc2VuZEh0dHBSZXF1ZXN0KHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG4iXX0=
\ No newline at end of file
diff --git a/packages/cdk-dynamodb-seeder/src/__tests__/mocks/dynamodb-seeder/index.js b/packages/cdk-dynamodb-seeder/src/__tests__/mocks/dynamodb-seeder/index.js
deleted file mode 100644
index bfa173f30..000000000
--- a/packages/cdk-dynamodb-seeder/src/__tests__/mocks/dynamodb-seeder/index.js
+++ /dev/null
@@ -1,1 +0,0 @@
-// mock
\ No newline at end of file