From 078015520d248d80a8e1697aa0a86b3eedea8c3e Mon Sep 17 00:00:00 2001 From: Alireza Assadzadeh Date: Mon, 22 Jun 2020 13:46:19 -0400 Subject: [PATCH] Update to version v1.46.0 --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- CHANGELOG.md | 63 +- CONTRIBUTING.md | 173 +++++- NOTICE.txt | 2 +- README.md | 37 +- source/lerna.json | 5 +- source/package.json | 19 +- .../aws-apigateway-dynamodb/.eslintignore | 0 .../aws-apigateway-dynamodb/.gitignore | 0 .../aws-apigateway-dynamodb/.npmignore | 0 .../aws-apigateway-dynamodb/README.md | 42 +- .../aws-apigateway-dynamodb/architecture.png | Bin .../aws-apigateway-dynamodb/lib/index.ts | 52 +- .../aws-apigateway-dynamodb/package.json | 42 +- .../apigateway-dynamodb.test.js.snap | 0 .../test/apigateway-dynamodb.test.ts | 7 +- ...teg.apigateway-dynamodb-CRUD.expected.json | 0 .../test/integ.apigateway-dynamodb-CRUD.ts | 0 .../test/integ.no-arguments.expected.json | 0 .../test/integ.no-arguments.ts | 0 .../aws-apigateway-lambda/.eslintignore | 0 .../aws-apigateway-lambda/.gitignore | 0 .../aws-apigateway-lambda/.npmignore | 0 .../aws-apigateway-lambda/README.md | 31 +- .../aws-apigateway-lambda/architecture.png | Bin .../aws-apigateway-lambda/lib/index.ts | 31 +- .../aws-apigateway-lambda/package.json | 42 +- .../test.apigateway-lambda.test.js.snap | 16 +- .../test/integ.deployFunction.expected.json | 4 +- .../test/integ.deployFunction.ts | 0 .../test/integ.existingFunction.expected.json | 4 +- .../test/integ.existingFunction.ts | 2 +- .../test/lambda/index.js | 0 .../test/test.apigateway-lambda.test.ts | 8 +- .../aws-apigateway-sqs/.eslintignore | 0 .../aws-apigateway-sqs/.gitignore | 0 .../aws-apigateway-sqs/.npmignore | 0 .../aws-apigateway-sqs/README.md | 40 +- .../aws-apigateway-sqs/architecture.png | Bin .../aws-apigateway-sqs/lib/index.ts | 53 +- .../aws-apigateway-sqs/package.json | 47 +- .../__snapshots__/apigateway-sqs.test.js.snap | 297 +--------- .../test/apigateway-sqs.test.ts | 11 +- .../integ.apigateway-sqs-crud.expected.json | 99 +--- .../test/integ.apigateway-sqs-crud.ts | 1 - .../test/integ.no-arguments.expected.json | 99 +--- .../test/integ.no-arguments.ts | 3 +- .../.eslintignore | 0 .../.gitignore | 0 .../.npmignore | 0 .../README.md | 43 +- .../architecture.png | Bin .../lib/index.ts | 50 +- .../package.json | 50 +- ....cloudfront-apigateway-lambda.test.js.snap | 4 +- .../test/integ.no-arguments.expected.json | 4 +- .../test/integ.no-arguments.ts | 0 .../test/lambda/index.js | 0 .../test.cloudfront-apigateway-lambda.test.ts | 8 +- .../aws-cloudfront-apigateway/.eslintignore | 0 .../aws-cloudfront-apigateway/.gitignore | 0 .../aws-cloudfront-apigateway/.npmignore | 0 .../aws-cloudfront-apigateway/README.md | 34 +- .../architecture.png | Bin .../aws-cloudfront-apigateway/lib/index.ts | 32 +- .../aws-cloudfront-apigateway/package.json | 46 +- .../test.cloudfront-apigateway.test.js.snap | 4 +- .../test/integ.no-arguments.expected.json | 4 +- .../test/integ.no-arguments.ts | 2 +- .../test/lambda/index.js | 0 .../test/test.cloudfront-apigateway.test.ts | 6 +- .../aws-cloudfront-s3/.eslintignore | 0 .../aws-cloudfront-s3/.gitignore | 0 .../aws-cloudfront-s3/.npmignore | 0 .../aws-cloudfront-s3/README.md | 36 +- .../aws-cloudfront-s3/architecture.png | Bin .../aws-cloudfront-s3/lib/index.ts | 30 +- .../aws-cloudfront-s3/package.json | 38 +- .../test.cloudfront-s3.test.js.snap | 0 .../test/integ.no-arguments.expected.json | 0 .../test/integ.no-arguments.ts | 0 .../integ.no-security-headers.expected.json | 0 .../test/integ.no-security-headers.ts | 0 .../test/test.cloudfront-s3.test.ts | 6 +- .../.eslintignore | 0 .../aws-cognito-apigateway-lambda/.gitignore | 0 .../aws-cognito-apigateway-lambda/.npmignore | 0 .../aws-cognito-apigateway-lambda/README.md | 39 +- .../architecture.png | Bin .../lib/index.ts | 64 +-- .../package.json | 42 +- ...est.cognito-apigateway-lambda.test.js.snap | 34 +- .../test/integ.no-arguments.expected.json | 36 +- .../test/integ.no-arguments.ts | 0 .../test/lambda/index.js | 0 .../test.cognito-apigateway-lambda.test.ts | 10 +- .../.eslintignore | 0 .../.gitignore | 0 .../.npmignore | 0 .../README.md | 52 +- .../architecture.png | Bin .../lib/index.ts | 91 +-- .../package.json | 91 +++ ...m-lambda-elasticsearch-kibana.test.js.snap | 30 + ...stream-lambda-elasticsearch-kibana.test.ts | 19 +- .../test/integ.no-arguments.expected.json | 42 +- .../test/integ.no-arguments.ts | 2 +- .../test/lambda/index.js | 0 .../aws-dynamodb-stream-lambda/.eslintignore | 0 .../aws-dynamodb-stream-lambda/.gitignore | 0 .../aws-dynamodb-stream-lambda/.npmignore | 0 .../aws-dynamodb-stream-lambda/README.md | 31 +- .../architecture.png | Bin .../aws-dynamodb-stream-lambda/lib/index.ts | 39 +- .../aws-dynamodb-stream-lambda/package.json | 42 +- .../dynamodb-stream-lambda.test.js.snap | 0 .../test/dynamodb-stream-lambda.test.ts | 4 +- .../test/integ.no-arguments.expected.json | 0 .../test/integ.no-arguments.ts | 0 .../test/lambda/index.js | 0 .../aws-events-rule-lambda/.eslintignore | 0 .../aws-events-rule-lambda/.gitignore | 0 .../aws-events-rule-lambda/.npmignore | 0 .../aws-events-rule-lambda/README.md | 28 +- .../aws-events-rule-lambda/architecture.png | Bin .../aws-events-rule-lambda/lib/index.ts | 38 +- .../aws-events-rule-lambda/package.json | 42 +- .../events-rule-lambda.test.js.snap | 0 .../test/events-rule-lambda.test.ts | 6 +- ...nteg.events-rule-no-argument.expected.json | 0 .../test/integ.events-rule-no-argument.ts | 0 .../test/lambda/index.js | 0 .../.eslintignore | 0 .../aws-events-rule-step-function}/.gitignore | 0 .../aws-events-rule-step-function}/.npmignore | 0 .../aws-events-rule-step-function/README.md | 88 +++ .../architecture.png | Bin 0 -> 54507 bytes .../lib/index.ts | 84 +++ .../package.json | 87 +++ .../events-rule-step-function.test.js.snap | 257 +++++++++ .../test/events-rule-step-function.test.ts | 98 ++++ ...le-step-function-no-argument.expected.json | 253 +++++++++ ...g.events-rule-step-function-no-argument.ts | 36 ++ ...le-step-function-with-lambda.expected.json | 412 ++++++++++++++ ...g.events-rule-step-function-with-lambda.ts | 50 ++ .../test/lambda/index.js | 0 .../aws-iot-kinesisfirehose-s3}/.eslintignore | 0 .../aws-iot-kinesisfirehose-s3}/.gitignore | 0 .../aws-iot-kinesisfirehose-s3}/.npmignore | 0 .../aws-iot-kinesisfirehose-s3/README.md | 39 +- .../architecture.png | Bin .../aws-iot-kinesisfirehose-s3/lib/index.ts | 60 +- .../aws-iot-kinesisfirehose-s3/package.json | 50 +- .../test.iot-kinesisfirehose-s3.test.js.snap | 0 .../test/integ.no-arguments.expected.json | 0 .../test/integ.no-arguments.ts | 0 .../test/test.iot-kinesisfirehose-s3.test.ts | 11 +- .../aws-iot-lambda-dynamodb}/.eslintignore | 0 .../aws-iot-lambda-dynamodb}/.gitignore | 0 .../aws-iot-lambda-dynamodb}/.npmignore | 0 .../aws-iot-lambda-dynamodb/README.md | 36 +- .../aws-iot-lambda-dynamodb/architecture.png | Bin .../aws-iot-lambda-dynamodb/lib/index.ts | 48 +- .../aws-iot-lambda-dynamodb/package.json | 50 +- .../iot-lambda-dynamodb.test.js.snap | 0 .../integ.iot-lambda-dynamodb.expected.json | 0 .../test/integ.iot-lambda-dynamodb.ts | 0 .../test/iot-lambda-dynamodb.test.ts | 8 +- .../test/lambda/index.js | 0 .../aws-iot-lambda}/.eslintignore | 0 .../aws-iot-lambda}/.gitignore | 0 .../aws-iot-lambda}/.npmignore | 0 .../aws-iot-lambda/README.md | 28 +- .../aws-iot-lambda/architecture.png | Bin .../aws-iot-lambda/lib/index.ts | 39 +- .../aws-iot-lambda/package.json | 42 +- .../__snapshots__/iot-lambda.test.js.snap | 0 .../integ.iot-lambda-new-func.expected.json | 0 .../test/integ.iot-lambda-new-func.ts | 0 ...iot-lambda-use-existing-func.expected.json | 0 .../integ.iot-lambda-use-existing-func.ts | 2 +- .../aws-iot-lambda/test/iot-lambda.test.ts | 7 +- .../aws-iot-lambda}/test/lambda/index.js | 0 .../.eslintignore | 0 .../.gitignore | 0 .../.npmignore | 0 .../README.md | 39 +- .../architecture.png | Bin .../lib/index.ts | 48 +- .../package.json | 54 +- ....kinesisfirehose-analytics-s3.test.js.snap | 0 .../test/integ.no-arguments.expected.json | 0 .../test/integ.no-arguments.ts | 0 .../test/lambda/index.js | 0 .../test.kinesisfirehose-analytics-s3.test.ts | 8 +- .../aws-kinesisfirehose-s3}/.eslintignore | 0 .../aws-kinesisfirehose-s3}/.gitignore | 0 .../aws-kinesisfirehose-s3}/.npmignore | 0 .../aws-kinesisfirehose-s3/README.md | 33 +- .../aws-kinesisfirehose-s3/architecture.png | Bin .../aws-kinesisfirehose-s3/lib/index.ts | 35 +- .../aws-kinesisfirehose-s3/package.json | 46 +- .../test.kinesisfirehose-s3.test.js.snap | 0 .../test/integ.no-arguments.expected.json | 0 .../test/integ.no-arguments.ts | 0 .../test/test.kinesisfirehose-s3.test.ts | 8 +- .../aws-kinesisstreams-lambda}/.eslintignore | 0 .../aws-kinesisstreams-lambda/.gitignore | 0 .../aws-kinesisstreams-lambda}/.npmignore | 0 .../aws-kinesisstreams-lambda/README.md | 31 +- .../architecture.png | Bin .../aws-kinesisstreams-lambda/lib/index.ts | 54 +- .../aws-kinesisstreams-lambda/package.json | 46 +- .../test.kinesisstreams-lambda.test.js.snap | 82 +-- .../test/integ.deployFunction.expected.json | 82 +-- .../test/integ.deployFunction.ts | 1 - .../test/lambda/index.js | 0 .../test/test.kinesisstreams-lambda.test.ts | 9 +- .../aws-lambda-dynamodb}/.eslintignore | 0 .../aws-lambda-dynamodb}/.gitignore | 0 .../aws-lambda-dynamodb}/.npmignore | 0 .../aws-lambda-dynamodb/README.md | 31 +- .../aws-lambda-dynamodb/architecture.png | Bin .../aws-lambda-dynamodb/lib/index.ts | 41 +- .../aws-lambda-dynamodb/package.json | 38 +- .../lambda-dynamodb.test.js.snap | 0 .../integ.add-secondary-index.expected.json | 0 .../test/integ.add-secondary-index.ts | 2 +- .../test/integ.no-arguments.expected.json | 0 .../test/integ.no-arguments.ts | 0 .../test/integ.set-billing-mode.expected.json | 0 .../test/integ.set-billing-mode.ts | 0 .../integ.use-existing-func.expected.json | 0 .../test/integ.use-existing-func.ts | 2 +- .../test/lambda-dynamodb.test.ts | 6 +- .../aws-lambda-dynamodb/test/lambda/index.js | 0 .../.eslintignore | 0 .../.gitignore | 0 .../.npmignore | 0 .../aws-lambda-elasticsearch-kibana/README.md | 44 +- .../architecture.png | Bin .../lib/index.ts | 100 +--- .../package.json | 50 +- .../lambda-elasticsearch-kibana.test.js.snap | 30 + .../test/integ.no-arguments.expected.json | 42 +- .../test/integ.no-arguments.ts | 2 +- .../test/lambda-elasticsearch-kibana.test.ts | 16 +- .../test/lambda/index.js | 0 .../aws-lambda-s3}/.eslintignore | 0 .../aws-lambda-s3/.gitignore | 0 .../aws-lambda-s3}/.npmignore | 0 .../aws-lambda-s3/README.md | 33 +- .../aws-lambda-s3/architecture.png | Bin .../aws-lambda-s3/lib/index.ts | 47 +- .../aws-lambda-s3/package.json | 38 +- .../test/__snapshots__/lambda-s3.test.js.snap | 0 .../test/integ.deployFunction.expected.json | 0 .../test/integ.deployFunction.ts | 0 .../test/integ.existingFunction.expected.json | 0 .../test/integ.existingFunction.ts | 2 +- .../aws-lambda-s3/test/lambda-s3.test.ts | 10 +- .../aws-lambda-s3/test/lambda/index.js | 0 .../aws-lambda-sns}/.eslintignore | 0 .../aws-lambda-sns/.gitignore | 0 .../aws-lambda-sns}/.npmignore | 0 .../aws-lambda-sns/README.md | 29 +- .../aws-lambda-sns/architecture.png | Bin .../aws-lambda-sns/lib/index.ts | 37 +- .../aws-lambda-sns/package.json | 42 +- .../__snapshots__/lambda-sns.test.js.snap | 0 .../test/integ.deployFunction.expected.json | 0 .../test/integ.deployFunction.ts | 0 .../test/integ.existingFunction.expected.json | 0 .../test/integ.existingFunction.ts | 2 +- .../aws-lambda-sns/test/lambda-sns.test.ts | 10 +- .../aws-lambda-sns/test/lambda/index.js | 0 .../aws-s3-lambda}/.eslintignore | 0 .../aws-s3-lambda}/.gitignore | 0 .../aws-s3-lambda}/.npmignore | 0 .../aws-s3-lambda/README.md | 32 +- .../aws-s3-lambda/architecture.png | Bin .../aws-s3-lambda/lib/index.ts | 35 +- .../aws-s3-lambda/package.json | 50 +- .../test/__snapshots__/s3-lambda.test.js.snap | 0 .../integ.existing-s3-bucket.expected.json | 0 .../test/integ.existing-s3-bucket.ts | 2 +- .../test/integ.no-arguments.expected.json | 0 .../aws-s3-lambda/test/integ.no-arguments.ts | 0 .../aws-s3-lambda/test/lambda/index.js | 0 .../aws-s3-lambda/test/s3-lambda.test.ts | 7 +- .../aws-s3-step-function}/.eslintignore | 0 .../aws-s3-step-function/.gitignore | 16 + .../aws-s3-step-function}/.npmignore | 0 .../aws-s3-step-function/README.md | 100 ++++ .../aws-s3-step-function/architecture.png | Bin 0 -> 65426 bytes .../aws-s3-step-function/lib/index.ts | 160 ++++++ .../aws-s3-step-function/package.json | 93 +++ .../s3-step-function.test.js.snap | 537 ++++++++++++++++++ ...s3-step-function-no-argument.expected.json | 533 +++++++++++++++++ .../integ.s3-step-function-no-argument.ts | 31 + .../aws-s3-step-function/test/lambda/index.js | 10 + .../test/s3-step-function.test.ts | 143 +++++ .../aws-sns-lambda/.eslintignore | 5 + .../aws-sns-lambda/.gitignore | 16 + .../aws-sns-lambda}/.npmignore | 0 .../aws-sns-lambda/README.md | 29 +- .../aws-sns-lambda/architecture.png | Bin .../aws-sns-lambda/lib/index.ts | 34 +- .../aws-sns-lambda/package.json | 50 +- .../__snapshots__/sns-lambda.test.js.snap | 0 .../test/integ.no-arguments.expected.json | 0 .../aws-sns-lambda/test/integ.no-arguments.ts | 0 .../aws-sns-lambda/test/lambda/index.js | 0 .../aws-sns-lambda/test/sns-lambda.test.ts | 7 +- .../aws-sqs-lambda/.eslintignore | 5 + .../aws-sqs-lambda/.gitignore | 0 .../aws-sqs-lambda/.npmignore | 21 + .../aws-sqs-lambda/README.md | 32 +- .../aws-sqs-lambda/architecture.png | Bin .../aws-sqs-lambda/lib/index.ts | 48 +- .../aws-sqs-lambda/package.json | 47 +- .../test.sqs-lambda.test.js.snap | 171 +----- .../test/integ.deployFunction.expected.json | 89 +-- .../test/integ.deployFunction.ts | 3 +- .../test/integ.existingFunction.expected.json | 89 +-- .../test/integ.existingFunction.ts | 5 +- .../aws-sqs-lambda/test/lambda/index.js | 0 .../test/test.sqs-lambda.test.ts | 13 +- .../core/.eslintignore | 0 .../core/.gitignore | 0 .../@aws-solutions-constructs/core/.npmignore | 21 + .../@aws-solutions-constructs/core/README.md | 81 +++ .../core/index.ts | 4 +- .../core/lib/apigateway-defaults.ts | 4 +- .../core/lib/apigateway-helper.ts | 0 .../lib/cloudfront-distribution-defaults.ts | 0 .../lib/cloudfront-distribution-helper.ts | 0 .../core/lib/cloudwatch-log-group-defaults.ts | 0 .../core/lib/cognito-defaults.ts | 0 .../core/lib/cognito-helper.ts | 0 .../core/lib/dynamodb-table-defaults.ts | 4 +- .../core/lib/elasticsearch-defaults.ts | 0 .../core/lib/elasticsearch-helper.ts | 0 .../core/lib/events-rule-defaults.ts | 0 .../core/lib/iot-topic-rule-defaults.ts | 0 .../core/lib/kinesis-analytics-defaults.ts | 0 .../core/lib/kinesis-analytics-helper.ts | 0 .../core/lib/kinesis-firehose-s3-defaults.ts | 0 .../core/lib/kinesis-streams-defaults.ts | 2 +- .../core/lib/kinesis-streams-helper.ts | 12 +- .../core/lib/kms-defaults.ts | 0 .../core/lib/kms-helper.ts | 0 .../core/lib/lambda-defaults.ts | 0 .../lambda-event-source-mapping-defaults.ts | 0 .../core/lib/lambda-helper.ts | 0 .../core/lib/override-warning-service.ts | 4 +- .../core/lib/s3-bucket-defaults.ts | 0 .../core/lib/s3-bucket-helper.ts | 14 +- .../core/lib/sns-defaults.ts | 0 .../core/lib/sns-helper.ts | 0 .../core/lib/sqs-defaults.ts | 6 +- .../core/lib/sqs-helper.ts | 13 +- .../core/lib/step-function-defaults.ts | 27 + .../core/lib/step-function-helper.ts | 119 ++++ .../core/lib/utils.ts | 0 .../core/package.json | 122 ++++ .../apigateway-helper.test.js.snap | 4 +- ...stribution-api-gateway-helper.test.js.snap | 4 +- ...dfront-distribution-s3-helper.test.js.snap | 0 .../cloudwatch-log-group.test.js.snap | 0 .../congnito-helper.test.js.snap | 42 ++ .../__snapshots__/dynamo-table.test.js.snap | 0 .../elasticsearch-helper.test.js.snap | 30 + .../__snapshots__/events-rule.test.js.snap | 0 .../test/__snapshots__/iot-rule.test.js.snap | 0 .../kinesis-analytics-helper.test.js.snap | 0 .../kinesis-analytics.test.js.snap | 0 .../kinesis-firehose-s3-defaults.test.js.snap | 0 .../kinesis-streams-defaults.test.js.snap | 19 + .../kinesis-streams-helper.test.js.snap} | 57 +- .../__snapshots__/kms-helper.test.js.snap | 0 .../__snapshots__/lambda-func.test.js.snap | 0 .../s3-bucket-helper.test.js.snap | 0 .../test/__snapshots__/s3-bucket.test.js.snap | 0 .../__snapshots__/sns-helper.test.js.snap | 0 .../__snapshots__/sqs-helper.test.js.snap} | 75 ++- .../step-function-helper.test.js.snap | 133 +++++ .../core/test/apigateway-helper.test.ts | 0 ...nt-distribution-api-gateway-helper.test.ts | 0 .../cloudfront-distribution-s3-helper.test.ts | 0 .../core/test/cloudwatch-log-group.test.ts | 0 .../core/test/congnito-helper.test.ts | 0 .../core/test/dynamo-table.test.ts | 0 .../core/test/elasticsearch-helper.test.ts | 0 .../core/test/events-rule.test.ts | 0 .../core/test/iot-rule.test.ts | 0 .../test/kinesis-analytics-helper.test.ts | 0 .../core/test/kinesis-analytics.test.ts | 0 .../test/kinesis-firehose-s3-defaults.test.ts | 0 .../test/kinesis-streams-defaults.test.ts | 0 .../core/test/kinesis-streams-helper.test.ts | 6 +- .../core/test/kms-helper.test.ts | 0 .../core/test/lambda-event-source.test.ts | 0 .../core/test/lambda-func.test.ts | 0 .../core/test/lambda-test/index.js | 0 .../core/test/lambda/index.js | 10 + .../test/override-warning-service.test.ts | 0 .../core/test/s3-bucket-helper.test.ts | 0 .../core/test/s3-bucket.test.ts | 0 .../core/test/sns-helper.test.ts | 0 .../core/test/sqs-helper.test.ts | 11 +- .../core/test/step-function-helper.test.ts | 165 ++++++ .../eslintrc.yml | 0 .../license-header.js | 0 .../package.json | 91 --- .../@aws-solutions-konstruk/core/README.md | 69 --- .../@aws-solutions-konstruk/core/package.json | 120 ---- .../__snapshots__/sqs-helper.test.js.snap | 283 --------- source/tools/cdk-integ-tools/package.json | 6 +- .../aws-s3-static-website/architecture.png | Bin 172868 -> 184640 bytes .../lib/s3-static-site-stack.ts | 8 +- .../aws-s3-static-website/package.json | 26 +- .../aws-serverless-image-handler/README.md | 2 +- .../aws-serverless-image-handler/lib/index.ts | 20 +- .../aws-serverless-image-handler/package.json | 58 +- .../lib/s3-static-site-stack.ts | 8 +- .../lib/serverless-backend-stack.ts | 14 +- .../aws-serverless-web-app/package.json | 36 +- 428 files changed, 5978 insertions(+), 3591 deletions(-) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/README.md (58%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/lib/index.ts (79%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/package.json (59%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/test/__snapshots__/apigateway-dynamodb.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/test/apigateway-dynamodb.test.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-dynamodb/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/README.md (64%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/lib/index.ts (74%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/package.json (59%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/test/integ.deployFunction.expected.json (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/test/integ.deployFunction.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/test/integ.existingFunction.expected.json (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/test/integ.existingFunction.ts (95%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/README.md (55%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/lib/index.ts (80%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/package.json (57%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap (83%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/test/apigateway-sqs.test.ts (93%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json (84%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/test/integ.no-arguments.expected.json (78%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-apigateway-sqs/test/integ.no-arguments.ts (95%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/README.md (58%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/lib/index.ts (68%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/package.json (53%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts (95%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/README.md (61%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/lib/index.ts (66%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/package.json (56%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/test/__snapshots__/test.cloudfront-apigateway.test.js.snap (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/test/integ.no-arguments.expected.json (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/test/integ.no-arguments.ts (95%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/README.md (61%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/lib/index.ts (73%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/package.json (61%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/test/__snapshots__/test.cloudfront-s3.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/test/integ.no-security-headers.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/test/integ.no-security-headers.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/README.md (62%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/lib/index.ts (66%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/package.json (58%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts (93%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md (53%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts (60%) create mode 100644 source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts (78%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/README.md (65%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/lib/index.ts (73%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/package.json (58%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/test/__snapshots__/dynamodb-stream-lambda.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-dynamodb-stream-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/README.md (67%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/lib/index.ts (73%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/package.json (59%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/test/__snapshots__/events-rule-lambda.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/test/events-rule-lambda.test.ts (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/test/integ.events-rule-no-argument.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-events-rule-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3 => @aws-solutions-constructs/aws-events-rule-step-function}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3 => @aws-solutions-constructs/aws-events-rule-step-function}/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3 => @aws-solutions-constructs/aws-events-rule-step-function}/.npmignore (100%) create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/README.md create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/architecture.png create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/lib/index.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/package.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/__snapshots__/events-rule-step-function.test.js.snap create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/events-rule-step-function.test.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-no-argument.expected.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-no-argument.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-with-lambda.expected.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-with-lambda.ts rename source/patterns/{@aws-solutions-konstruk/aws-iot-lambda-dynamodb => @aws-solutions-constructs/aws-events-rule-step-function}/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-lambda-dynamodb => @aws-solutions-constructs/aws-iot-kinesisfirehose-s3}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-lambda-dynamodb => @aws-solutions-constructs/aws-iot-kinesisfirehose-s3}/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-lambda-dynamodb => @aws-solutions-constructs/aws-iot-kinesisfirehose-s3}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-kinesisfirehose-s3/README.md (58%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-kinesisfirehose-s3/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-kinesisfirehose-s3/lib/index.ts (66%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-kinesisfirehose-s3/package.json (53%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-kinesisfirehose-s3/test/__snapshots__/test.iot-kinesisfirehose-s3.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-kinesisfirehose-s3/test/test.iot-kinesisfirehose-s3.test.ts (93%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-lambda => @aws-solutions-constructs/aws-iot-lambda-dynamodb}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-lambda => @aws-solutions-constructs/aws-iot-lambda-dynamodb}/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-lambda => @aws-solutions-constructs/aws-iot-lambda-dynamodb}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda-dynamodb/README.md (63%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda-dynamodb/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda-dynamodb/lib/index.ts (68%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda-dynamodb/package.json (54%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda-dynamodb/test/__snapshots__/iot-lambda-dynamodb.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda-dynamodb/test/iot-lambda-dynamodb.test.ts (97%) rename source/patterns/{@aws-solutions-konstruk/aws-iot-lambda => @aws-solutions-constructs/aws-iot-lambda-dynamodb}/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics => @aws-solutions-constructs/aws-iot-lambda}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-kinesisfirehose-s3 => @aws-solutions-constructs/aws-iot-lambda}/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics => @aws-solutions-constructs/aws-iot-lambda}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/README.md (69%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/lib/index.ts (72%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/package.json (60%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/test/integ.iot-lambda-new-func.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-iot-lambda/test/iot-lambda.test.ts (97%) rename source/patterns/{@aws-solutions-konstruk/core => @aws-solutions-constructs/aws-iot-lambda}/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk/aws-kinesisfirehose-s3 => @aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-kinesisfirehose-s3 => @aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md (66%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts (68%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json (52%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/test/__snapshots__/test.kinesisfirehose-analytics-s3.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts (95%) rename source/patterns/{@aws-solutions-konstruk/aws-kinesisstreams-lambda => @aws-solutions-constructs/aws-kinesisfirehose-s3}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-dynamodb => @aws-solutions-constructs/aws-kinesisfirehose-s3}/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-kinesisstreams-lambda => @aws-solutions-constructs/aws-kinesisfirehose-s3}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3/README.md (59%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3/lib/index.ts (82%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3/package.json (57%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts (92%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-dynamodb => @aws-solutions-constructs/aws-kinesisstreams-lambda}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-dynamodb => @aws-solutions-constructs/aws-kinesisstreams-lambda}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/README.md (65%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/lib/index.ts (73%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/package.json (58%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap (76%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json (76%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/test/integ.deployFunction.ts (98%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts (93%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana => @aws-solutions-constructs/aws-lambda-dynamodb}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana => @aws-solutions-constructs/aws-lambda-dynamodb}/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana => @aws-solutions-constructs/aws-lambda-dynamodb}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/README.md (63%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/lib/index.ts (71%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/package.json (61%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/integ.add-secondary-index.ts (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/integ.set-billing-mode.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/integ.use-existing-func.ts (95%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-dynamodb/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-s3 => @aws-solutions-constructs/aws-lambda-elasticsearch-kibana}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-s3-lambda => @aws-solutions-constructs/aws-lambda-elasticsearch-kibana}/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-s3 => @aws-solutions-constructs/aws-lambda-elasticsearch-kibana}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/README.md (52%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/lib/index.ts (52%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/package.json (54%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json (95%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts (80%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-elasticsearch-kibana/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-sns => @aws-solutions-constructs/aws-lambda-s3}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-lambda-sns => @aws-solutions-constructs/aws-lambda-s3}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/README.md (64%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/lib/index.ts (76%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/package.json (62%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/test/integ.deployFunction.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/test/integ.deployFunction.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/test/integ.existingFunction.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/test/integ.existingFunction.ts (95%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/test/lambda-s3.test.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-s3/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk/aws-s3-lambda => @aws-solutions-constructs/aws-lambda-sns}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-s3-lambda => @aws-solutions-constructs/aws-lambda-sns}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/README.md (67%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/lib/index.ts (76%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/package.json (60%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/test/__snapshots__/lambda-sns.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/test/integ.deployFunction.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/test/integ.deployFunction.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/test/integ.existingFunction.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/test/integ.existingFunction.ts (95%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/test/lambda-sns.test.ts (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-lambda-sns/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk/aws-sns-lambda => @aws-solutions-constructs/aws-s3-lambda}/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-sns-lambda => @aws-solutions-constructs/aws-s3-lambda}/.gitignore (100%) rename source/patterns/{@aws-solutions-konstruk/aws-sns-lambda => @aws-solutions-constructs/aws-s3-lambda}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/README.md (69%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/lib/index.ts (84%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/package.json (56%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/test/__snapshots__/s3-lambda.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/test/integ.existing-s3-bucket.ts (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-s3-lambda/test/s3-lambda.test.ts (87%) rename source/patterns/{@aws-solutions-konstruk/aws-sqs-lambda => @aws-solutions-constructs/aws-s3-step-function}/.eslintignore (100%) create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/.gitignore rename source/patterns/{@aws-solutions-konstruk/aws-sqs-lambda => @aws-solutions-constructs/aws-s3-step-function}/.npmignore (100%) create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/README.md create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/architecture.png create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/lib/index.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/package.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/__snapshots__/s3-step-function.test.js.snap create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/integ.s3-step-function-no-argument.expected.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/integ.s3-step-function-no-argument.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/s3-step-function.test.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-sns-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-constructs/aws-sns-lambda/.gitignore rename source/patterns/{@aws-solutions-konstruk/core => @aws-solutions-constructs/aws-sns-lambda}/.npmignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/README.md (70%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/lib/index.ts (78%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/package.json (56%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/test/integ.no-arguments.expected.json (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/test/integ.no-arguments.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sns-lambda/test/sns-lambda.test.ts (87%) create mode 100644 source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.eslintignore rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/.gitignore (100%) create mode 100644 source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.npmignore rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/README.md (64%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/architecture.png (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/lib/index.ts (70%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/package.json (57%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap (70%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/test/integ.deployFunction.expected.json (72%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/test/integ.deployFunction.ts (96%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/test/integ.existingFunction.expected.json (72%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/test/integ.existingFunction.ts (92%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/test/lambda/index.js (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/aws-sqs-lambda/test/test.sqs-lambda.test.ts (93%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/.eslintignore (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/.gitignore (100%) create mode 100644 source/patterns/@aws-solutions-constructs/core/.npmignore create mode 100644 source/patterns/@aws-solutions-constructs/core/README.md rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/index.ts (93%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/apigateway-defaults.ts (97%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/apigateway-helper.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/cloudfront-distribution-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/cloudfront-distribution-helper.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/cloudwatch-log-group-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/cognito-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/cognito-helper.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/dynamodb-table-defaults.ts (91%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/elasticsearch-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/elasticsearch-helper.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/events-rule-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/iot-topic-rule-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/kinesis-analytics-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/kinesis-analytics-helper.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/kinesis-firehose-s3-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/kinesis-streams-defaults.ts (93%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/kinesis-streams-helper.ts (80%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/kms-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/kms-helper.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/lambda-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/lambda-event-source-mapping-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/lambda-helper.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/override-warning-service.ts (94%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/s3-bucket-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/s3-bucket-helper.ts (84%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/sns-defaults.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/sns-helper.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/sqs-defaults.ts (80%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/sqs-helper.ts (83%) create mode 100644 source/patterns/@aws-solutions-constructs/core/lib/step-function-defaults.ts create mode 100644 source/patterns/@aws-solutions-constructs/core/lib/step-function-helper.ts rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/lib/utils.ts (100%) create mode 100644 source/patterns/@aws-solutions-constructs/core/package.json rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/apigateway-helper.test.js.snap (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap (99%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/cloudwatch-log-group.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/congnito-helper.test.js.snap (83%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/dynamo-table.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/elasticsearch-helper.test.js.snap (93%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/events-rule.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/iot-rule.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/kinesis-analytics.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap (100%) create mode 100644 source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap rename source/patterns/{@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap => @aws-solutions-constructs/core/test/__snapshots__/kinesis-streams-helper.test.js.snap} (75%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/kms-helper.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/lambda-func.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/s3-bucket-helper.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/s3-bucket.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/__snapshots__/sns-helper.test.js.snap (100%) rename source/patterns/{@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-helper.test.js.snap => @aws-solutions-constructs/core/test/__snapshots__/sqs-helper.test.js.snap} (76%) create mode 100644 source/patterns/@aws-solutions-constructs/core/test/__snapshots__/step-function-helper.test.js.snap rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/apigateway-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/cloudfront-distribution-api-gateway-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/cloudfront-distribution-s3-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/cloudwatch-log-group.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/congnito-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/dynamo-table.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/elasticsearch-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/events-rule.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/iot-rule.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/kinesis-analytics-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/kinesis-analytics.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/kinesis-firehose-s3-defaults.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/kinesis-streams-defaults.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/kinesis-streams-helper.test.ts (92%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/kms-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/lambda-event-source.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/lambda-func.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/lambda-test/index.js (100%) create mode 100644 source/patterns/@aws-solutions-constructs/core/test/lambda/index.js rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/override-warning-service.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/s3-bucket-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/s3-bucket.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/sns-helper.test.ts (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/core/test/sqs-helper.test.ts (88%) create mode 100644 source/patterns/@aws-solutions-constructs/core/test/step-function-helper.test.ts rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/eslintrc.yml (100%) rename source/patterns/{@aws-solutions-konstruk => @aws-solutions-constructs}/license-header.js (100%) delete mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json delete mode 100644 source/patterns/@aws-solutions-konstruk/core/README.md delete mode 100644 source/patterns/@aws-solutions-konstruk/core/package.json delete mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sqs-helper.test.js.snap diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3634b9aab..90140b121 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -33,7 +33,7 @@ what is the error message you are seeing? - **CDK CLI Version :** - **CDK Framework Version:** - - **Konstruk Version :** + - **AWS Solutions Constructs Version :** - **OS :** - **Language :** diff --git a/CHANGELOG.md b/CHANGELOG.md index f65f0ded8..3ecabe739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.46.0] - 2020-06-22 + +General Availability of the AWS Solutions Constructs!! 🎉🎉🥂🥂🍾🍾 + +### Added +- aws-events-rule-step-function pattern added +- aws-s3-step-function pattern added +- Upgraded all patterns to CDK v1.46.0 +- Renamed the Github repo and NPM, PyPi & Maven namespaces to AWS Solutions Constructs + +### Changed +- Changed the default encryption setting for Amazon SQS & Amazon Kinesis to use AWS Managed KMS Key +- Updated READMEs for all patterns to include Default settings section +- For all patterns, converted the getter methods to properties; used for retrieving the underlying AWS Resource object(s) created by the Solutions Constructs + ## [0.8.1-beta] - 2020-05-21 ### Changed - Upgraded to CDK v1.40.0 @@ -14,27 +29,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.8.0-beta] - 2020-03-31 ### Added - Initial public beta release -- aws-apigateway-dynamodb module added -- aws-apigateway-lambda module added -- aws-apigateway-sqs module added -- aws-cloudfront-apigateway-lambda module added -- aws-cloudfront-apigateway module added -- aws-cloudfront-s3 module added -- aws-cognito-apigateway-lambda module added -- aws-dynamodb-stream-lambda-elasticsearch-kibana module added -- aws-dynamodb-stream-lambda module added -- aws-events-rule-lambda module added -- aws-iot-kinesisfirehose-s3 module added -- aws-iot-lambda-dynamodb module added -- aws-iot-lambda module added -- aws-kinesisfirehose-s3-and-kinesisanalytics module added -- aws-kinesisfirehose-s3 module added -- aws-kinesisstreams-lambda module added -- aws-lambda-dynamodb module added -- aws-lambda-elasticsearch-kibana module added -- aws-lambda-s3 module added -- aws-lambda-sns module added -- aws-s3-lambda module added -- aws-sns-lambda module added -- aws-sqs-lambda module added -- core module added +- aws-apigateway-dynamodb pattern added +- aws-apigateway-lambda pattern added +- aws-apigateway-sqs pattern added +- aws-cloudfront-apigateway-lambda pattern added +- aws-cloudfront-apigateway pattern added +- aws-cloudfront-s3 pattern added +- aws-cognito-apigateway-lambda pattern added +- aws-dynamodb-stream-lambda-elasticsearch-kibana pattern added +- aws-dynamodb-stream-lambda pattern added +- aws-events-rule-lambda pattern added +- aws-iot-kinesisfirehose-s3 pattern added +- aws-iot-lambda-dynamodb pattern added +- aws-iot-lambda pattern added +- aws-kinesisfirehose-s3-and-kinesisanalytics pattern added +- aws-kinesisfirehose-s3 pattern added +- aws-kinesisstreams-lambda pattern added +- aws-lambda-dynamodb pattern added +- aws-lambda-elasticsearch-kibana pattern added +- aws-lambda-s3 pattern added +- aws-lambda-sns pattern added +- aws-s3-lambda pattern added +- aws-sns-lambda pattern added +- aws-sqs-lambda pattern added +- core pattern added \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79071822b..ec5bae250 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ information to effectively respond to your bug report or contribution. We welcome you to use the GitHub issue tracker to report bugs or suggest features. -When filing an issue, please check [existing open](https://github.com/awslabs/aws-solutions-konstruk/issues), or [recently closed](https://github.com/awslabs/aws-solutions-konstruk/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already +When filing an issue, please check [existing open](https://github.com/awslabs/aws-solutions-constructs/issues), or [recently closed](https://github.com/awslabs/aws-solutions-constructs/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: * A reproducible test case or series of steps @@ -21,28 +21,171 @@ reported the issue. Please try to include as much information as you can. Detail ## Contributing via Pull Requests -Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: -1. You are working against the latest source on the *master* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. +### Pull Request Checklist + +* [ ] Testing + - Unit test added (prefer not to modify an existing test, otherwise, it's probably a breaking change) + - Integration test added (if adding a new pattern or making a significant update to an existing pattern) +* [ ] Docs + - __README__: README and/or documentation topic updated + - __Design__: For significant features, design document added to `design` folder +* [ ] Title and Description + - __Change type__: title prefixed with **fix**, **feat** and module name in parens, which will appear in changelog + - __Title__: use lower-case and doesn't end with a period + - __Breaking?__: last paragraph: "BREAKING CHANGE: " + - __Issues__: Indicate issues fixed via: "**Fixes #xxx**" or "**Closes #xxx**" -To send us a pull request, please: +--- -1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure local tests pass. -4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. +### Step 1: Open Issue + +If there isn't one already, open an issue describing what you intend to contribute. It's useful to communicate in +advance, because sometimes, someone is already working in this space, so maybe it's worth collaborating with them +instead of duplicating the efforts. + +### Step 2: Design (optional) + +In some cases, it is useful to seek for feedback by iterating on a design document. This is useful +when you plan a big change or feature, or you want advice on what would be the best path forward. + +Sometimes, the GitHub issue is sufficient for such discussions, and can be sufficient to get +clarity on what you plan to do. Sometimes, a design document would work better, so people can provide +iterative feedback. + +In such cases, use the GitHub issue description to collect **requirements** and +**use cases** for your feature. + +Then, create a design document in markdown format under the `design/` directory +and request feedback through a pull request. + +Once the design is finalized, you can re-purpose this PR for the implementation, +or open a new PR to that end. + +### Step 3: Work your Magic + +Work your magic. Here are some guidelines: + +* Coding style (abbreviated): + * In general, follow the style of the code around you + * 2 space indentation + * 120 characters wide + * ATX style headings in markdown (e.g. `## H2 heading`) +* Every change requires a unit test +* If you change APIs, make sure to update the module's README file +* Try to maintain a single feature/bugfix per pull request. It's okay to introduce a little bit of housekeeping + changes along the way, but try to avoid conflating multiple features. Eventually all these are going to go into a + single commit, so you can use that to frame your scope. +* If your change introduces a new construct, take a look at the our + [aws-apigateway-lambd Construct](https://github.com/awslabs/aws-solutions-constructs/tree/master/source/patterns/%40aws-solutions-constructs/aws-apigateway-lambda) for an explanation of the L3 patterns we use. + Feel free to start your contribution by copy&pasting files from that project, + and then edit and rename them as appropriate - + it might be easier to get started that way. + +#### Integration Tests + +Integration tests perform a few functions in the CDK code base - +1. Acts as a regression detector. It does this by running `cdk synth` on the integration test and comparing it against + the `*.expected.json` file. This highlights how a change affects the synthesized stacks. +2. Allows for a way to verify if the stacks are still valid CloudFormation templates, as part of an intrusive change. + This is done by running `yarn integ` which will run `cdk deploy` across all of the integration tests in that package. + Remember to set up AWS credentials before doing this. +3. (Optionally) Acts as a way to validate that constructs set up the CloudFormation resources as expected. A successful + CloudFormation deployment does not mean that the resources are set up correctly. + +If you are working on a new feature that is using previously unused CloudFormation resource types, or involves +configuring resource types across services, you need to write integration tests that use these resource types or +features. + +To the extent possible, include a section (like below) in the integration test file that specifies how the successfully +deployed stack can be verified for correctness. Correctness here implies that the resources have been set up correctly. +The steps here are usually AWS CLI commands but they need not be. + +```ts +/* + * Stack verification steps: + * * + * * + */ +``` + +Examples: +* [integ.deployFunction.ts](https://github.com/awslabs/aws-solutions-constructs/blob/master/source/patterns/%40aws-solutions-constructs/aws-apigateway-lambda/test/integ.deployFunction.ts) +* [integ.existingFunction.ts](https://github.com/awslabs/aws-solutions-constructs/blob/master/source/patterns/%40aws-solutions-constructs/aws-apigateway-lambda/test/integ.existingFunction.ts) + +### Step 4: Commit + +Create a commit with the proposed changes: + +* Commit title and message (and PR title and description) must adhere to [conventionalcommits](https://www.conventionalcommits.org). + * The title must begin with `feat(module): title`, `fix(module): title`, `refactor(module): title` or + `chore(module): title`. + * Title should be lowercase. + * No period at the end of the title. + +* Commit message should describe _motivation_. Think about your code reviewers and what information they need in + order to understand what you did. If it's a big commit (hopefully not), try to provide some good entry points so + it will be easier to follow. + +* Commit message should indicate which issues are fixed: `fixes #` or `closes #`. + +* Shout out to collaborators. + +* If not obvious (i.e. from unit tests), describe how you verified that your change works. + +* If this commit includes breaking changes, they must be listed at the end in the following format (notice how multiple breaking changes should be formatted): + +``` +BREAKING CHANGE: Description of what broke and how to achieve this behavior now +* **module-name:** Another breaking change +* **module-name:** Yet another breaking change +``` + +### Step 5: Pull Request + +* Push to a GitHub fork or to a branch (naming convention: `/`) +* Submit a Pull Requests on GitHub. +* Please follow the PR checklist written below. We trust our contributors to self-check, and this helps that process! +* Discuss review comments and iterate until you get at least one “Approve”. When iterating, push new commits to the + same branch. Usually all these are going to be squashed when you merge to master. The commit messages should be hints + for you when you finalize your merge commit message. +* Make sure to update the PR title/description if things change. The PR title/description are going to be used as the + commit title/message and will appear in the CHANGELOG, so maintain them all the way throughout the process. + + + +### Step 6: Merge + +* Make sure your PR builds successfully (we have CodeBuild setup to automatically build all PRs) +* Once approved and tested, a maintainer will squash-merge to master and will use your PR title/description as the + commit message. GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +## Building Pattern(s) + +### Full Build + +```console +$ cd +$ docker run --rm --net=host -it -v $PWD:$PWD -w $PWD jsii/superchain +docker$ cd deployment +docker$ ./build-patterns.sh +docker$ exit +``` + +### Partial Build -## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-solutions-konstruk/labels/help%20wanted) issues is a great place to start. +First run a clean Full Build before doing the partial build. +```console +$ cd +$ docker run --rm --net=host -it -v $PWD:$PWD -w $PWD jsii/superchain +docker$ cd source/patterns/@aws-solutions-constructs/my-module +docker$ npm run build+lint+test +docker$ exit +``` ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). @@ -56,6 +199,6 @@ If you discover a potential security issue in this project we ask that you notif ## Licensing -See the [LICENSE](https://github.com/awslabs/aws-solutions-konstruk/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](https://github.com/awslabs/aws-solutions-constructs/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/NOTICE.txt b/NOTICE.txt index 833fe2b9f..60c18149b 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,4 +1,4 @@ -AWS Konstruk +AWS Solutions Constructs 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/ diff --git a/README.md b/README.md index e0bce0513..e06a5ab9d 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,25 @@ -# API Reference - +# AWS Solutions Constructs ---- - -![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) - -> **This is a _developer preview_ (public beta) library.** -> -> All modules are under active development and subject to non-backward compatible changes or removal in any -> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. -> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. - ---- - - -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
-The AWS Solutions Konstruk Library (Konstruk) is an open-source extension of the AWS Cloud Development Kit (AWS CDK) that provides multi-service, well-architected patterns for quickly defining solutions in code to create predictable and repeatable infrastructure. Konstruk's goal is to accelerates the experience for developers to build solutions of any size using pattern-based definitions for their architecture. +The AWS Solutions Constructs library is an open-source extension of the AWS Cloud Development Kit (AWS CDK) that provides multi-service, well-architected patterns for quickly defining solutions in code to create predictable and repeatable infrastructure. The goal of AWS Solutions Constructs is to accelerate the experience for developers to build solutions of any size using pattern-based definitions for their architecture. -The patterns defined in Konstruk are high level, multi-service abstractions of AWS CDK constructs that have default configurations based on well-architected best practices. The library is organized into logical modules using object-oriented techniques to create each architectural pattern model. +The patterns defined in AWS Solutions Constructs are high level, multi-service abstractions of AWS CDK constructs that have default configurations based on well-architected best practices. The library is organized into logical modules using object-oriented techniques to create each architectural pattern model. The CDK is available in the following languages: -* JavaScript, TypeScript (Node.js ≥ 10.3.0) +* JavaScript, TypeScript (Node.js ≥ 10.13.0) * Python (Python ≥ 3.6) +* Java (Java ≥ 8 and Maven ≥ 3.5.4) ## Modules -The Konstruk library is organized into several modules. They are named like this: +The AWS Solutions Constructs library is organized into several modules. They are named like this: * __aws-xxx__: well architected pattern package for the indicated services. This package will contain constructs that contain multiple AWS CDK service modules to configure the given pattern. -* __xxx__: packages that don't start "aws-" are Konstruk core modules that are used to configure best practice defaults for services used within the pattern library. +* __xxx__: packages that don't start "aws-" are core modules that are used to configure best practice defaults for services used within the pattern library. ## Module Contents @@ -47,14 +34,14 @@ The pattern's documentation page also lists the available methods to call and th ## Sample Use Cases -This library includes a collection of functional use case implementations to demonstrate the usage of Konstruk architectural patterns. These can be used in the same way as architectural patterns, and can be conceptualized as an additional "higher-level" abstraction of those patterns. The following use cases are provided as functional examples: +This library includes a collection of functional use case implementations to demonstrate the usage of AWS Solutions Constructs architectural patterns. These can be used in the same way as architectural patterns, and can be conceptualized as an additional "higher-level" abstraction of those patterns. The following use cases are provided as functional examples: * __aws-s3-static-website__ - implements an Amazon CloudFront distribution, Amazon S3 bucket and AWS Lambda-based custom resource to copy the static website content for the Wild Rydes demo website (part of the aws-serverless-web-app implementation). - * Use case pattern: https://github.com/awslabs/aws-solutions-konstruk/source/use_cases/aws-s3-static-website + * Use case pattern: https://github.com/awslabs/aws-solutions-constructs/tree/master/source/use_cases/aws-s3-static-website * __aws-serverless-image-handler__ - implements an Amazon CloudFront distribution, an Amazon API Gateway REST API, an AWS Lambda function, and necessary permissions/logic to provision a functional image handler API for serving image content from one or more Amazon S3 buckets within the deployment account. - * Use case pattern: https://github.com/awslabs/aws-solutions-konstruk/source/use_cases/aws-serverless-image-handler + * Use case pattern: https://github.com/awslabs/aws-solutions-constructs/tree/master/source/use_cases/aws-serverless-image-handler * __aws-serverless-web-app__ - implements a simple serverless web application that enables users to request unicorn rides from the Wild Rydes fleet. The application will present users with an HTML based user interface for indicating the location where they would like to be picked up and will interface on the backend with a RESTful web service to submit the request and dispatch a nearby unicorn. The application will also provide facilities for users to register with the service and log in before requesting rides. - * Use case pattern: https://github.com/awslabs/aws-solutions-konstruk/source/use_cases/aws-serverless-web-app + * Use case pattern: https://github.com/awslabs/aws-solutions-constructs/tree/master/source/use_cases/aws-serverless-web-app *** © Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/source/lerna.json b/source/lerna.json index cb6781ba1..9812c6adf 100644 --- a/source/lerna.json +++ b/source/lerna.json @@ -3,9 +3,8 @@ "npmClient": "yarn", "useWorkspaces": true, "packages": [ - "./patterns/@aws-solutions-konstruk/*", - "./use_cases/*" + "./patterns/@aws-solutions-constructs/*" ], "rejectCycles": "true", - "version": "0.8.1" + "version": "1.46.0" } diff --git a/source/package.json b/source/package.json index e709e1248..fd2c4c27c 100644 --- a/source/package.json +++ b/source/package.json @@ -1,10 +1,10 @@ { - "name": "aws-solutions-konstruk", - "version": "0.8.1", - "description": "AWS Solutions Konstruk Library", + "name": "aws-solutions-constructs", + "version": "1.46.0", + "description": "AWS Solutions Constructs Library", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", "directory": "source" }, "license": "Apache-2.0", @@ -23,18 +23,17 @@ "eslint-plugin-license-header": "^0.2.0", "fs-extra": "^8.1.0", "jest": "^24.9.0", - "jsii": "^1.4.1", - "jsii-pacmak": "^1.4.1", + "jsii": "^1.7.0", + "jsii-pacmak": "^1.7.0", "tslint": "^5.20.1", - "typescript": "~3.8.3" + "typescript": "~3.9.5" }, "devDependencies": { - "lerna": "^3.18.4" + "lerna": "^3.22.1" }, "workspaces": { "packages": [ - "./patterns/@aws-solutions-konstruk/*", - "./use_cases/*" + "./patterns/@aws-solutions-constructs/*" ], "nohoist": [ "**/deepmerge", diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.gitignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.npmignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/README.md b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/README.md similarity index 58% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/README.md rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/README.md index b551ac27b..58480e96f 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,23 +12,24 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-apigateway-dynamodb/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_apigateway_dynamodb`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-apigateway-dynamodb`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_apigateway_dynamodb`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-apigateway-dynamodb`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.apigatewaydynamodb`| ## Overview -This AWS Solutions Konstruk implements an Amazon API Gateway REST API connected to Amazon DynamoDB table. +This AWS Solutions Construct implements an Amazon API Gateway REST API connected to Amazon DynamoDB table. Here is a minimal deployable pattern definition: ``` javascript -import { ApiGatewayToDynamoDBProps, ApiGatewayToDynamoDB } from "@aws-solutions-konstruk/aws-apigateway-dynamodb"; +import { ApiGatewayToDynamoDBProps, ApiGatewayToDynamoDB } from "@aws-solutions-constructs/aws-apigateway-dynamodb"; const props: ApiGatewayToDynamoDBProps = {}; @@ -56,19 +55,36 @@ _Parameters_ |:-------------|:----------------|-----------------| |dynamoTableProps|[`dynamodb.TableProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.TableProps.html)|Optional user provided props to override the default props for DynamoDB Table| |apiGatewayProps?|[`api.RestApiProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApiProps.html)|Optional user-provided props to override the default props for the API Gateway.| -|allowCreateOperation|`boolean`|Whether to deploy API Gateway Method for Create operation on Dynamodb DB table.| +|allowCreateOperation|`boolean`|Whether to deploy API Gateway Method for Create operation on DynamoDB table.| |createRequestTemplate|`string`|API Gateway Request template for Create method, required if allowCreateOperation set to true| -|allowReadOperation|`boolean`|Whether to deploy API Gateway Method for Read operation on Dynamodb DB table.| -|allowUpdateOperation|`boolean`|Whether to deploy API Gateway Method for Update operation on Dynamodb DB table.| +|allowReadOperation|`boolean`|Whether to deploy API Gateway Method for Read operation on DynamoDB table.| +|allowUpdateOperation|`boolean`|Whether to deploy API Gateway Method for Update operation on DynamoDB table.| |updateRequestTemplate|`string`|API Gateway Request template for Update method, required if allowUpdateOperation set to true| -|allowDeleteOperation|`boolean`|Whether to deploy API Gateway Method for Delete operation on Dynamodb DB table.| +|allowDeleteOperation|`boolean`|Whether to deploy API Gateway Method for Delete operation on DynamoDB table.| ## Pattern Properties | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|restApi()|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of the api.RestApi created by the construct.| -|dynamoTable()|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct.| +|apiGateway|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of the api.RestApi created by the construct.| +|apiGatewayRole|[`iam.Role`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html)|Returns an instance of the iam.Role created by the construct for API Gateway.| +|dynamoTable|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon API Gateway +* Deploy an edge-optimized API endpoint +* Enable CloudWatch logging for API Gateway +* Configure least privilege access IAM role for API Gateway +* Set the default authorizationType for all API methods to IAM + +### Amazon DynamoDB Table +* Set the billing mode for DynamoDB Table to On-Demand (Pay per request) +* Enable server-side encryption for DynamoDB Table using AWS managed KMS Key +* Creates a partition key called 'id' for DynamoDB Table +* Retain the Table when deleting the CloudFormation stack ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/architecture.png b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/lib/index.ts similarity index 79% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/lib/index.ts index b4a72ae80..608b51a60 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/lib/index.ts @@ -13,10 +13,10 @@ import * as api from '@aws-cdk/aws-apigateway'; import * as iam from '@aws-cdk/aws-iam'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; -import { overrideProps } from '@aws-solutions-konstruk/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; /** * @summary The properties for the ApiGatewayToDynamoDB class. @@ -36,7 +36,7 @@ export interface ApiGatewayToDynamoDBProps { readonly apiGatewayProps?: api.RestApiProps | any, /** - * Whether to deploy API Gateway Method for Create operation on Dynamodb DB table. + * Whether to deploy API Gateway Method for Create operation on DynamoDB table. * * @default - false */ @@ -50,14 +50,14 @@ export interface ApiGatewayToDynamoDBProps { readonly createRequestTemplate?: string, /** - * Whether to deploy API Gateway Method for Read operation on Dynamodb DB table. + * Whether to deploy API Gateway Method for Read operation on DynamoDB table. * * @default - true */ readonly allowReadOperation?: boolean, /** - * Whether to deploy API Gateway Method for Update operation on Dynamodb DB table. + * Whether to deploy API Gateway Method for Update operation on DynamoDB table. * * @default - false */ @@ -71,7 +71,7 @@ export interface ApiGatewayToDynamoDBProps { readonly updateRequestTemplate?: string, /** - * Whether to deploy API Gateway Method for Delete operation on Dynamodb DB table. + * Whether to deploy API Gateway Method for Delete operation on DynamoDB table. * * @default - false */ @@ -82,9 +82,9 @@ export interface ApiGatewayToDynamoDBProps { * @summary The ApiGatewayToDynamoDB class. */ export class ApiGatewayToDynamoDB extends Construct { - private table: dynamodb.Table; - private apiGatewayRole: iam.Role; - private apiGateway: api.RestApi; + public readonly dynamoTable: dynamodb.Table; + public readonly apiGatewayRole: iam.Role; + public readonly apiGateway: api.RestApi; /** * @summary Constructs a new instance of the ApiGatewayToDynamoDB class. @@ -102,10 +102,10 @@ export class ApiGatewayToDynamoDB extends Construct { if (props.dynamoTableProps) { const dynamoTableProps: dynamodb.TableProps = overrideProps(defaults.DefaultTableProps, props.dynamoTableProps); partitionKeyName = dynamoTableProps.partitionKey.name; - this.table = new dynamodb.Table(this, 'DynamoTable', dynamoTableProps); + this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', dynamoTableProps); } else { partitionKeyName = defaults.DefaultTableProps.partitionKey.name; - this.table = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableProps); + this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableProps); } // Setup the API Gateway @@ -122,25 +122,25 @@ export class ApiGatewayToDynamoDB extends Construct { // Setup API Gateway Method // Create if (props.allowCreateOperation && props.allowCreateOperation === true && props.createRequestTemplate) { - const createRequestTemplate = props.createRequestTemplate.replace("${Table}", this.table.tableName); + const createRequestTemplate = props.createRequestTemplate.replace("${Table}", this.dynamoTable.tableName); this.addActiontoPlicy("dynamodb:PutItem"); this.addMethod(this.apiGateway.root, createRequestTemplate, "PutItem", "POST"); } // Read if (!props.allowReadOperation || props.allowReadOperation === true) { - const getRequestTemplate = "{\r\n\"TableName\": \"" + this.table.tableName + "\",\r\n \"KeyConditionExpression\": \"" + partitionKeyName + " = :v1\",\r\n \"ExpressionAttributeValues\": {\r\n \":v1\": {\r\n \"S\": \"$input.params('" + partitionKeyName + "')\"\r\n }\r\n }\r\n}"; + const getRequestTemplate = "{\r\n\"TableName\": \"" + this.dynamoTable.tableName + "\",\r\n \"KeyConditionExpression\": \"" + partitionKeyName + " = :v1\",\r\n \"ExpressionAttributeValues\": {\r\n \":v1\": {\r\n \"S\": \"$input.params('" + partitionKeyName + "')\"\r\n }\r\n }\r\n}"; this.addActiontoPlicy("dynamodb:Query"); this.addMethod(apiGatewayResource, getRequestTemplate, "Query", "GET"); } // Update if (props.allowUpdateOperation && props.allowUpdateOperation === true && props.updateRequestTemplate) { - const updateRequestTemplate = props.updateRequestTemplate.replace("${Table}", this.table.tableName); + const updateRequestTemplate = props.updateRequestTemplate.replace("${Table}", this.dynamoTable.tableName); this.addActiontoPlicy("dynamodb:UpdateItem"); this.addMethod(apiGatewayResource, updateRequestTemplate, "UpdateItem", "PUT"); } // Delete if (props.allowDeleteOperation && props.allowDeleteOperation === true) { - const deleteRequestTemplate = "{\r\n \"TableName\": \"" + this.table.tableName + "\",\r\n \"Key\": {\r\n \"" + partitionKeyName + "\": {\r\n \"S\": \"$input.params('" + partitionKeyName + "')\"\r\n }\r\n },\r\n \"ConditionExpression\": \"attribute_not_exists(Replies)\",\r\n \"ReturnValues\": \"ALL_OLD\"\r\n}"; + const deleteRequestTemplate = "{\r\n \"TableName\": \"" + this.dynamoTable.tableName + "\",\r\n \"Key\": {\r\n \"" + partitionKeyName + "\": {\r\n \"S\": \"$input.params('" + partitionKeyName + "')\"\r\n }\r\n },\r\n \"ConditionExpression\": \"attribute_not_exists(Replies)\",\r\n \"ReturnValues\": \"ALL_OLD\"\r\n}"; this.addActiontoPlicy("dynamodb:DeleteItem"); this.addMethod(apiGatewayResource, deleteRequestTemplate, "DeleteItem", "DELETE"); } @@ -149,7 +149,7 @@ export class ApiGatewayToDynamoDB extends Construct { private addActiontoPlicy(action: string) { this.apiGatewayRole.addToPolicy(new iam.PolicyStatement({ resources: [ - this.table.tableArn + this.dynamoTable.tableArn ], actions: [ `${action}` ] })); @@ -203,24 +203,4 @@ export class ApiGatewayToDynamoDB extends Construct { ] }); } - - /** - * @summary Returns an instance of the api.RestApi created by the construct. - * @returns {api.RestApi} Instance of the RestApi created by the construct. - * @since 0.8.0 - * @access public - */ - public restApi(): api.RestApi { - return this.apiGateway; - } - - /** - * @summary Returns an instance of dynamodb.Table created by the construct. - * @returns {dynamodb.Table} Instance of dynamodb.Table created by the construct - * @since 0.8.0 - * @access public - */ - public dynamoTable(): dynamodb.Table { - return this.table; - } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/package.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/package.json similarity index 59% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/package.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/package.json index fd384e38d..9eb69d9c8 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-apigateway-dynamodb", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-apigateway-dynamodb", + "version": "1.46.0", "description": "CDK Constructs for AWS API Gateway and Amazon DynamoDB integration.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb" }, "author": { "name": "Amazon Web Services", @@ -34,34 +34,34 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.apigatewaydynamodb", + "package": "software.amazon.awsconstructs.services.apigatewaydynamodb", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "apigatewaydynamodb" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.ApiGatewayDynamoDB", - "packageId": "Amazon.Konstruk.AWS.ApiGatewayDynamoDB", + "namespace": "Amazon.Constructs.AWS.ApiGatewayDynamoDB", + "packageId": "Amazon.Constructs.AWS.ApiGatewayDynamoDB", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-apigateway-dynamodb", - "module": "aws_solutions_konstruk.aws_apigateway_dynamodb" + "distName": "aws-solutions-constructs.aws-apigateway-dynamodb", + "module": "aws_solutions_constructs.aws_apigateway_dynamodb" } } }, "dependencies": { - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,11 +71,11 @@ ] }, "peerDependencies": { - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/__snapshots__/apigateway-dynamodb.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/__snapshots__/apigateway-dynamodb.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/__snapshots__/apigateway-dynamodb.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/__snapshots__/apigateway-dynamodb.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/apigateway-dynamodb.test.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/apigateway-dynamodb.test.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/apigateway-dynamodb.test.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/apigateway-dynamodb.test.ts index 7a01ccad7..884fb196e 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/apigateway-dynamodb.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/apigateway-dynamodb.test.ts @@ -25,13 +25,14 @@ test('snapshot test ApiGatewayToDynamoDB default params', () => { expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new Stack(); const apiGatewayToDynamoDBProps: ApiGatewayToDynamoDBProps = {}; const construct = new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb-default', apiGatewayToDynamoDBProps); - expect(construct.dynamoTable()).toBeDefined(); - expect(construct.restApi()).toBeDefined(); + expect(construct.dynamoTable !== null); + expect(construct.apiGateway !== null); + expect(construct.apiGatewayRole !== null); }); test('check allow CRUD operations', () => { diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-dynamodb/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/README.md similarity index 64% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/README.md index 3de5d141c..edffd7f49 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,23 +12,24 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-apigateway-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_apigateway_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-apigateway-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_apigateway_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-apigateway-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.apigatewaylambda`| ## Overview -This AWS Solutions Konstruk implements an Amazon API Gateway REST API connected to an AWS Lambda function pattern. +This AWS Solutions Construct implements an Amazon API Gateway REST API connected to an AWS Lambda function pattern. Here is a minimal deployable pattern definition: ``` javascript -const { ApiGatewayToLambda } = require('@aws-solutions-konstruk/aws-apigateway-lambda'); +const { ApiGatewayToLambda } = require('@aws-solutions-constructs/aws-apigateway-lambda'); new ApiGatewayToLambda(stack, 'ApiGatewayToLambdaPattern', { deployLambda: true, @@ -68,8 +67,22 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| -|restApi()|[`api.LambdaRestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaRestApi.html)|Returns an instance of the API Gateway REST API created by the pattern.| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| +|restApi|[`api.LambdaRestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaRestApi.html)|Returns an instance of the API Gateway REST API created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon API Gateway +* Deploy an edge-optimized API endpoint +* Enable CloudWatch logging for API Gateway +* Configure least privilege access IAM role for API Gateway +* Set the default authorizationType for all API methods to IAM + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/lib/index.ts similarity index 74% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/lib/index.ts index a461340b6..874b72d9c 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/lib/index.ts @@ -14,7 +14,7 @@ // Imports import * as api from '@aws-cdk/aws-apigateway'; import * as lambda from '@aws-cdk/aws-lambda'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; /** @@ -54,9 +54,8 @@ export interface ApiGatewayToLambdaProps { * @summary The ApiGatewayToLambda class. */ export class ApiGatewayToLambda extends Construct { - // Private variables - private api: api.RestApi; - private fn: lambda.Function; + public readonly apiGateway: api.RestApi; + public readonly lambdaFunction: lambda.Function; /** * @summary Constructs a new instance of the ApiGatewayToLambda class. @@ -70,33 +69,13 @@ export class ApiGatewayToLambda extends Construct { super(scope, id); // Setup the Lambda function - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps }); // Setup the API Gateway - this.api = defaults.GlobalLambdaRestApi(this, this.fn, props.apiGatewayProps); - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns { lambda.Function } Instance of Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of api.LambdaRestApi created by the construct. - * @returns { api.LambdaRestApi } Instance of LambdaRestApi created by the construct - * @since 0.8.0 - * @access public - */ - public restApi(): api.LambdaRestApi { - return this.api; + this.apiGateway = defaults.GlobalLambdaRestApi(this, this.lambdaFunction, props.apiGatewayProps); } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/package.json similarity index 59% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/package.json index 5b4a2d6dc..21375230f 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-apigateway-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-apigateway-lambda", + "version": "1.46.0", "description": "CDK constructs for defining an interaction between an API Gateway and a Lambda function.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-apigateway-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,34 +34,34 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.apigatewaylambda", + "package": "software.amazon.awsconstructs.services.apigatewaylambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "apigatewaylambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.ApiGatewayLambda", - "packageId": "Amazon.Konstruk.AWS.ApiGatewayLambda", + "namespace": "Amazon.Constructs.AWS.ApiGatewayLambda", + "packageId": "Amazon.Constructs.AWS.ApiGatewayLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-apigateway-lambda", - "module": "aws_solutions_konstruk.aws_apigateway_lambda" + "distName": "aws-solutions-constructs.aws-apigateway-lambda", + "module": "aws_solutions_constructs.aws_apigateway_lambda" } } }, "dependencies": { - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,11 +71,11 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap similarity index 99% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap index 9596458f6..fdceec1f4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap @@ -450,7 +450,7 @@ Object { Object { "Ref": "testapigatewaylambdaLambdaRestApiE957E944", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, @@ -491,7 +491,7 @@ Object { Object { "Ref": "testapigatewaylambdaLambdaRestApiDeploymentStageprod4EBF7247", }, - "/*/{proxy+}", + "/*/*", ], ], }, @@ -1027,7 +1027,7 @@ Object { Object { "Ref": "testapigatewaylambdaLambdaRestApiE957E944", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, @@ -1068,7 +1068,7 @@ Object { Object { "Ref": "testapigatewaylambdaLambdaRestApiDeploymentStageprod4EBF7247", }, - "/*/{proxy+}", + "/*/*", ], ], }, @@ -1670,7 +1670,7 @@ Object { Object { "Ref": "pattern1LambdaRestApi6083801A", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, @@ -1711,7 +1711,7 @@ Object { Object { "Ref": "pattern1LambdaRestApiDeploymentStageprodFF2B9A97", }, - "/*/{proxy+}", + "/*/*", ], ], }, @@ -2198,7 +2198,7 @@ Object { Object { "Ref": "pattern2LambdaRestApi7106C394", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, @@ -2239,7 +2239,7 @@ Object { Object { "Ref": "pattern2LambdaRestApiDeploymentStageprod134BC514", }, - "/*/{proxy+}", + "/*/*", ], ], }, diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.deployFunction.expected.json similarity index 99% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.deployFunction.expected.json index 316c3cbc5..4adc6b950 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.deployFunction.expected.json @@ -240,7 +240,7 @@ { "Ref": "testapigatewaylambdaLambdaRestApiDeploymentStageprod4EBF7247" }, - "/*/{proxy+}" + "/*/*" ] ] } @@ -277,7 +277,7 @@ { "Ref": "testapigatewaylambdaLambdaRestApiE957E944" }, - "/test-invoke-stage/*/{proxy+}" + "/test-invoke-stage/*/*" ] ] } diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.deployFunction.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.deployFunction.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.existingFunction.expected.json similarity index 99% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.existingFunction.expected.json index a158223f0..537702bee 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.existingFunction.expected.json @@ -240,7 +240,7 @@ { "Ref": "testapigatewaylambdaLambdaRestApiDeploymentStageprod4EBF7247" }, - "/*/{proxy+}" + "/*/*" ] ] } @@ -277,7 +277,7 @@ { "Ref": "testapigatewaylambdaLambdaRestApiE957E944" }, - "/test-invoke-stage/*/{proxy+}" + "/test-invoke-stage/*/*" ] ] } diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.existingFunction.ts similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.existingFunction.ts index 5d311aa88..f9d44698e 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/integ.existingFunction.ts @@ -15,7 +15,7 @@ import { App, Stack } from "@aws-cdk/core"; import { ApiGatewayToLambda, ApiGatewayToLambdaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; // App setup const app = new App(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts index 56ee9442c..cadf21df4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts @@ -96,13 +96,13 @@ test('Test deployLambda=true with lambdaFunctionProps', () => { }; const app = new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); // Assertion 1 - expect(app.lambdaFunction()).toHaveProperty('environment.OVERRIDE_STATUS', 'true'); + expect(app.lambdaFunction).toHaveProperty('environment.OVERRIDE_STATUS', 'true'); }); // -------------------------------------------------------------- // Test getter methods // -------------------------------------------------------------- -test('Test getter methods', () => { +test('Test properties', () => { // Initial Setup const stack = new Stack(); const props: ApiGatewayToLambdaProps = { @@ -115,9 +115,9 @@ test('Test getter methods', () => { }; const app = new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); // Assertion 1 - expect(app.lambdaFunction()).toBeDefined(); + expect(app.lambdaFunction !== null); // Assertion 2 - expect(app.restApi()).toBeDefined(); + expect(app.apiGateway !== null); }); // -------------------------------------------------------------- diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.gitignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.npmignore b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/README.md b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/README.md similarity index 55% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/README.md rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/README.md index cdfb9e4fd..1df8171b4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,28 +12,28 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-apigateway-sqs/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_apigateway_sqs`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-apigateway-sqs`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_apigateway_sqs`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-apigateway-sqs`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.apigatewaysqs`| ## Overview -This AWS Solutions Konstruk implements an Amazon API Gateway connected to an Amazon SQS queue pattern. +This AWS Solutions Construct implements an Amazon API Gateway connected to an Amazon SQS queue pattern. Here is a minimal deployable pattern definition: ``` javascript -const { ApiGatewayToSqs } = require('@aws-solutions-konstruk/aws-apigateway-sqs'); +const { ApiGatewayToSqs } = require('@aws-solutions-constructs/aws-apigateway-sqs'); new ApiGatewayToSqs(stack, 'ApiGatewayToSqsPattern', { apiGatewayProps: {}, queueProps: {}, - encryptionKeyProps: {}, deployDeadLetterQueue?: true, maxReceiveCount?: 3 }); @@ -60,16 +58,20 @@ _Parameters_ |:-------------|:----------------|-----------------| |apiGatewayProps?|[`api.RestApiProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApiProps.html)|Optional user-provided props to override the default props for the API Gateway.| |queueProps?|[`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.QueueProps.html)|Optional user-provided props to override the default props for the queue.| -|encryptionKeyProps?|[`kms.KeyProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.KeyProps.html)|Optional user-provided props to override the default props for the encryption key.| |deployDeadLetterQueue|`boolean`|Whether to deploy a secondary queue to be used as a dead letter queue.| -|maxReceiveCount|`number`|The number of times a message can be unsuccesfully dequeued before being moved to the dead-letter queue.| +|maxReceiveCount|`number`|The number of times a message can be unsuccessfully dequeued before being moved to the dead-letter queue.| +|allowCreateOperation?|`boolean`|Whether to deploy an API Gateway Method for Create operations on the queue (i.e. sqs:SendMessage).| +|createRequestTemplate?|`string`|API Gateway Request template for Create method, required if allowCreateOperation set to true.| +|allowReadOperation?|`boolean`|Whether to deploy an API Gateway Method for Read operations on the queue (i.e. sqs:ReceiveMessage).| +|allowDeleteOperation?|`boolean`|Whether to deploy an API Gateway Method for Delete operations on the queue (i.e. sqs:DeleteMessage).| ## Pattern Properties | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|api()|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of the API Gateway REST API created by the pattern.| -|sqsQueue()|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html)|Returns an instance of the SQS queue created by the pattern.| +|apiGateway|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of the API Gateway REST API created by the pattern.| +|apiGatewayRole|[`iam.Role`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html)|Returns an instance of the iam.Role created by the construct for API Gateway.| +|sqsQueue|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html)|Returns an instance of the SQS queue created by the pattern.| ## Sample API Usage @@ -79,6 +81,20 @@ _Parameters_ |POST|`/`| `{ "data": "Hello World!" }` |`sqs::SendMessage`|Delivers a message to the queue.| |DELETE|`/message?receiptHandle=[value]`||`sqs::DeleteMessage`|Deletes a specified message from the queue| +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon API Gateway +* Deploy an edge-optimized API endpoint +* Enable CloudWatch logging for API Gateway +* Configure least privilege access IAM role for API Gateway +* Set the default authorizationType for all API methods to IAM + +### Amazon SQS Queue +* Deploy SQS dead-letter queue for the source SQS Queue +* Enable server-side encryption for source SQS Queue using AWS Managed KMS Key + ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/architecture.png b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/lib/index.ts similarity index 80% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/lib/index.ts index 22f53eeef..ee8de6615 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/lib/index.ts @@ -14,9 +14,8 @@ // Imports import * as api from '@aws-cdk/aws-apigateway'; import * as sqs from '@aws-cdk/aws-sqs'; -import * as kms from '@aws-cdk/aws-kms'; import * as iam from '@aws-cdk/aws-iam'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; import * as cdk from '@aws-cdk/core'; @@ -36,12 +35,6 @@ export interface ApiGatewayToSqsProps { * @default - Default props are used */ readonly queueProps?: sqs.QueueProps | any - /** - * Optional user-provided props to override the default props for the encryption key. - * - * @default - Default props are used - */ - readonly encryptionKeyProps?: kms.KeyProps | any /** * Whether to deploy a secondary queue to be used as a dead letter queue. * @@ -49,7 +42,7 @@ export interface ApiGatewayToSqsProps { */ readonly deployDeadLetterQueue?: boolean, /** - * The number of times a message can be unsuccesfully dequeued before being moved to the dead-letter queue. + * The number of times a message can be unsuccessfully dequeued before being moved to the dead-letter queue. * * @default - required only if deployDeadLetterQueue = true. */ @@ -84,11 +77,9 @@ export interface ApiGatewayToSqsProps { * @summary The ApiGatewayToSqs class. */ export class ApiGatewayToSqs extends Construct { - // Private variables - private encryptionKey: kms.Key; - private apiGateway: api.RestApi; - private apiGatewayRole: iam.Role; - private queue: sqs.Queue; + public readonly apiGateway: api.RestApi; + public readonly apiGatewayRole: iam.Role; + public readonly sqsQueue: sqs.Queue; /** * @summary Constructs a new instance of the ApiGatewayToSqs class. @@ -101,14 +92,10 @@ export class ApiGatewayToSqs extends Construct { constructor(scope: Construct, id: string, props: ApiGatewayToSqsProps) { super(scope, id); - // Setup the encryption key - this.encryptionKey = defaults.buildEncryptionKey(this, props.encryptionKeyProps); - // Setup the dead letter queue, if applicable let dlqi: sqs.DeadLetterQueue | undefined; if (!props.deployDeadLetterQueue || props.deployDeadLetterQueue === true) { const dlq: sqs.Queue = defaults.buildQueue(this, 'deadLetterQueue', { - encryptionKey: this.encryptionKey, queueProps: props.queueProps }); dlqi = defaults.buildDeadLetterQueue({ @@ -118,8 +105,7 @@ export class ApiGatewayToSqs extends Construct { } // Setup the queue - this.queue = defaults.buildQueue(this, 'queue', { - encryptionKey: this.encryptionKey, + this.sqsQueue = defaults.buildQueue(this, 'queue', { queueProps: props.queueProps, deadLetterQueue: dlqi }); @@ -135,9 +121,6 @@ export class ApiGatewayToSqs extends Construct { // Setup the API Gateway resource const apiGatewayResource = this.apiGateway.root.addResource('message'); - // Grant encrypt/decrypt permissions for the API Gateway via KMS - this.encryptionKey.grantEncryptDecrypt(this.apiGatewayRole); - // Setup API Gateway methods // Create if (props.allowCreateOperation && props.allowCreateOperation === true && props.createRequestTemplate) { @@ -162,7 +145,7 @@ export class ApiGatewayToSqs extends Construct { private addActionToPolicy(action: string) { this.apiGatewayRole.addToPolicy(new iam.PolicyStatement({ resources: [ - this.queue.queueArn + this.sqsQueue.queueArn ], actions: [ `${action}` ] })); @@ -172,7 +155,7 @@ export class ApiGatewayToSqs extends Construct { // Add the integration const apiGatewayIntegration = new api.AwsIntegration({ service: "sqs", - path: `${cdk.Aws.ACCOUNT_ID}/${this.queue.queueName}`, + path: `${cdk.Aws.ACCOUNT_ID}/${this.sqsQueue.queueName}`, integrationHttpMethod: "POST", options: { passthroughBehavior: api.PassthroughBehavior.NEVER, @@ -217,24 +200,4 @@ export class ApiGatewayToSqs extends Construct { ] }); } - - /** - * @summary Returns an instance of the api.RestApi created by the construct. - * @returns {api.RestApi} Instance of the RestApi created by the construct. - * @since 0.8.0 - * @access public - */ - public api(): api.RestApi { - return this.apiGateway; - } - - /** - * @summary Returns an instance of the sqs.Queue created by the construct. - * @returns {sqs.Queue} Instance of the Queue created by the construct. - * @since 0.8.0 - * @access public - */ - public sqsQueue(): sqs.Queue { - return this.queue; - } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/package.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/package.json similarity index 57% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/package.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/package.json index f43cb2d80..fd7468e05 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-apigateway-sqs", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-apigateway-sqs", + "version": "1.46.0", "description": "CDK constructs for defining an interaction between an AWS Lambda function and an Amazon S3 bucket.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-apigateway-sqs" }, "author": { "name": "Amazon Web Services", @@ -24,6 +24,7 @@ "watch": "tsc -b -w", "integ": "cdk-integ", "integ-assert": "cdk-integ-assert", + "integ-no-clean": "cdk-integ --no-clean", "jsii": "jsii", "jsii-pacmak": "jsii-pacmak", "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", @@ -33,35 +34,35 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.apigatewaysqs", + "package": "software.amazon.awsconstructs.services.apigatewaysqs", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "apigatewaysqs" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.ApiGatewaySqs", - "packageId": "Amazon.Konstruk.AWS.ApiGatewaySqs", + "namespace": "Amazon.Constructs.AWS.ApiGatewaySqs", + "packageId": "Amazon.Constructs.AWS.ApiGatewaySqs", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-apigateway-sqs", - "module": "aws_solutions_konstruk.aws_apigateway_sqs" + "distName": "aws-solutions-constructs.aws-apigateway-sqs", + "module": "aws_solutions_constructs.aws_apigateway_sqs" } } }, "dependencies": { - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-sqs": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-sqs": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,12 +72,12 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-sqs": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-sqs": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap similarity index 83% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap index 2da122a15..752617217 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap @@ -36,76 +36,6 @@ Object { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", }, - "apigatewaysqsEncryptionKey4A698F7C": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "EnableKeyRotation": true, - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - Object { - "Action": Array [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsapigatewayrole2BA120D3", - "Arn", - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, "apigatewaysqsLambdaRestApiAccount8FA59342": Object { "DependsOn": Array [ "apigatewaysqsRestApi03BFD711", @@ -544,21 +474,6 @@ Object { "Properties": Object { "PolicyDocument": Object { "Statement": Array [ - Object { - "Action": Array [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, - }, Object { "Action": "sqs:SendMessage", "Effect": "Allow", @@ -603,23 +518,13 @@ Object { }, "apigatewaysqsdeadLetterQueue25B510FA": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", }, "Type": "AWS::SQS::Queue", }, "apigatewaysqsqueueE186B895": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", "RedrivePolicy": Object { "deadLetterTargetArn": Object { "Fn::GetAtt": Array [ @@ -672,76 +577,6 @@ Object { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", }, - "apigatewaysqsEncryptionKey4A698F7C": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "EnableKeyRotation": true, - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - Object { - "Action": Array [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsapigatewayrole2BA120D3", - "Arn", - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, "apigatewaysqsLambdaRestApiAccount8FA59342": Object { "DependsOn": Array [ "apigatewaysqsRestApi03BFD711", @@ -1180,21 +1015,6 @@ Object { "Properties": Object { "PolicyDocument": Object { "Statement": Array [ - Object { - "Action": Array [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, - }, Object { "Action": "sqs:SendMessage", "Effect": "Allow", @@ -1239,23 +1059,13 @@ Object { }, "apigatewaysqsdeadLetterQueue25B510FA": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", }, "Type": "AWS::SQS::Queue", }, "apigatewaysqsqueueE186B895": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", "RedrivePolicy": Object { "deadLetterTargetArn": Object { "Fn::GetAtt": Array [ @@ -1308,76 +1118,6 @@ Object { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", }, - "apigatewaysqsEncryptionKey4A698F7C": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "EnableKeyRotation": true, - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - Object { - "Action": Array [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsapigatewayrole2BA120D3", - "Arn", - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, "apigatewaysqsLambdaRestApiAccount8FA59342": Object { "DependsOn": Array [ "apigatewaysqsRestApi03BFD711", @@ -1647,21 +1387,6 @@ Object { "Properties": Object { "PolicyDocument": Object { "Statement": Array [ - Object { - "Action": Array [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, - }, Object { "Action": "sqs:ReceiveMessage", "Effect": "Allow", @@ -1686,23 +1411,13 @@ Object { }, "apigatewaysqsdeadLetterQueue25B510FA": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", }, "Type": "AWS::SQS::Queue", }, "apigatewaysqsqueueE186B895": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "apigatewaysqsEncryptionKey4A698F7C", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", "RedrivePolicy": Object { "deadLetterTargetArn": Object { "Fn::GetAtt": Array [ diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/apigateway-sqs.test.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/apigateway-sqs.test.ts similarity index 93% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/apigateway-sqs.test.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/apigateway-sqs.test.ts index 2fdbf63a3..c680a6fee 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/apigateway-sqs.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/apigateway-sqs.test.ts @@ -40,7 +40,6 @@ test('Test deployment w/ DLQ', () => { new ApiGatewayToSqs(stack, 'api-gateway-sqs', { apiGatewayProps: {}, queueProps: {}, - encryptionKeyProps: {}, createRequestTemplate: "{}", allowCreateOperation: true, allowReadOperation: true, @@ -61,7 +60,6 @@ test('Test deployment w/o DLQ', () => { new ApiGatewayToSqs(stack, 'api-gateway-sqs', { apiGatewayProps: {}, queueProps: {}, - encryptionKeyProps: {}, createRequestTemplate: "{}", allowCreateOperation: true, allowReadOperation: false, @@ -90,19 +88,20 @@ test('Test deployment w/o DLQ', () => { // -------------------------------------------------------------- // Test the getter methods // -------------------------------------------------------------- -test('Test the getter methods', () => { +test('Test properties', () => { // Stack const stack = new Stack(); // Helper declaration const pattern = new ApiGatewayToSqs(stack, 'api-gateway-sqs', { apiGatewayProps: {}, queueProps: {}, - encryptionKeyProps: {}, deployDeadLetterQueue: true, maxReceiveCount: 3 }); // Assertion 1 - expect(pattern.api()).toBeDefined(); + expect(pattern.apiGateway !== null); // Assertion 2 - expect(pattern.sqsQueue()).toBeDefined(); + expect(pattern.sqsQueue !== null); + // Assertion 3 + expect(pattern.apiGatewayRole !== null); }); diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json similarity index 84% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json index e8c175405..6f62f8470 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json @@ -1,96 +1,16 @@ { "Description": "Integration Test for aws-apigateway-sqs", "Resources": { - "testapigatewaysqsEncryptionKeyFD2F56B1": { - "Type": "AWS::KMS::Key", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" - ] - ] - } - }, - "Resource": "*" - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "testapigatewaysqsapigatewayrole07110CD6", - "Arn" - ] - } - }, - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "EnableKeyRotation": true - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, "testapigatewaysqsdeadLetterQueueAA4F6060": { "Type": "AWS::SQS::Queue", "Properties": { - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "testapigatewaysqsEncryptionKeyFD2F56B1", - "Arn" - ] - } + "KmsMasterKeyId": "alias/aws/sqs" } }, "testapigatewaysqsqueue8EDC3CAF": { "Type": "AWS::SQS::Queue", "Properties": { - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "testapigatewaysqsEncryptionKeyFD2F56B1", - "Arn" - ] - }, + "KmsMasterKeyId": "alias/aws/sqs", "RedrivePolicy": { "deadLetterTargetArn": { "Fn::GetAtt": [ @@ -546,21 +466,6 @@ "Properties": { "PolicyDocument": { "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "testapigatewaysqsEncryptionKeyFD2F56B1", - "Arn" - ] - } - }, { "Action": "sqs:SendMessage", "Effect": "Allow", diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts index e913a8800..6b421148d 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts @@ -24,7 +24,6 @@ stack.templateOptions.description = 'Integration Test for aws-apigateway-sqs'; const props: ApiGatewayToSqsProps = { apiGatewayProps: {}, queueProps: {}, - encryptionKeyProps: {}, allowReadOperation: true, allowCreateOperation: true, allowDeleteOperation: true, diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.no-arguments.expected.json similarity index 78% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.no-arguments.expected.json index 92c44fedd..e91626529 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.no-arguments.expected.json @@ -1,96 +1,16 @@ { "Description": "Integration Test for aws-apigateway-sqs", "Resources": { - "testapigatewaysqsdefaultEncryptionKey76707C9E": { - "Type": "AWS::KMS::Key", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" - ] - ] - } - }, - "Resource": "*" - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "testapigatewaysqsdefaultapigatewayrole080B85EC", - "Arn" - ] - } - }, - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "EnableKeyRotation": true - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, "testapigatewaysqsdefaultdeadLetterQueue24467CAD": { "Type": "AWS::SQS::Queue", "Properties": { - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "testapigatewaysqsdefaultEncryptionKey76707C9E", - "Arn" - ] - } + "KmsMasterKeyId": "alias/aws/sqs" } }, "testapigatewaysqsdefaultqueueCAC098BE": { "Type": "AWS::SQS::Queue", "Properties": { - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "testapigatewaysqsdefaultEncryptionKey76707C9E", - "Arn" - ] - }, + "KmsMasterKeyId": "alias/aws/sqs", "RedrivePolicy": { "deadLetterTargetArn": { "Fn::GetAtt": [ @@ -377,21 +297,6 @@ "Properties": { "PolicyDocument": { "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "testapigatewaysqsdefaultEncryptionKey76707C9E", - "Arn" - ] - } - }, { "Action": "sqs:ReceiveMessage", "Effect": "Allow", diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.no-arguments.ts similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.no-arguments.ts index 3da9f042d..4fd448640 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-sqs/test/integ.no-arguments.ts @@ -23,8 +23,7 @@ stack.templateOptions.description = 'Integration Test for aws-apigateway-sqs'; // Definitions const props: ApiGatewayToSqsProps = { apiGatewayProps: {}, - queueProps: {}, - encryptionKeyProps: {} + queueProps: {} }; new ApiGatewayToSqs(stack, 'test-api-gateway-sqs-default', props); diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/README.md similarity index 58% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/README.md index 1901ce22b..ebfb0c006 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,22 +12,23 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-cloudfront-apigateway-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_cloudfront_apigateway_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_cloudfront_apigateway_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-cloudfront-apigateway-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.cloudfrontapigatewaylambda`| -This AWS Solutions Konstruk implements an AWS Cloudfront fronting an Amazon API Gateway Lambda backed REST API. +This AWS Solutions Construct implements an AWS CloudFront fronting an Amazon API Gateway Lambda backed REST API. Here is a minimal deployable pattern definition: ``` javascript -import * as defaults from '@aws-solutions-konstruk/core'; -import { CloudFrontToApiGatewayToLambda } from '@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda'; +import * as defaults from '@aws-solutions-constructs/core'; +import { CloudFrontToApiGatewayToLambda } from '@aws-solutions-constructs/aws-cloudfront-apigateway-lambda'; const stack = new Stack(); @@ -65,16 +64,34 @@ _Parameters_ |existingLambdaObj?|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Existing instance of Lambda Function object| |lambdaFunctionProps?|[`lambda.FunctionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.FunctionProps.html)|Optional user provided props to override the default props for Lambda function| |apiGatewayProps?|[`api.LambdaRestApiProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaRestApiProps.html)|Optional user provided props to override the default props for API Gateway| -|cloudFrontDistributionProps?|[`cloudfront.CloudFrontWebDistributionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistributionProps.html)|Optional user provided props to override the default props for Cloudfront Distribution| -|insertHttpSecurityHeaders?|`boolean`|Optional user provided props to turn on/off the automatic injection of best practice HTTP security headers in all resonses from cloudfront| +|cloudFrontDistributionProps?|[`cloudfront.CloudFrontWebDistributionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistributionProps.html)|Optional user provided props to override the default props for CloudFront Distribution| +|insertHttpSecurityHeaders?|`boolean`|Optional user provided props to turn on/off the automatic injection of best practice HTTP security headers in all responses from CloudFront| ## Pattern Properties | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|cloudFrontWebDistribution()|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| -|restApi()|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of the API Gateway REST API created by the pattern.| +|cloudFrontWebDistribution|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| +|apiGateway|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of the API Gateway REST API created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon CloudFront +* Configure Access logging for CloudFront WebDistribution +* Enable automatic injection of best practice HTTP security headers in all responses from CloudFront WebDistribution + +### Amazon API Gateway +* Deploy a regional API endpoint +* Enable CloudWatch logging for API Gateway +* Configure least privilege access IAM role for API Gateway +* Set the default authorizationType for all API methods to IAM + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/lib/index.ts similarity index 68% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/lib/index.ts index fefc17799..76b74531c 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/lib/index.ts @@ -14,9 +14,9 @@ import * as api from '@aws-cdk/aws-apigateway'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cloudfront from '@aws-cdk/aws-cloudfront'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; -import { CloudFrontToApiGateway } from '@aws-solutions-konstruk/aws-cloudfront-apigateway'; +import { CloudFrontToApiGateway } from '@aws-solutions-constructs/aws-cloudfront-apigateway'; /** * @summary The properties for the CloudFrontToApiGatewayToLambda Construct @@ -57,7 +57,7 @@ export interface CloudFrontToApiGatewayToLambdaProps { readonly cloudFrontDistributionProps?: cloudfront.CloudFrontWebDistributionProps | any, /** * Optional user provided props to turn on/off the automatic injection of best practice HTTP - * security headers in all resonses from cloudfront + * security headers in all responses from cloudfront * * @default - true */ @@ -65,9 +65,9 @@ export interface CloudFrontToApiGatewayToLambdaProps { } export class CloudFrontToApiGatewayToLambda extends Construct { - private cloudfront: cloudfront.CloudFrontWebDistribution; - private api: api.RestApi; - private fn: lambda.Function; + public readonly cloudFrontWebDistribution: cloudfront.CloudFrontWebDistribution; + public readonly apiGateway: api.RestApi; + public readonly lambdaFunction: lambda.Function; /** * @summary Constructs a new instance of the CloudFrontToApiGatewayToLambda class. @@ -80,50 +80,20 @@ export class CloudFrontToApiGatewayToLambda extends Construct { constructor(scope: Construct, id: string, props: CloudFrontToApiGatewayToLambdaProps) { super(scope, id); - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps }); - this.api = defaults.RegionalLambdaRestApi(this, this.fn, props.apiGatewayProps); + this.apiGateway = defaults.RegionalLambdaRestApi(this, this.lambdaFunction, props.apiGatewayProps); const apiCloudfront: CloudFrontToApiGateway = new CloudFrontToApiGateway(this, 'CloudFrontToApiGateway', { - existingApiGatewayObj: this.api, + existingApiGatewayObj: this.apiGateway, cloudFrontDistributionProps: props.cloudFrontDistributionProps, insertHttpSecurityHeaders: props.insertHttpSecurityHeaders }); - this.cloudfront = apiCloudfront.cloudFrontWebDistribution(); - } - - /** - * @summary Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct. - * @returns {cloudfront.CloudFrontWebDistribution} Instance of CloudFrontWebDistribution created by the construct - * @since 0.8.0 - * @access public - */ - public cloudFrontWebDistribution(): cloudfront.CloudFrontWebDistribution { - return this.cloudfront; - } - - /** - * @summary Returns an instance of api.RestApi created by the construct. - * @returns {api.RestApi} Instance of RestApi created by the construct - * @since 0.8.0 - * @access public - */ - public restApi(): api.RestApi { - return this.api; - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; + this.cloudFrontWebDistribution = apiCloudfront.cloudFrontWebDistribution; } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/package.json similarity index 53% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/package.json index f75483ec6..6492dde58 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-cloudfront-apigateway-lambda", + "version": "1.46.0", "description": "CDK Constructs for AWS Cloudfront to AWS API Gateway to AWS Lambda integration.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,36 +34,36 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.cloudfrontapigatewaylambda", + "package": "software.amazon.awsconstructs.services.cloudfrontapigatewaylambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "cloudfrontapigatewaylambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.CloudfrontApiGatewayLambda", - "packageId": "Amazon.Konstruk.AWS.CloudfrontApiGatewayLambda", + "namespace": "Amazon.Constructs.AWS.CloudfrontApiGatewayLambda", + "packageId": "Amazon.Constructs.AWS.CloudfrontApiGatewayLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-cloudfront-apigateway-lambda", - "module": "aws_solutions_konstruk.aws_cloudfront_apigateway_lambda" + "distName": "aws-solutions-constructs.aws-cloudfront-apigateway-lambda", + "module": "aws_solutions_constructs.aws_cloudfront_apigateway_lambda" } } }, "dependencies": { - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-solutions-konstruk/aws-cloudfront-apigateway": "~0.8.1", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-solutions-constructs/aws-cloudfront-apigateway": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -73,13 +73,13 @@ ] }, "peerDependencies": { - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-solutions-konstruk/aws-cloudfront-apigateway": "~0.8.1", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-solutions-constructs/aws-cloudfront-apigateway": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap similarity index 99% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap index b48deef2f..d8e5c4cfa 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap @@ -765,7 +765,7 @@ Object { Object { "Ref": "testcloudfrontapigatewaylambdaLambdaRestApi6A4CBD44", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, @@ -806,7 +806,7 @@ Object { Object { "Ref": "testcloudfrontapigatewaylambdaLambdaRestApiDeploymentStageprod4617A7B7", }, - "/*/{proxy+}", + "/*/*", ], ], }, diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json similarity index 99% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json index 28df3967d..305f776d3 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json @@ -240,7 +240,7 @@ { "Ref": "testcloudfrontapigatewaylambdaLambdaRestApiDeploymentStageprod4617A7B7" }, - "/*/{proxy+}" + "/*/*" ] ] } @@ -277,7 +277,7 @@ { "Ref": "testcloudfrontapigatewaylambdaLambdaRestApi6A4CBD44" }, - "/test-invoke-stage/*/{proxy+}" + "/test-invoke-stage/*/*" ] ] } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts index c9185ae4d..c76368890 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts @@ -49,14 +49,14 @@ test('snapshot test CloudFrontToApiGatewayToLambda default params', () => { expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: CloudFrontToApiGatewayToLambda = deployNewFunc(stack); - expect(construct.cloudFrontWebDistribution()).toBeDefined(); - expect(construct.restApi()).toBeDefined(); - expect(construct.lambdaFunction()).toBeDefined(); + expect(construct.cloudFrontWebDistribution !== null); + expect(construct.apiGateway !== null); + expect(construct.lambdaFunction !== null); }); test('check lambda function properties for deploy: true', () => { diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.gitignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.npmignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/README.md b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/README.md similarity index 61% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/README.md rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/README.md index 82ae9d7f1..621f1fb6b 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,22 +12,23 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-cloudfront-apigateway/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_cloudfront_apigateway`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-cloudfront-apigateway`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_cloudfront_apigateway`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-cloudfront-apigateway`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.cloudfrontapigateway`| -This AWS Solutions Konstruk implements an AWS Cloudfront fronting an Amazon API Gateway REST API. +This AWS Solutions Construct implements an AWS CloudFront fronting an Amazon API Gateway REST API. Here is a minimal deployable pattern definition: ``` javascript -const { defaults } = require('@aws-solutions-konstruk/core'); -const { CloudFrontToApiGateway } = require('@aws-solutions-konstruk/aws-cloudfront-apigateway'); +const { defaults } = require('@aws-solutions-constructs/core'); +const { CloudFrontToApiGateway } = require('@aws-solutions-constructs/aws-cloudfront-apigateway'); const stack = new Stack(); @@ -66,14 +65,25 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| |existingApiGatewayObj|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|The regional API Gateway that will be fronted with the CloudFront| -|cloudFrontDistributionProps?|[`cloudfront.CloudFrontWebDistributionProps | any`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistributionProps.html)|Optional user provided props to override the default props for Cloudfront Distribution| -|insertHttpSecurityHeaders?|`boolean`|Optional user provided props to turn on/off the automatic injection of best practice HTTP security headers in all resonses from cloudfront| +|cloudFrontDistributionProps?|[`cloudfront.CloudFrontWebDistributionProps | any`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistributionProps.html)|Optional user provided props to override the default props for CloudFront Distribution| +|insertHttpSecurityHeaders?|`boolean`|Optional user provided props to turn on/off the automatic injection of best practice HTTP security headers in all responses from CloudFront| ## Pattern Properties | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|cloudFrontWebDistribution()|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct| -|restApi()|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of the API Gateway REST API created by the pattern.| +|cloudFrontWebDistribution|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct| +|apiGateway|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of the API Gateway REST API created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon CloudFront +* Configure Access logging for CloudFront WebDistribution +* Enable automatic injection of best practice HTTP security headers in all responses from CloudFront WebDistribution + +### Amazon API Gateway +* User provided API Gateway object is used as-is ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/architecture.png b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/lib/index.ts similarity index 66% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/lib/index.ts index 06e7901ed..022f32822 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/lib/index.ts @@ -13,7 +13,7 @@ import * as api from '@aws-cdk/aws-apigateway'; import * as cloudfront from '@aws-cdk/aws-cloudfront'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; /** @@ -34,7 +34,7 @@ export interface CloudFrontToApiGatewayProps { readonly cloudFrontDistributionProps?: cloudfront.CloudFrontWebDistributionProps | any, /** * Optional user provided props to turn on/off the automatic injection of best practice HTTP - * security headers in all resonses from cloudfront + * security headers in all responses from cloudfront * * @default - true */ @@ -42,8 +42,8 @@ export interface CloudFrontToApiGatewayProps { } export class CloudFrontToApiGateway extends Construct { - private cloudfront: cloudfront.CloudFrontWebDistribution; - private api: api.RestApi; + public readonly cloudFrontWebDistribution: cloudfront.CloudFrontWebDistribution; + public readonly apiGateway: api.RestApi; /** * @summary Constructs a new instance of the CloudFrontToApiGateway class. @@ -56,29 +56,9 @@ export class CloudFrontToApiGateway extends Construct { constructor(scope: Construct, id: string, props: CloudFrontToApiGatewayProps) { super(scope, id); - this.api = props.existingApiGatewayObj; + this.apiGateway = props.existingApiGatewayObj; - this.cloudfront = defaults.CloudFrontDistributionForApiGateway(this, props.existingApiGatewayObj, + this.cloudFrontWebDistribution = defaults.CloudFrontDistributionForApiGateway(this, props.existingApiGatewayObj, props.cloudFrontDistributionProps, props.insertHttpSecurityHeaders); } - - /** - * @summary Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct. - * @returns {cloudfront.CloudFrontWebDistribution} Instance of CloudFrontWebDistribution created by the construct - * @since 0.8.0 - * @access public - */ - public cloudFrontWebDistribution(): cloudfront.CloudFrontWebDistribution { - return this.cloudfront; - } - - /** - * @summary Returns an instance of api.RestApi created by the construct. - * @returns {api.RestApi} Instance of RestApi created by the construct - * @since 0.8.0 - * @access public - */ - public restApi(): api.RestApi { - return this.api; - } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/package.json b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/package.json similarity index 56% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/package.json rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/package.json index 95cb9debf..3135dd4ca 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-cloudfront-apigateway", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-cloudfront-apigateway", + "version": "1.46.0", "description": "CDK Constructs for AWS Cloudfront to AWS API Gateway integration.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway" }, "author": { "name": "Amazon Web Services", @@ -34,35 +34,35 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.cloudfrontapigateway", + "package": "software.amazon.awsconstructs.services.cloudfrontapigateway", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "cloudfrontapigateway" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.CloudfrontApiGateway", - "packageId": "Amazon.Konstruk.AWS.CloudfrontApiGateway", + "namespace": "Amazon.Constructs.AWS.CloudfrontApiGateway", + "packageId": "Amazon.Constructs.AWS.CloudfrontApiGateway", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-cloudfront-apigateway", - "module": "aws_solutions_konstruk.aws_cloudfront_apigateway" + "distName": "aws-solutions-constructs.aws-cloudfront-apigateway", + "module": "aws_solutions_constructs.aws_cloudfront_apigateway" } } }, "dependencies": { - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -72,12 +72,12 @@ ] }, "peerDependencies": { - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/__snapshots__/test.cloudfront-apigateway.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/__snapshots__/test.cloudfront-apigateway.test.js.snap similarity index 99% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/__snapshots__/test.cloudfront-apigateway.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/__snapshots__/test.cloudfront-apigateway.test.js.snap index a1739d0ba..a454b7a32 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/__snapshots__/test.cloudfront-apigateway.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/__snapshots__/test.cloudfront-apigateway.test.js.snap @@ -529,7 +529,7 @@ Object { Object { "Ref": "LambdaRestApiDeploymentStageprodB1F3862A", }, - "/*/{proxy+}", + "/*/*", ], ], }, @@ -566,7 +566,7 @@ Object { Object { "Ref": "LambdaRestApi95870433", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/integ.no-arguments.expected.json similarity index 99% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/integ.no-arguments.expected.json index 1f4d0046f..a9577acc8 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/integ.no-arguments.expected.json @@ -240,7 +240,7 @@ { "Ref": "LambdaRestApiDeploymentStageprodB1F3862A" }, - "/*/{proxy+}" + "/*/*" ] ] } @@ -277,7 +277,7 @@ { "Ref": "LambdaRestApi95870433" }, - "/test-invoke-stage/*/{proxy+}" + "/test-invoke-stage/*/*" ] ] } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/integ.no-arguments.ts similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/integ.no-arguments.ts index d9dffb3cb..3da513c56 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/integ.no-arguments.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/integ.no-arguments.ts @@ -15,7 +15,7 @@ import { App, Stack } from "@aws-cdk/core"; import { CloudFrontToApiGateway } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; // Setup const app = new App(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts index 15f8d4f6e..10f7c80b3 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts @@ -14,7 +14,7 @@ import { SynthUtils, ResourcePart } from '@aws-cdk/assert'; import { CloudFrontToApiGateway } from "../lib"; import * as cdk from "@aws-cdk/core"; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import * as lambda from '@aws-cdk/aws-lambda'; import '@aws-cdk/assert/jest'; @@ -45,8 +45,8 @@ test('check getter methods', () => { const construct: CloudFrontToApiGateway = deploy(stack); - expect(construct.cloudFrontWebDistribution()).toBeDefined(); - expect(construct.restApi()).toBeDefined(); + expect(construct.cloudFrontWebDistribution !== null); + expect(construct.apiGateway !== null); }); test('test cloudfront DomainName', () => { diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.gitignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.npmignore b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/README.md b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/README.md similarity index 61% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/README.md rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/README.md index 4a14b7186..23a682421 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-cloudfront-s3/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_cloudfront_s3`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-cloudfront-s3`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_cloudfront_s3`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-cloudfront-s3`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.cloudfronts3`| -This AWS Solutions Konstruk implements an AWS Cloudfront fronting an AWS S3 Bucket. +This AWS Solutions Construct implements an AWS CloudFront fronting an AWS S3 Bucket. Here is a minimal deployable pattern definition: ``` javascript -const { CloudFrontToS3 } = require('@aws-solutions-konstruk/aws-cloudfront-s3'); +const { CloudFrontToS3 } = require('@aws-solutions-constructs/aws-cloudfront-s3'); new CloudFrontToS3(stack, 'test-cloudfront-s3', { deployBucket: true @@ -55,15 +54,30 @@ _Parameters_ |deployBucket|`boolean`|Whether to create a S3 Bucket or use an existing S3 Bucket| |existingBucketObj?|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Existing instance of S3 Bucket object| |bucketProps?|[`s3.BucketProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.BucketProps.html)|Optional user provided props to override the default props for S3 Bucket| -|cloudFrontDistributionProps?|[`cloudfront.CloudFrontWebDistributionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistributionProps.html)|Optional user provided props to override the default props for Cloudfront Distribution| -|insertHttpSecurityHeaders?|`boolean`|Optional user provided props to turn on/off the automatic injection of best practice HTTP security headers in all resonses from cloudfront| +|cloudFrontDistributionProps?|[`cloudfront.CloudFrontWebDistributionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistributionProps.html)|Optional user provided props to override the default props for CloudFront Distribution| +|insertHttpSecurityHeaders?|`boolean`|Optional user provided props to turn on/off the automatic injection of best practice HTTP security headers in all responses from CloudFront| ## Pattern Properties | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|cloudFrontWebDistribution()|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct| -|bucket()|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct| +|cloudFrontWebDistribution|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct| +|s3Bucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon CloudFront +* Configure Access logging for CloudFront WebDistribution +* Enable automatic injection of best practice HTTP security headers in all responses from CloudFront WebDistribution + +### Amazon S3 Bucket +* Configure Access logging for S3 Bucket +* Enable server-side encryption for S3 Bucket using AWS managed KMS Key +* Turn on the versioning for S3 Bucket +* Don't allow public access for S3 Bucket +* Retain the S3 Bucket when deleting the CloudFormation stack ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/architecture.png b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/lib/index.ts similarity index 73% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/lib/index.ts index 1732e7f07..f702359c3 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/lib/index.ts @@ -14,7 +14,7 @@ import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as s3 from '@aws-cdk/aws-s3'; import { Construct } from '@aws-cdk/core'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; /** * @summary The properties for the CloudFrontToS3 Construct @@ -49,7 +49,7 @@ export interface CloudFrontToS3Props { readonly cloudFrontDistributionProps?: cloudfront.CloudFrontWebDistributionProps | any, /** * Optional user provided props to turn on/off the automatic injection of best practice HTTP - * security headers in all resonses from cloudfront + * security headers in all responses from cloudfront * * @default - true */ @@ -57,8 +57,8 @@ export interface CloudFrontToS3Props { } export class CloudFrontToS3 extends Construct { - private cloudfront: cloudfront.CloudFrontWebDistribution; - private s3Bucket: s3.Bucket; + public readonly cloudFrontWebDistribution: cloudfront.CloudFrontWebDistribution; + public readonly s3Bucket: s3.Bucket; /** * @summary Constructs a new instance of the CloudFrontToS3 class. @@ -77,27 +77,7 @@ export class CloudFrontToS3 extends Construct { bucketProps: props.bucketProps }); - this.cloudfront = defaults.CloudFrontDistributionForS3(this, this.s3Bucket, + this.cloudFrontWebDistribution = defaults.CloudFrontDistributionForS3(this, this.s3Bucket, props.cloudFrontDistributionProps, props.insertHttpSecurityHeaders); } - - /** - * @summary Returns an instance of cloudfront.CloudFrontWebDistribution created by the construct - * @returns {cloudfront.CloudFrontWebDistribution} Instance of CloudFrontWebDistribution created by the construct - * @since 0.8.0 - * @access public - */ - public cloudFrontWebDistribution(): cloudfront.CloudFrontWebDistribution { - return this.cloudfront; - } - - /** - * @summary Returns an instance of s3.Bucket created by the construct. - * @returns {s3.Bucket} Instance of Bucket created by the construct - * @since 0.8.0 - * @access public - */ - public bucket(): s3.Bucket { - return this.s3Bucket; - } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/package.json b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/package.json similarity index 61% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/package.json rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/package.json index 943f0ab85..33a3b38e4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-cloudfront-s3", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-cloudfront-s3", + "version": "1.46.0", "description": "CDK Constructs for AWS Cloudfront to AWS S3 integration.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-cloudfront-s3" }, "author": { "name": "Amazon Web Services", @@ -34,33 +34,33 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.cloudfronts3", + "package": "software.amazon.awsconstructs.services.cloudfronts3", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "cloudfronts3" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.CloudfrontS3", - "packageId": "Amazon.Konstruk.AWS.CloudfrontS3", + "namespace": "Amazon.Constructs.AWS.CloudfrontS3", + "packageId": "Amazon.Constructs.AWS.CloudfrontS3", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-cloudfront-s3", - "module": "aws_solutions_konstruk.aws_cloudfront_s3" + "distName": "aws-solutions-constructs.aws-cloudfront-s3", + "module": "aws_solutions_constructs.aws_cloudfront_s3" } } }, "dependencies": { - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -70,10 +70,10 @@ ] }, "peerDependencies": { - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/__snapshots__/test.cloudfront-s3.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/__snapshots__/test.cloudfront-s3.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/__snapshots__/test.cloudfront-s3.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/__snapshots__/test.cloudfront-s3.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-security-headers.expected.json b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-security-headers.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-security-headers.expected.json rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-security-headers.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-security-headers.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-security-headers.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-security-headers.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/integ.no-security-headers.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts rename to source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts index 23098e514..63ae5c977 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts @@ -212,11 +212,11 @@ test('check exception for Missing existingObj from props for deploy = false', () } }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: CloudFrontToS3 = deploy(stack); - expect(construct.cloudFrontWebDistribution()).toBeDefined(); - expect(construct.bucket()).toBeDefined(); + expect(construct.cloudFrontWebDistribution !== null); + expect(construct.s3Bucket !== null); }); diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/README.md similarity index 62% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/README.md index 2b139ac10..62bb315c4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-cognito-apigateway-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_cognito_apigateway_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-cognito-apigateway-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_cognito_apigateway_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-cognito-apigateway-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.cognitoapigatewaylambda`| -This AWS Solutions Konstruk implements an Amazon Cognito securing an Amazon API Gateway Lambda backed REST APIs pattern. +This AWS Solutions Construct implements an Amazon Cognito securing an Amazon API Gateway Lambda backed REST APIs pattern. Here is a minimal deployable pattern definition: ``` javascript -const { CognitoToApiGatewayToLambda } = require('@aws-solutions-konstruk/aws-cognito-apigateway-lambda'); +const { CognitoToApiGatewayToLambda } = require('@aws-solutions-constructs/aws-cognito-apigateway-lambda'); const stack = new Stack(app, 'test-cognito-apigateway-lambda-stack'); @@ -71,10 +70,28 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|restApi()|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of api.RestApi created by the construct| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| -|userPool()|[`cognito.UserPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPool.html)|Returns an instance of cognito.UserPool created by the construct| -|userPoolClient()|[`cognito.UserPoolClient`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPoolClient.html)|Returns an instance of cognito.UserPoolClient created by the construct| +|apiGateway|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Returns an instance of api.RestApi created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| +|userPool|[`cognito.UserPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPool.html)|Returns an instance of cognito.UserPool created by the construct| +|userPoolClient|[`cognito.UserPoolClient`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPoolClient.html)|Returns an instance of cognito.UserPoolClient created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon Cognito +* Set password policy for User Pools +* Enforce the advanced security mode for User Pools + +### Amazon API Gateway +* Deploy an edge-optimized API endpoint +* Enable CloudWatch logging for API Gateway +* Configure least privilege access IAM role for API Gateway +* Set the default authorizationType for all API methods to IAM + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/lib/index.ts similarity index 66% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/lib/index.ts index 8e59fadaf..b10eb0334 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/lib/index.ts @@ -14,7 +14,7 @@ import * as api from '@aws-cdk/aws-apigateway'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cognito from '@aws-cdk/aws-cognito'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; /** @@ -63,10 +63,10 @@ export interface CognitoToApiGatewayToLambdaProps { } export class CognitoToApiGatewayToLambda extends Construct { - private userpool: cognito.UserPool; - private userpoolclient: cognito.UserPoolClient; - private api: api.RestApi; - private fn: lambda.Function; + public readonly userPool: cognito.UserPool; + public readonly userPoolClient: cognito.UserPoolClient; + public readonly apiGateway: api.RestApi; + public readonly lambdaFunction: lambda.Function; /** * @summary Constructs a new instance of the CognitoToApiGatewayToLambda class. @@ -79,24 +79,24 @@ export class CognitoToApiGatewayToLambda extends Construct { constructor(scope: Construct, id: string, props: CognitoToApiGatewayToLambdaProps) { super(scope, id); - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps }); - this.api = defaults.GlobalLambdaRestApi(this, this.fn, props.apiGatewayProps); - this.userpool = defaults.buildUserPool(this, props.cognitoUserPoolProps); - this.userpoolclient = defaults.buildUserPoolClient(this, this.userpool, props.cognitoUserPoolClientProps); + this.apiGateway = defaults.GlobalLambdaRestApi(this, this.lambdaFunction, props.apiGatewayProps); + this.userPool = defaults.buildUserPool(this, props.cognitoUserPoolProps); + this.userPoolClient = defaults.buildUserPoolClient(this, this.userPool, props.cognitoUserPoolClientProps); const cfnAuthorizer = new api.CfnAuthorizer(this, 'CognitoAuthorizer', { - restApiId: this.api.restApiId, + restApiId: this.apiGateway.restApiId, type: 'COGNITO_USER_POOLS', - providerArns: [this.userpool.userPoolArn], + providerArns: [this.userPool.userPoolArn], identitySource: "method.request.header.Authorization", name: "authorizer" }); - this.api.methods.forEach((apiMethod) => { + this.apiGateway.methods.forEach((apiMethod) => { // Leave the authorizer NONE for HTTP OPTIONS method, for the rest set it to COGNITO const child = apiMethod.node.findChild('Resource') as api.CfnMethod; if (apiMethod.httpMethod === 'OPTIONS') { @@ -107,44 +107,4 @@ export class CognitoToApiGatewayToLambda extends Construct { } }); } - - /** - * @summary Returns an instance of api.RestApi created by the construct. - * @returns {api.RestApi} Instance of RestApi created by the construct - * @since 0.8.0 - * @access public - */ - public restApi(): api.RestApi { - return this.api; - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of cognito.UserPool created by the construct. - * @returns {cognito.UserPool} Instance of UserPool created by the construct - * @since 0.8.0 - * @access public - */ - public userPool(): cognito.UserPool { - return this.userpool; - } - - /** - * @summary Returns an instance of cognito.UserPoolClient created by the construct. - * @returns {cognito.UserPoolClient} Instance of UserPoolClient created by the construct - * @since 0.8.0 - * @access public - */ - public userPoolClient(): cognito.UserPoolClient { - return this.userpoolclient; - } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/package.json similarity index 58% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/package.json index 0ea892116..b8ea4e4c7 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-cognito-apigateway-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-cognito-apigateway-lambda", + "version": "1.46.0", "description": "CDK Constructs for AWS Cognito to AWS API Gateway to AWS Lambda integration", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,34 +34,34 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.cognitoapigatewaylambda", + "package": "software.amazon.awsconstructs.services.cognitoapigatewaylambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "cognitoapigatewaylambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.CognitoApigatewayLambda", - "packageId": "Amazon.Konstruk.AWS.CognitoApigatewayLambda", + "namespace": "Amazon.Constructs.AWS.CognitoApigatewayLambda", + "packageId": "Amazon.Constructs.AWS.CognitoApigatewayLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-cognito-apigateway-lambda", - "module": "aws_solutions_konstruk.aws_cognito_apigateway_lambda" + "distName": "aws-solutions-constructs.aws-cognito-apigateway-lambda", + "module": "aws_solutions_constructs.aws_cognito_apigateway_lambda" } } }, "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,11 +71,11 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap index 12f9d5f05..6a27c2a9b 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap @@ -71,6 +71,24 @@ Object { }, "testcognitoapigatewaylambdaCognitoUserPoolClientDA118627": Object { "Properties": Object { + "AllowedOAuthFlows": Array [ + "implicit", + "code", + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": Array [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin", + ], + "CallbackURLs": Array [ + "https://example.com", + ], + "SupportedIdentityProviders": Array [ + "COGNITO", + ], "UserPoolId": Object { "Ref": "testcognitoapigatewaylambdaCognitoUserPoolD5E74489", }, @@ -79,6 +97,18 @@ Object { }, "testcognitoapigatewaylambdaCognitoUserPoolD5E74489": Object { "Properties": Object { + "AccountRecoverySetting": Object { + "RecoveryMechanisms": Array [ + Object { + "Name": "verified_phone_number", + "Priority": 1, + }, + Object { + "Name": "verified_email", + "Priority": 2, + }, + ], + }, "AdminCreateUserConfig": Object { "AllowAdminCreateUserOnly": true, }, @@ -634,7 +664,7 @@ Object { Object { "Ref": "testcognitoapigatewaylambdaLambdaRestApi2E272431", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, @@ -675,7 +705,7 @@ Object { Object { "Ref": "testcognitoapigatewaylambdaLambdaRestApiDeploymentStageprod850C17D1", }, - "/*/{proxy+}", + "/*/*", ], ], }, diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json index 3109add75..227901718 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json @@ -239,7 +239,7 @@ { "Ref": "testcognitoapigatewaylambdaLambdaRestApiDeploymentStageprod850C17D1" }, - "/*/{proxy+}" + "/*/*" ] ] } @@ -276,7 +276,7 @@ { "Ref": "testcognitoapigatewaylambdaLambdaRestApi2E272431" }, - "/test-invoke-stage/*/{proxy+}" + "/test-invoke-stage/*/*" ] ] } @@ -584,6 +584,18 @@ "testcognitoapigatewaylambdaCognitoUserPoolD5E74489": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, @@ -615,7 +627,25 @@ "Properties": { "UserPoolId": { "Ref": "testcognitoapigatewaylambdaCognitoUserPoolD5E74489" - } + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO" + ] } }, "testcognitoapigatewaylambdaCognitoAuthorizer170CACC9": { diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts similarity index 93% rename from source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts index 2eefc1f17..07d2ff11f 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts @@ -101,13 +101,13 @@ test('check exception for Missing existingObj from props for deploy = false', () } }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: CognitoToApiGatewayToLambda = deployNewFunc(stack); - expect(construct.userPool()).toBeDefined(); - expect(construct.userPoolClient()).toBeDefined(); - expect(construct.restApi()).toBeDefined(); - expect(construct.lambdaFunction()).toBeDefined(); + expect(construct.userPool !== null); + expect(construct.userPoolClient !== null); + expect(construct.apiGateway !== null); + expect(construct.lambdaFunction !== null); }); diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.gitignore b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.npmignore b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md similarity index 53% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md index 09da02fd5..6aebf0c15 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-dynamodb-stream-lambda-elasticsearch-kibana/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_dynamodb_stream_elasticsearch_kibana`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_dynamodb_stream_elasticsearch_kibana`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.dynamodbstreamlambdaelasticsearchkibana`| -This AWS Solutions Konstruk implements Amazon DynamoDB table with stream, AWS Lambda function and Amazon Elasticsearch Service with the least privileged permissions. +This AWS Solutions Construct implements Amazon DynamoDB table with stream, AWS Lambda function and Amazon Elasticsearch Service with the least privileged permissions. Here is a minimal deployable pattern definition: ``` javascript -const { DynamoDBStreamToLambdaToElasticSearchAndKibana, DynamoDBStreamToLambdaToElasticSearchAndKibanaProps } = require('@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana'); +const { DynamoDBStreamToLambdaToElasticSearchAndKibana, DynamoDBStreamToLambdaToElasticSearchAndKibanaProps } = require('@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana'); const props: DynamoDBStreamToLambdaToElasticSearchAndKibanaProps = { deployLambda: true, @@ -71,13 +70,38 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|dynamoTable()|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| -|userPool()|[`cognito.UserPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPool.html)|Returns an instance of cognito.UserPool created by the construct| -|userPoolClient()|[`cognito.UserPoolClient`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPoolClient.html)|Returns an instance of cognito.UserPoolClient created by the construct| -|identityPool()|[`cognito.CfnIdentityPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.CfnIdentityPool.html)|Returns an instance of cognito.CfnIdentityPool created by the construct| -|elasticsearchDomain()|[`elasticsearch.CfnDomain`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticsearch.CfnDomain.html)|Returns an instance of elasticsearch.CfnDomain created by the construct| -|cloudwatchAlarms()|[`cloudwatch.Alarm[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudwatch.Alarm.html)|Returns a list of cloudwatch.Alarm created by the construct| +|dynamoTable|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| +|userPool|[`cognito.UserPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPool.html)|Returns an instance of cognito.UserPool created by the construct| +|userPoolClient|[`cognito.UserPoolClient`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPoolClient.html)|Returns an instance of cognito.UserPoolClient created by the construct| +|identityPool|[`cognito.CfnIdentityPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.CfnIdentityPool.html)|Returns an instance of cognito.CfnIdentityPool created by the construct| +|elasticsearchDomain|[`elasticsearch.CfnDomain`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticsearch.CfnDomain.html)|Returns an instance of elasticsearch.CfnDomain created by the construct| +|cloudwatchAlarms|[`cloudwatch.Alarm[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudwatch.Alarm.html)|Returns a list of cloudwatch.Alarm created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon DynamoDB Table +* Set the billing mode for DynamoDB Table to On-Demand (Pay per request) +* Enable server-side encryption for DynamoDB Table using AWS managed KMS Key +* Creates a partition key called 'id' for DynamoDB Table +* Retain the Table when deleting the CloudFormation stack + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function + +### Amazon Cognito +* Set password policy for User Pools +* Enforce the advanced security mode for User Pools + +### Amazon Elasticsearch Service +* Deploy best practices CloudWatch Alarms for the Elasticsearch Domain +* Secure the Kibana dashboard access with Cognito User Pools +* Enable server-side encryption for Elasticsearch Domain using AWS managed KMS Key +* Enable node-to-node encryption for Elasticsearch Domain +* Configure the cluster for the Amazon ES domain ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/architecture.png b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts similarity index 60% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts index 65fb464d0..746fc7584 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts @@ -14,8 +14,8 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as elasticsearch from '@aws-cdk/aws-elasticsearch'; import { DynamoEventSourceProps } from '@aws-cdk/aws-lambda-event-sources'; -import { DynamoDBStreamToLambdaProps, DynamoDBStreamToLambda } from '@aws-solutions-konstruk/aws-dynamodb-stream-lambda'; -import { LambdaToElasticSearchAndKibanaProps, LambdaToElasticSearchAndKibana } from '@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana'; +import { DynamoDBStreamToLambdaProps, DynamoDBStreamToLambda } from '@aws-solutions-constructs/aws-dynamodb-stream-lambda'; +import { LambdaToElasticSearchAndKibanaProps, LambdaToElasticSearchAndKibana } from '@aws-solutions-constructs/aws-lambda-elasticsearch-kibana'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as cognito from '@aws-cdk/aws-cognito'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; @@ -75,7 +75,13 @@ export interface DynamoDBStreamToLambdaToElasticSearchAndKibanaProps { export class DynamoDBStreamToLambdaToElasticSearchAndKibana extends Construct { private dynamoDBStreamToLambda: DynamoDBStreamToLambda; private lambdaToElasticSearchAndKibana: LambdaToElasticSearchAndKibana; - private fn: lambda.Function; + public readonly lambdaFunction: lambda.Function; + public readonly dynamoTable: dynamodb.Table; + public readonly userPool: cognito.UserPool; + public readonly userPoolClient: cognito.UserPoolClient; + public readonly identityPool: cognito.CfnIdentityPool; + public readonly elasticsearchDomain: elasticsearch.CfnDomain; + public readonly cloudwatchAlarms: cloudwatch.Alarm[]; /** * @summary Constructs a new instance of the LambdaToDynamoDB class. @@ -98,85 +104,22 @@ export class DynamoDBStreamToLambdaToElasticSearchAndKibana extends Construct { this.dynamoDBStreamToLambda = new DynamoDBStreamToLambda(this, 'DynamoDBStreamToLambda', _props1); - this.fn = this.dynamoDBStreamToLambda.lambdaFunction(); + this.lambdaFunction = this.dynamoDBStreamToLambda.lambdaFunction; const _props2: LambdaToElasticSearchAndKibanaProps = { deployLambda: false, - existingLambdaObj: this.fn, + existingLambdaObj: this.lambdaFunction, domainName: props.domainName, esDomainProps: props.esDomainProps }; this.lambdaToElasticSearchAndKibana = new LambdaToElasticSearchAndKibana(this, 'LambdaToElasticSearch', _props2); - } - /** - * @summary Returns an instance of dynamodb.Table created by the construct. - * @returns {dynamodb.Table} Instance of dynamodb.Table created by the construct - * @since 0.8.0 - * @access public - */ - public dynamoTable(): dynamodb.Table { - return this.dynamoDBStreamToLambda.dynamoTable(); - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of lambda.Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.dynamoDBStreamToLambda.lambdaFunction(); - } - - /** - * @summary Returns an instance of cognito.UserPool created by the construct. - * @returns {cognito.UserPool} Instance of UserPool created by the construct - * @since 0.8.0 - * @access public - */ - public userPool(): cognito.UserPool { - return this.lambdaToElasticSearchAndKibana.userPool(); - } - - /** - * @summary Returns an instance of cognito.UserPoolClient created by the construct. - * @returns {cognito.UserPoolClient} Instance of UserPoolClient created by the construct - * @since 0.8.0 - * @access public - */ - public userPoolClient(): cognito.UserPoolClient { - return this.lambdaToElasticSearchAndKibana.userPoolClient(); - } - - /** - * @summary Returns an instance of cognito.CfnIdentityPool created by the construct. - * @returns {cognito.CfnIdentityPool} Instance of CfnIdentityPool created by the construct - * @since 0.8.0 - * @access public - */ - public identityPool(): cognito.CfnIdentityPool { - return this.lambdaToElasticSearchAndKibana.identityPool(); - } - - /** - * @summary Returns an instance of elasticsearch.CfnDomain created by the construct. - * @returns {elasticsearch.CfnDomain} Instance of CfnDomain created by the construct - * @since 0.8.0 - * @access public - */ - public elasticsearchDomain(): elasticsearch.CfnDomain { - return this.lambdaToElasticSearchAndKibana.elasticsearchDomain(); - } - - /** - * @summary Returns a list of cloudwatch.Alarm created by the construct. - * @returns {cloudwatch.Alarm[]} List of cloudwatch.Alarm created by the construct - * @since 0.8.0 - * @access public - */ - public cloudwatchAlarms(): cloudwatch.Alarm[] { - return this.lambdaToElasticSearchAndKibana.cloudwatchAlarms(); + this.dynamoTable = this.dynamoDBStreamToLambda.dynamoTable; + this.userPool = this.lambdaToElasticSearchAndKibana.userPool; + this.userPoolClient = this.lambdaToElasticSearchAndKibana.userPoolClient; + this.identityPool = this.lambdaToElasticSearchAndKibana.identityPool; + this.elasticsearchDomain = this.lambdaToElasticSearchAndKibana.elasticsearchDomain; + this.cloudwatchAlarms = this.lambdaToElasticSearchAndKibana.cloudwatchAlarms; } } diff --git a/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json new file mode 100644 index 000000000..30f4645e7 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json @@ -0,0 +1,91 @@ +{ + "name": "@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana", + "version": "1.46.0", + "description": "CDK Constructs for Amazon Dynamodb stream to AWS Lambda to AWS Elasticsearch with Kibana integration", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc -b .", + "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", + "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", + "test": "jest --coverage", + "clean": "tsc -b --clean", + "watch": "tsc -b -w", + "integ": "cdk-integ", + "integ-no-clean": "cdk-integ --no-clean", + "integ-assert": "cdk-integ-assert", + "jsii": "jsii", + "jsii-pacmak": "jsii-pacmak", + "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", + "snapshot-update": "npm run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.dynamodbstreamlambdaelasticsearchkibana", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "dynamodbstreamlambdaelasticsearchkibana" + } + }, + "dotnet": { + "namespace": "Amazon.Constructs.AWS.DynamodbStreamLambdaElasticsearchKibana", + "packageId": "Amazon.Constructs.AWS.DynamodbStreamLambdaElasticsearchKibana", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.aws-dynamodb-stream-lambda-elasticsearch-kibana", + "module": "aws_solutions_constructs.aws_dynamodb_stream_lambda_elasticsearch_kibana" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-elasticsearch": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-dynamodb-stream-lambda": "~1.46.0", + "@aws-solutions-constructs/aws-lambda-elasticsearch-kibana": "~1.46.0", + "constructs": "^3.0.2" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.46.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-elasticsearch": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-dynamodb-stream-lambda": "~1.46.0", + "@aws-solutions-constructs/aws-lambda-elasticsearch-kibana": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "constructs": "^3.0.2" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap index 2679dfe89..15b4f5c9e 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap @@ -447,6 +447,24 @@ Object { }, "testdynamodbstreamlambdaelasticsearchstackLambdaToElasticSearchCognitoUserPoolClientE03C5E18": Object { "Properties": Object { + "AllowedOAuthFlows": Array [ + "implicit", + "code", + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": Array [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin", + ], + "CallbackURLs": Array [ + "https://example.com", + ], + "SupportedIdentityProviders": Array [ + "COGNITO", + ], "UserPoolId": Object { "Ref": "testdynamodbstreamlambdaelasticsearchstackLambdaToElasticSearchCognitoUserPoolF99F93E5", }, @@ -455,6 +473,18 @@ Object { }, "testdynamodbstreamlambdaelasticsearchstackLambdaToElasticSearchCognitoUserPoolF99F93E5": Object { "Properties": Object { + "AccountRecoverySetting": Object { + "RecoveryMechanisms": Array [ + Object { + "Name": "verified_phone_number", + "Priority": 1, + }, + Object { + "Name": "verified_email", + "Priority": 2, + }, + ], + }, "AdminCreateUserConfig": Object { "AllowAdminCreateUserOnly": true, }, diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts similarity index 78% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts index 62648749a..f62b089bd 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts @@ -14,10 +14,7 @@ import { SynthUtils } from '@aws-cdk/assert'; import { DynamoDBStreamToLambdaToElasticSearchAndKibana, DynamoDBStreamToLambdaToElasticSearchAndKibanaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as cdk from "@aws-cdk/core"; -import { CfnDomain } from '@aws-cdk/aws-elasticsearch'; -import { CfnIdentityPool, UserPool, UserPoolClient } from '@aws-cdk/aws-cognito'; import '@aws-cdk/assert/jest'; function deployNewFunc(stack: cdk.Stack) { @@ -58,18 +55,18 @@ test('check domain names', () => { }); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: DynamoDBStreamToLambdaToElasticSearchAndKibana = deployNewFunc(stack); - expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); - expect(construct.dynamoTable()).toBeInstanceOf(dynamodb.Table); - expect(construct.elasticsearchDomain()).toBeInstanceOf(CfnDomain); - expect(construct.identityPool()).toBeInstanceOf(CfnIdentityPool); - expect(construct.userPool()).toBeInstanceOf(UserPool); - expect(construct.userPoolClient()).toBeInstanceOf(UserPoolClient); - expect(construct.cloudwatchAlarms()).toHaveLength(9); + expect(construct.lambdaFunction !== null); + expect(construct.dynamoTable !== null); + expect(construct.elasticsearchDomain !== null); + expect(construct.identityPool !== null); + expect(construct.userPool !== null); + expect(construct.userPoolClient !== null); + expect(construct.cloudwatchAlarms !== null); }); test('check exception for Missing existingObj from props for deploy = false', () => { diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json index 92458221c..e6bf88332 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json @@ -267,6 +267,18 @@ "testdynamodbstreamlambdaelasticsearchkibanaLambdaToElasticSearchCognitoUserPool77221A72": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, @@ -298,7 +310,25 @@ "Properties": { "UserPoolId": { "Ref": "testdynamodbstreamlambdaelasticsearchkibanaLambdaToElasticSearchCognitoUserPool77221A72" - } + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO" + ] } }, "testdynamodbstreamlambdaelasticsearchkibanaLambdaToElasticSearchCognitoIdentityPool7F08EC45": { @@ -324,7 +354,7 @@ "testdynamodbstreamlambdaelasticsearchkibanaLambdaToElasticSearchUserPoolDomain0A904A18": { "Type": "AWS::Cognito::UserPoolDomain", "Properties": { - "Domain": "myvesperdomain", + "Domain": "myconstructsdomain1", "UserPoolId": { "Ref": "testdynamodbstreamlambdaelasticsearchkibanaLambdaToElasticSearchCognitoUserPool77221A72" } @@ -377,7 +407,7 @@ { "Ref": "AWS::AccountId" }, - ":domain/myvesperdomain/*" + ":domain/myconstructsdomain1/*" ] ] } @@ -482,7 +512,7 @@ { "Ref": "AWS::AccountId" }, - ":domain/myvesperdomain" + ":domain/myconstructsdomain1" ] ] } @@ -550,7 +580,7 @@ { "Ref": "AWS::AccountId" }, - ":domain/myvesperdomain/*" + ":domain/myconstructsdomain1/*" ] ] } @@ -573,7 +603,7 @@ "Ref": "testdynamodbstreamlambdaelasticsearchkibanaLambdaToElasticSearchCognitoUserPool77221A72" } }, - "DomainName": "myvesperdomain", + "DomainName": "myconstructsdomain1", "EBSOptions": { "EBSEnabled": true, "VolumeSize": 10 diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts index d34bf0dce..092feacf3 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts @@ -27,7 +27,7 @@ const props: DynamoDBStreamToLambdaToElasticSearchAndKibanaProps = { runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler' }, - domainName: 'myvesperdomain' + domainName: 'myconstructsdomain1' }; new DynamoDBStreamToLambdaToElasticSearchAndKibana(stack, 'test-dynamodb-stream-lambda-elasticsearch-kibana', props); diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/README.md similarity index 65% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/README.md index d7bf6887a..cf68afa76 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-dynamodb-stream-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_dynamodb_stream_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-dynamodb-stream-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_dynamodb_stream_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-dynamodb-stream-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.dynamodbstreamlambda`| -This AWS Solutions Konstruk implements a pattern Amazon DynamoDB table with stream to invoke the AWS Lambda function with the least privileged permissions. +This AWS Solutions Construct implements a pattern Amazon DynamoDB table with stream to invoke the AWS Lambda function with the least privileged permissions. Here is a minimal deployable pattern definition: ``` javascript -const { DynamoDBStreamToLambdaProps, DynamoDBStreamToLambda} = require('@aws-solutions-konstruk/aws-dynamodb-stream-lambda'); +const { DynamoDBStreamToLambdaProps, DynamoDBStreamToLambda} = require('@aws-solutions-constructs/aws-dynamodb-stream-lambda'); const props: DynamoDBStreamToLambdaProps = { deployLambda: true, @@ -69,8 +68,22 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|dynamoTable()|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| +|dynamoTable|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon DynamoDB Table +* Set the billing mode for DynamoDB Table to On-Demand (Pay per request) +* Enable server-side encryption for DynamoDB Table using AWS managed KMS Key +* Creates a partition key called 'id' for DynamoDB Table +* Retain the Table when deleting the CloudFormation stack + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/lib/index.ts similarity index 73% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/lib/index.ts index 1d5edf5f8..3cef2c808 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/lib/index.ts @@ -14,9 +14,9 @@ import * as lambda from '@aws-cdk/aws-lambda'; import { DynamoEventSourceProps, DynamoEventSource } from '@aws-cdk/aws-lambda-event-sources'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; -import { overrideProps } from '@aws-solutions-konstruk/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; /** * @summary The properties for the DynamoDBStreamToLambda Construct @@ -58,8 +58,8 @@ export interface DynamoDBStreamToLambdaProps { } export class DynamoDBStreamToLambda extends Construct { - private fn: lambda.Function; - private table: dynamodb.Table; + public readonly lambdaFunction: lambda.Function; + public readonly dynamoTable: dynamodb.Table; /** * @summary Constructs a new instance of the LambdaToDynamoDB class. @@ -72,7 +72,7 @@ export class DynamoDBStreamToLambda extends Construct { constructor(scope: Construct, id: string, props: DynamoDBStreamToLambdaProps) { super(scope, id); - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps @@ -81,37 +81,16 @@ export class DynamoDBStreamToLambda extends Construct { // Set the default props for DynamoDB table if (props.dynamoTableProps) { const dynamoTableProps = overrideProps(defaults.DefaultTableWithStreamProps, props.dynamoTableProps); - this.table = new dynamodb.Table(this, 'DynamoTable', dynamoTableProps); + this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', dynamoTableProps); } else { - this.table = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableWithStreamProps); + this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableWithStreamProps); } // Grant DynamoDB Stream read perimssion for lambda function - this.table.grantStreamRead(this.fn.grantPrincipal); + this.dynamoTable.grantStreamRead(this.lambdaFunction.grantPrincipal); // Create DynamDB trigger to invoke lambda function - this.fn.addEventSource(new DynamoEventSource(this.table, + this.lambdaFunction.addEventSource(new DynamoEventSource(this.dynamoTable, defaults.DynamoEventSourceProps(props.dynamoEventSourceProps))); } - - /** - * @summary Returns an instance of dynamodb.Table created by the construct. - * @returns {dynamodb.Table} Instance of dynamodb.Table created by the construct - * @since 0.8.0 - * @access public - */ - public dynamoTable(): dynamodb.Table { - return this.table; - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of lambda.Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/package.json similarity index 58% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/package.json index 03b23db00..3da3750b6 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-dynamodb-stream-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-dynamodb-stream-lambda", + "version": "1.46.0", "description": "CDK Constructs for AWS DynamoDB Stream to AWS Lambda integration.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,34 +34,34 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.dynamodbstreamlambda", + "package": "software.amazon.awsconstructs.services.dynamodbstreamlambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "dynamodbstreamlambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.DynamodbStreamLambda", - "packageId": "Amazon.Konstruk.AWS.DynamodbStreamLambda", + "namespace": "Amazon.Constructs.AWS.DynamodbStreamLambda", + "packageId": "Amazon.Constructs.AWS.DynamodbStreamLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-dynamodb-stream-lambda", - "module": "aws_solutions_konstruk.aws_dynamodb_stream_lambda" + "distName": "aws-solutions-constructs.aws-dynamodb-stream-lambda", + "module": "aws_solutions_constructs.aws_dynamodb_stream_lambda" } } }, "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,11 +71,11 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/__snapshots__/dynamodb-stream-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/__snapshots__/dynamodb-stream-lambda.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/__snapshots__/dynamodb-stream-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/__snapshots__/dynamodb-stream-lambda.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts index 882057a33..22f4de34b 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts @@ -187,8 +187,8 @@ test('check getter methods', () => { const construct: DynamoDBStreamToLambda = deployNewFunc(stack); - expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); - expect(construct.dynamoTable()).toBeInstanceOf(dynamodb.Table); + expect(construct.lambdaFunction !== null); + expect(construct.dynamoTable !== null); }); test('check exception for Missing existingObj from props for deploy = false', () => { diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-dynamodb-stream-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/README.md similarity index 67% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/README.md index 2d021809e..b0cf32b2e 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-events-rule-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_events_rule_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-events-rule-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_events_rule_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-events-rule-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.eventsrulelambda`| -This AWS Solutions Konstruk implements an AWS Events rule and an AWS Lambda function. +This AWS Solutions Construct implements an AWS Events rule and an AWS Lambda function. Here is a minimal deployable pattern definition: ``` javascript -const { EventsRuleToLambdaProps, EventsRuleToLambda } = require('@aws-solutions-konstruk/aws-events-rule-lambda'); +const { EventsRuleToLambdaProps, EventsRuleToLambda } = require('@aws-solutions-constructs/aws-events-rule-lambda'); const props: EventsRuleToLambdaProps = { deployLambda: true, @@ -70,8 +69,19 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|eventsRule()|[`events.Rule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.Rule.html)|Returns an instance of events.Rule created by the construct| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| +|eventsRule|[`events.Rule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.Rule.html)|Returns an instance of events.Rule created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon CloudWatch Events Rule +* Grant least privilege permissions to CloudWatch Events to trigger the Lambda Function + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/lib/index.ts similarity index 73% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/lib/index.ts index 4aa0fb7e0..830f25065 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/lib/index.ts @@ -13,10 +13,10 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as events from '@aws-cdk/aws-events'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import * as iam from '@aws-cdk/aws-iam'; import { Construct } from '@aws-cdk/core'; -import { overrideProps } from '@aws-solutions-konstruk/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; /** * @summary The properties for the CloudFrontToApiGateway Construct @@ -52,8 +52,8 @@ export interface EventsRuleToLambdaProps { } export class EventsRuleToLambda extends Construct { - private fn: lambda.Function; - private rule: events.Rule; + public readonly lambdaFunction: lambda.Function; + public readonly eventsRule: events.Rule; /** * @summary Constructs a new instance of the EventsRuleToLambda class. @@ -66,7 +66,7 @@ export class EventsRuleToLambda extends Construct { constructor(scope: Construct, id: string, props: EventsRuleToLambdaProps) { super(scope, id); - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps @@ -75,38 +75,18 @@ export class EventsRuleToLambda extends Construct { const lambdaFunc: events.IRuleTarget = { bind: () => ({ id: '', - arn: this.fn.functionArn + arn: this.lambdaFunction.functionArn }) }; const defaultEventsRuleProps = defaults.DefaultEventsRuleProps([lambdaFunc]); const eventsRuleProps = overrideProps(defaultEventsRuleProps, props.eventRuleProps, true); - this.rule = new events.Rule(this, 'EventsRule', eventsRuleProps); + this.eventsRule = new events.Rule(this, 'EventsRule', eventsRuleProps); - this.fn.addPermission("LambdaInvokePermission", { + this.lambdaFunction.addPermission("LambdaInvokePermission", { principal: new iam.ServicePrincipal('events.amazonaws.com'), - sourceArn: this.rule.ruleArn + sourceArn: this.eventsRule.ruleArn }); } - - /** - * @summary Returns an instance of events.Rule created by the construct. - * @returns {events.Rule} Instance of events.Rule created by the construct - * @since 0.8.0 - * @access public - */ - public eventsRule(): events.Rule { - return this.rule; - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of lambda.Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/package.json similarity index 59% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/package.json index d554b93c5..e074d4c7d 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-events-rule-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-events-rule-lambda", + "version": "1.46.0", "description": "CDK Constructs for deploying AWS Events Rule that inveokes AWS Lambda", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-events-rule-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,34 +34,34 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.eventsrulelambda", + "package": "software.amazon.awsconstructs.services.eventsrulelambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "eventsrulelambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.EventsRuleLambda", - "packageId": "Amazon.Konstruk.AWS.EventsRuleLambda", + "namespace": "Amazon.Constructs.AWS.EventsRuleLambda", + "packageId": "Amazon.Constructs.AWS.EventsRuleLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-events-rule-lambda", - "module": "aws_solutions_konstruk.aws_events_rule_lambda" + "distName": "aws-solutions-constructs.aws-events-rule-lambda", + "module": "aws_solutions_constructs.aws_events_rule_lambda" } } }, "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-events": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-events": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,11 +71,11 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-events": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-events": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/__snapshots__/events-rule-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/__snapshots__/events-rule-lambda.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/__snapshots__/events-rule-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/__snapshots__/events-rule-lambda.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/events-rule-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/events-rule-lambda.test.ts similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/events-rule-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/events-rule-lambda.test.ts index ccfc1b548..e841d655b 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/events-rule-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/events-rule-lambda.test.ts @@ -162,13 +162,13 @@ test('check events rule properties for deploy: true', () => { }); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: EventsRuleToLambda = deployNewFunc(stack); - expect(construct.eventsRule()).toBeInstanceOf(events.Rule); - expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); + expect(construct.eventsRule !== null); + expect(construct.lambdaFunction !== null); }); test('check exception for Missing existingObj from props for deploy = false', () => { diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.expected.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/integ.events-rule-no-argument.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.expected.json rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/integ.events-rule-no-argument.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-events-rule-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.gitignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.npmignore b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/.npmignore diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/README.md b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/README.md new file mode 100644 index 000000000..2af36fa18 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/README.md @@ -0,0 +1,88 @@ +# aws-events-rule-step-function module + + +--- + +![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) + +> All classes are under active development and subject to non-backward compatible changes or removal in any +> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. +> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + +--- + + +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| +|:-------------|:-------------| +
+ +| **Language** | **Package** | +|:-------------|-----------------| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_events_rule_step_function`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-events-rule-step-function`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.eventsrulestepfunction`| + +This AWS Solutions Construct implements an AWS Events rule and an AWS Step function. + +Here is a minimal deployable pattern definition: + +``` javascript +const { EventsRuleToStepFunction, EventsRuleToStepFunctionProps } = require('@aws-solutions-constructs/aws-events-rule-step-function'); + +const startState = new stepfunctions.Pass(stack, 'StartState'); + +const props: EventsRuleToStepFunctionProps = { + stateMachineProps: { + definition: startState + }, + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + } +}; + +new EventsRuleToStepFunction(stack, 'test-events-rule-step-function-stack', props); +``` + +## Initializer + +``` text +new EventsRuleToStepFunction(scope: Construct, id: string, props: EventsRuleToStepFunctionProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`EventsRuleToStepFunctionProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|stateMachineProps|[`sfn.StateMachineProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.StateMachineProps.html)|Optional user provided props to override the default props for sfn.StateMachine| +|eventRuleProps|[`events.RuleProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.RuleProps.html)|User provided eventRuleProps to override the defaults| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|eventsRule|[`events.Rule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.Rule.html)|Returns an instance of events.Rule created by the construct| +|stateMachine|[`sfn.StateMachine`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.StateMachine.html)|Returns an instance of sfn.StateMachine created by the construct| +|cloudwatchAlarms|[`cloudwatch.Alarm[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudwatch.Alarm.html)|Returns a list of cloudwatch.Alarm created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon CloudWatch Events Rule +* Grant least privilege permissions to CloudWatch Events to trigger the Lambda Function + +### AWS Step Function +* Enable CloudWatch logging for API Gateway +* Deploy best practices CloudWatch Alarms for the Step Function + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/architecture.png b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..b7144584211657c0213c633e83991080a806955d GIT binary patch literal 54507 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 ({ + id: '', + arn: this.stateMachine.stateMachineArn, + role: eventsRole + }) + }; + + // Defaults props for the Events + const defaultEventsRuleProps = defaults.DefaultEventsRuleProps([stateMachine]); + // Override the defaults with the user provided props + const eventsRuleProps = overrideProps(defaultEventsRuleProps, props.eventRuleProps, true); + // Create the Events Rule for the State Machine + this.eventsRule = new events.Rule(this, 'EventsRule', eventsRuleProps); + // Deploy best practices CW Alarms for State Machine + this.cloudwatchAlarms = defaults.buildStepFunctionCWAlarms(this, this.stateMachine); + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/package.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/package.json new file mode 100644 index 000000000..0b0df1563 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/package.json @@ -0,0 +1,87 @@ +{ + "name": "@aws-solutions-constructs/aws-events-rule-step-function", + "version": "1.46.0", + "description": "CDK Constructs for deploying AWS Events Rule that invokes AWS Step Function", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-events-rule-step-function" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc -b .", + "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", + "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", + "test": "jest --coverage", + "clean": "tsc -b --clean", + "watch": "tsc -b -w", + "integ": "cdk-integ", + "integ-no-clean": "cdk-integ --no-clean", + "integ-assert": "cdk-integ-assert", + "jsii": "jsii", + "jsii-pacmak": "jsii-pacmak", + "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", + "snapshot-update": "npm run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.eventsrulestepfunction", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "eventsrulestepfunction" + } + }, + "dotnet": { + "namespace": "Amazon.Constructs.AWS.EventsRuleStepFunction", + "packageId": "Amazon.Constructs.AWS.EventsRuleStepFunction", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.aws-events-rule-step-function", + "module": "aws_solutions_constructs.aws_events_rule_step_function" + } + } + }, + "dependencies": { + "@aws-cdk/aws-stepfunctions": "~1.46.0", + "@aws-cdk/aws-stepfunctions-tasks": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-events": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "constructs": "^3.0.2" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.46.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-stepfunctions": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-events": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "constructs": "^3.0.2", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-cdk/aws-stepfunctions-tasks": "~1.46.0" + } +} diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/__snapshots__/events-rule-step-function.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/__snapshots__/events-rule-step-function.test.js.snap new file mode 100644 index 000000000..f5b246db5 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/__snapshots__/events-rule-step-function.test.js.snap @@ -0,0 +1,257 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test EventsRuleToStepFunction default params 1`] = ` +Object { + "Resources": Object { + "testeventsrulestepfunctionEventsRuleCC6E98C1": Object { + "Properties": Object { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Ref": "testeventsrulestepfunctionStateMachineBB26627E", + }, + "Id": "Target0", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "testeventsrulestepfunctionEventsRuleRole5AC0B2DC", + "Arn", + ], + }, + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "testeventsrulestepfunctionEventsRuleRole5AC0B2DC": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "events.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "testeventsrulestepfunctionEventsRuleRoleDefaultPolicyA944B4E8": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": Object { + "Ref": "testeventsrulestepfunctionStateMachineBB26627E", + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "testeventsrulestepfunctionEventsRuleRoleDefaultPolicyA944B4E8", + "Roles": Array [ + Object { + "Ref": "testeventsrulestepfunctionEventsRuleRole5AC0B2DC", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "testeventsrulestepfunctionExecutionAbortedAlarmD8769425": Object { + "Properties": Object { + "AlarmDescription": "Alarm for the number of executions that aborted exceeded the threshold of 1. ", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": Array [ + Object { + "Name": "StateMachineArn", + "Value": Object { + "Ref": "testeventsrulestepfunctionStateMachineBB26627E", + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ExecutionsAborted", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "testeventsrulestepfunctionExecutionFailedAlarmEFD0D099": Object { + "Properties": Object { + "AlarmDescription": "Alarm for the number of executions that failed exceeded the threshold of 1. ", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": Array [ + Object { + "Name": "StateMachineArn", + "Value": Object { + "Ref": "testeventsrulestepfunctionStateMachineBB26627E", + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ExecutionsFailed", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "testeventsrulestepfunctionExecutionThrottledAlarm87D39B14": Object { + "Properties": Object { + "AlarmDescription": "Alarm for the number of executions that throttled exceeded the threshold of 1. ", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": Array [ + Object { + "Name": "StateMachineArn", + "Value": Object { + "Ref": "testeventsrulestepfunctionStateMachineBB26627E", + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ExecutionThrottled", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "testeventsrulestepfunctionStateMachineBB26627E": Object { + "DependsOn": Array [ + "testeventsrulestepfunctionStateMachineRoleDefaultPolicy782F9F1D", + "testeventsrulestepfunctionStateMachineRole1488CE0E", + ], + "Properties": Object { + "DefinitionString": "{\\"StartAt\\":\\"StartState\\",\\"States\\":{\\"StartState\\":{\\"Type\\":\\"Pass\\",\\"End\\":true}}}", + "LoggingConfiguration": Object { + "Destinations": Array [ + Object { + "CloudWatchLogsLogGroup": Object { + "LogGroupArn": Object { + "Fn::GetAtt": Array [ + "testeventsrulestepfunctionStateMachineLogGroup3D62D3BF", + "Arn", + ], + }, + }, + }, + ], + "Level": "ERROR", + }, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "testeventsrulestepfunctionStateMachineRole1488CE0E", + "Arn", + ], + }, + }, + "Type": "AWS::StepFunctions::StateMachine", + }, + "testeventsrulestepfunctionStateMachineLogGroup3D62D3BF": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "testeventsrulestepfunctionStateMachineRole1488CE0E": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": Object { + "Fn::Join": Array [ + "", + Array [ + "states.", + Object { + "Ref": "AWS::Region", + }, + ".amazonaws.com", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "testeventsrulestepfunctionStateMachineRoleDefaultPolicy782F9F1D": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "The 'LogDelivery' actions do not support resource-level authorizations", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries", + ], + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": Array [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + "logs:DescribeLogGroups", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "testeventsrulestepfunctionStateMachineRoleDefaultPolicy782F9F1D", + "Roles": Array [ + Object { + "Ref": "testeventsrulestepfunctionStateMachineRole1488CE0E", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/events-rule-step-function.test.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/events-rule-step-function.test.ts new file mode 100644 index 000000000..c494edfb3 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/events-rule-step-function.test.ts @@ -0,0 +1,98 @@ +/** + * 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. + */ + +import { SynthUtils } from '@aws-cdk/assert'; +import * as events from '@aws-cdk/aws-events'; +import { EventsRuleToStepFunction, EventsRuleToStepFunctionProps } from '../lib/index'; +import { Duration } from '@aws-cdk/core'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; + +function deployNewStateMachine(stack: cdk.Stack) { + + const startState = new sfn.Pass(stack, 'StartState'); + + const props: EventsRuleToStepFunctionProps = { + stateMachineProps: { + definition: startState + }, + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + } + }; + + return new EventsRuleToStepFunction(stack, 'test-events-rule-step-function', props); +} + +test('snapshot test EventsRuleToStepFunction default params', () => { + const stack = new cdk.Stack(); + deployNewStateMachine(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check events rule role policy permissions', () => { + const stack = new cdk.Stack(); + + deployNewStateMachine(stack); + + expect(stack).toHaveResource("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: "states:StartExecution", + Effect: "Allow", + Resource: { + Ref: "testeventsrulestepfunctionStateMachineBB26627E" + } + } + ], + Version: "2012-10-17" + } + }); +}); + +test('check events rule properties', () => { + const stack = new cdk.Stack(); + + deployNewStateMachine(stack); + + expect(stack).toHaveResource('AWS::Events::Rule', { + ScheduleExpression: "rate(5 minutes)", + State: "ENABLED", + Targets: [ + { + Arn: { + Ref: "testeventsrulestepfunctionStateMachineBB26627E" + }, + Id: "Target0", + RoleArn: { + "Fn::GetAtt": [ + "testeventsrulestepfunctionEventsRuleRole5AC0B2DC", + "Arn" + ] + } + } + ] + }); +}); + +test('check properties', () => { + const stack = new cdk.Stack(); + + const construct: EventsRuleToStepFunction = deployNewStateMachine(stack); + + expect(construct.cloudwatchAlarms !== null); + expect(construct.stateMachine !== null); + expect(construct.eventsRule !== null); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-no-argument.expected.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-no-argument.expected.json new file mode 100644 index 000000000..5708e457d --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-no-argument.expected.json @@ -0,0 +1,253 @@ +{ + "Resources": { + "testeventsrulestepfunctionstackStateMachineLogGroupC3B398D4": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testeventsrulestepfunctionstackStateMachineRoleA5C98F35": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testeventsrulestepfunctionstackStateMachineRoleDefaultPolicyC51897AF": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + "logs:DescribeLogGroups" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testeventsrulestepfunctionstackStateMachineRoleDefaultPolicyC51897AF", + "Roles": [ + { + "Ref": "testeventsrulestepfunctionstackStateMachineRoleA5C98F35" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "The 'LogDelivery' actions do not support resource-level authorizations" + } + ] + } + } + }, + "testeventsrulestepfunctionstackStateMachine48534048": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": "{\"StartAt\":\"StartState\",\"States\":{\"StartState\":{\"Type\":\"Pass\",\"End\":true}}}", + "RoleArn": { + "Fn::GetAtt": [ + "testeventsrulestepfunctionstackStateMachineRoleA5C98F35", + "Arn" + ] + }, + "LoggingConfiguration": { + "Destinations": [ + { + "CloudWatchLogsLogGroup": { + "LogGroupArn": { + "Fn::GetAtt": [ + "testeventsrulestepfunctionstackStateMachineLogGroupC3B398D4", + "Arn" + ] + } + } + } + ], + "Level": "ERROR" + } + }, + "DependsOn": [ + "testeventsrulestepfunctionstackStateMachineRoleDefaultPolicyC51897AF", + "testeventsrulestepfunctionstackStateMachineRoleA5C98F35" + ] + }, + "testeventsrulestepfunctionstackEventsRuleRole6AD4C16A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testeventsrulestepfunctionstackEventsRuleRoleDefaultPolicy9F3CC359": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "testeventsrulestepfunctionstackStateMachine48534048" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testeventsrulestepfunctionstackEventsRuleRoleDefaultPolicy9F3CC359", + "Roles": [ + { + "Ref": "testeventsrulestepfunctionstackEventsRuleRole6AD4C16A" + } + ] + } + }, + "testeventsrulestepfunctionstackEventsRuleF510C733": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Ref": "testeventsrulestepfunctionstackStateMachine48534048" + }, + "Id": "Target0", + "RoleArn": { + "Fn::GetAtt": [ + "testeventsrulestepfunctionstackEventsRuleRole6AD4C16A", + "Arn" + ] + } + } + ] + } + }, + "testeventsrulestepfunctionstackExecutionFailedAlarm865F1B9B": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that failed exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "testeventsrulestepfunctionstackStateMachine48534048" + } + } + ], + "MetricName": "ExecutionsFailed", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1 + } + }, + "testeventsrulestepfunctionstackExecutionThrottledAlarm25CE7A69": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that throttled exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "testeventsrulestepfunctionstackStateMachine48534048" + } + } + ], + "MetricName": "ExecutionThrottled", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1 + } + }, + "testeventsrulestepfunctionstackExecutionAbortedAlarmADD2893F": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that aborted exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "testeventsrulestepfunctionstackStateMachine48534048" + } + } + ], + "MetricName": "ExecutionsAborted", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-no-argument.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-no-argument.ts new file mode 100644 index 000000000..a18709e40 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-no-argument.ts @@ -0,0 +1,36 @@ +/** + * 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 { EventsRuleToStepFunction, EventsRuleToStepFunctionProps } from "../lib"; +import { Duration } from '@aws-cdk/core'; +import * as stepfunctions from '@aws-cdk/aws-stepfunctions'; +import * as events from '@aws-cdk/aws-events'; + +const app = new App(); +const stack = new Stack(app, 'test-events-rule-step-function-stack'); + +const startState = new stepfunctions.Pass(stack, 'StartState'); + +const props: EventsRuleToStepFunctionProps = { + stateMachineProps: { + definition: startState + }, + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + } +}; + +new EventsRuleToStepFunction(stack, 'test-events-rule-step-function-stack', props); +app.synth(); diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-with-lambda.expected.json b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-with-lambda.expected.json new file mode 100644 index 000000000..dc5c18bde --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-with-lambda.expected.json @@ -0,0 +1,412 @@ +{ + "Resources": { + "LambdaFunctionServiceRole0C4CDE0B": { + "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" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRole0C4CDE0B" + ], + "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." + } + ] + } + } + }, + "testeventsrulestepfunctionandlambdastackStateMachineLogGroup5FA8F5A3": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testeventsrulestepfunctionandlambdastackStateMachineRole77040795": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testeventsrulestepfunctionandlambdastackStateMachineRoleDefaultPolicy7FB04121": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + } + }, + { + "Action": [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + "logs:DescribeLogGroups" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testeventsrulestepfunctionandlambdastackStateMachineRoleDefaultPolicy7FB04121", + "Roles": [ + { + "Ref": "testeventsrulestepfunctionandlambdastackStateMachineRole77040795" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "The 'LogDelivery' actions do not support resource-level authorizations" + } + ] + } + } + }, + "testeventsrulestepfunctionandlambdastackStateMachine3BC6D432": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"StartState\",\"States\":{\"StartState\":{\"Type\":\"Pass\",\"Next\":\"LambdaTask\"},\"LambdaTask\":{\"End\":true,\"Parameters\":{\"FunctionName\":\"", + { + "Ref": "LambdaFunctionBF21E41F" + }, + "\",\"Payload.$\":\"$\"},\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::lambda:invoke\"}},\"TimeoutSeconds\":300}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "testeventsrulestepfunctionandlambdastackStateMachineRole77040795", + "Arn" + ] + }, + "LoggingConfiguration": { + "Destinations": [ + { + "CloudWatchLogsLogGroup": { + "LogGroupArn": { + "Fn::GetAtt": [ + "testeventsrulestepfunctionandlambdastackStateMachineLogGroup5FA8F5A3", + "Arn" + ] + } + } + } + ], + "Level": "ERROR" + } + }, + "DependsOn": [ + "testeventsrulestepfunctionandlambdastackStateMachineRoleDefaultPolicy7FB04121", + "testeventsrulestepfunctionandlambdastackStateMachineRole77040795" + ] + }, + "testeventsrulestepfunctionandlambdastackEventsRuleRole1FC528B4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testeventsrulestepfunctionandlambdastackEventsRuleRoleDefaultPolicyCA432EB7": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "testeventsrulestepfunctionandlambdastackStateMachine3BC6D432" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testeventsrulestepfunctionandlambdastackEventsRuleRoleDefaultPolicyCA432EB7", + "Roles": [ + { + "Ref": "testeventsrulestepfunctionandlambdastackEventsRuleRole1FC528B4" + } + ] + } + }, + "testeventsrulestepfunctionandlambdastackEventsRule5C68D98F": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Ref": "testeventsrulestepfunctionandlambdastackStateMachine3BC6D432" + }, + "Id": "Target0", + "RoleArn": { + "Fn::GetAtt": [ + "testeventsrulestepfunctionandlambdastackEventsRuleRole1FC528B4", + "Arn" + ] + } + } + ] + } + }, + "testeventsrulestepfunctionandlambdastackExecutionFailedAlarm1E10C548": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that failed exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "testeventsrulestepfunctionandlambdastackStateMachine3BC6D432" + } + } + ], + "MetricName": "ExecutionsFailed", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1 + } + }, + "testeventsrulestepfunctionandlambdastackExecutionThrottledAlarm19B70D6A": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that throttled exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "testeventsrulestepfunctionandlambdastackStateMachine3BC6D432" + } + } + ], + "MetricName": "ExecutionThrottled", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1 + } + }, + "testeventsrulestepfunctionandlambdastackExecutionAbortedAlarm8EC0918C": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that aborted exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "testeventsrulestepfunctionandlambdastackStateMachine3BC6D432" + } + } + ], + "MetricName": "ExecutionsAborted", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + } + }, + "Parameters": { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": { + "Type": "String", + "Description": "S3 bucket for asset \"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\"" + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": { + "Type": "String", + "Description": "S3 key for asset version \"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\"" + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": { + "Type": "String", + "Description": "Artifact hash for asset \"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-with-lambda.ts b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-with-lambda.ts new file mode 100644 index 000000000..ce2b44af4 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/integ.events-rule-step-function-with-lambda.ts @@ -0,0 +1,50 @@ +/** + * 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 { EventsRuleToStepFunction, EventsRuleToStepFunctionProps } from "../lib"; +import { Duration } from '@aws-cdk/core'; +import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; +import * as events from '@aws-cdk/aws-events'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { deployLambdaFunction } from '@aws-solutions-constructs/core'; +import * as stepfunctions from '@aws-cdk/aws-stepfunctions'; + +const app = new App(); +const stack = new Stack(app, 'test-events-rule-step-function-and-lambda-stack'); + +const submitLambda = deployLambdaFunction(stack, { + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.asset(`${__dirname}/lambda`), + handler: 'index.handler' +}); + +const submitJob = new tasks.RunLambdaTask(submitLambda); +const startState = new stepfunctions.Pass(stack, 'StartState'); +startState.next(new stepfunctions.Task(stack, 'LambdaTask', { + task: submitJob +})); + +const props: EventsRuleToStepFunctionProps = { + stateMachineProps: { + definition: startState, + timeout: Duration.minutes(5) + }, + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + } +}; + +new EventsRuleToStepFunction(stack, 'test-events-rule-step-function-and-lambda-stack', props); +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-events-rule-step-function/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.gitignore b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.npmignore b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/README.md b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/README.md similarity index 58% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/README.md rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/README.md index 7721dcd4e..f2ff14a3a 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-iot-kinesisfirehose-s3/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_iot_kinesisfirehose_s3`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_iot_kinesisfirehose_s3`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-iot-kinesisfirehose-s3`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.iotkinesisfirehoses3`| -This AWS Solutions Konstruk implements an AWS IoT MQTT topic rule to send data to an Amazon Kinesis Data Firehose delivery stream connected to an Amazon S3 bucket. +This AWS Solutions Construct implements an AWS IoT MQTT topic rule to send data to an Amazon Kinesis Data Firehose delivery stream connected to an Amazon S3 bucket. Here is a minimal deployable pattern definition: ``` javascript -const { IotToKinesisFirehoseToS3Props, IotToKinesisFirehoseToS3 } = require('@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3'); +const { IotToKinesisFirehoseToS3Props, IotToKinesisFirehoseToS3 } = require('@aws-solutions-constructs/aws-iot-kinesisfirehose-s3'); const props: IotToKinesisFirehoseToS3Props = { iotTopicRuleProps: { @@ -71,9 +70,29 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|kinesisFirehose()|[`kinesisfirehose.CfnDeliveryStream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStream.html)|Returns an instance of kinesisfirehose.CfnDeliveryStream created by the construct| -|bucket()|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct| -|iotTopicRule()|[`iot.CfnTopicRule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRule.html)|Returns an instance of iot.CfnTopicRule created by the construct| +|kinesisFirehose|[`kinesisfirehose.CfnDeliveryStream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStream.html)|Returns an instance of kinesisfirehose.CfnDeliveryStream created by the construct| +|s3Bucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct| +|iotTopicRule|[`iot.CfnTopicRule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRule.html)|Returns an instance of iot.CfnTopicRule created by the construct| +|iotActionsRole|[`iam.Role`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html)|Returns an instance of the iam.Role created by the construct for IoT Rule| +|kinesisFirehoseRole|[`iam.Role`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html)|Returns an instance of the iam.Role created by the construct for Kinesis Data Firehose delivery stream| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon IoT Rule +* Configure least privilege access IAM role for Amazon IoT + +### Amazon Kinesis Firehose +* Enable CloudWatch logging for Kinesis Firehose +* Configure least privilege access IAM role for Amazon Kinesis Firehose + +### Amazon S3 Bucket +* Configure Access logging for S3 Bucket +* Enable server-side encryption for S3 Bucket using AWS managed KMS Key +* Turn on the versioning for S3 Bucket +* Don't allow public access for S3 Bucket +* Retain the S3 Bucket when deleting the CloudFormation stack ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/architecture.png b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/lib/index.ts similarity index 66% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/lib/index.ts index 58ff91db5..784565546 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/lib/index.ts @@ -16,9 +16,9 @@ import * as iot from '@aws-cdk/aws-iot'; import * as s3 from '@aws-cdk/aws-s3'; import * as iam from '@aws-cdk/aws-iam'; import { Construct } from '@aws-cdk/core'; -import * as defaults from '@aws-solutions-konstruk/core'; -import { overrideProps } from '@aws-solutions-konstruk/core'; -import { KinesisFirehoseToS3 } from '@aws-solutions-konstruk/aws-kinesisfirehose-s3'; +import * as defaults from '@aws-solutions-constructs/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; +import { KinesisFirehoseToS3 } from '@aws-solutions-constructs/aws-kinesisfirehose-s3'; /** * @summary The properties for the IotToKinesisFirehoseToS3 Construct @@ -60,9 +60,11 @@ export interface IotToKinesisFirehoseToS3Props { } export class IotToKinesisFirehoseToS3 extends Construct { - private topic: iot.CfnTopicRule; - private firehose: kinesisfirehose.CfnDeliveryStream; - private s3Bucket: s3.Bucket; + public readonly iotTopicRule: iot.CfnTopicRule; + public readonly kinesisFirehose: kinesisfirehose.CfnDeliveryStream; + public readonly s3Bucket: s3.Bucket; + public readonly iotActionsRole: iam.Role; + public readonly kinesisFirehoseRole: iam.Role; /** * @summary Constructs a new instance of the IotToKinesisFirehoseToS3 class. @@ -81,11 +83,11 @@ export class IotToKinesisFirehoseToS3 extends Construct { existingBucketObj: props.existingBucketObj, bucketProps: props.bucketProps }); - this.firehose = firehoseToS3.kinesisFirehose(); - this.s3Bucket = firehoseToS3.bucket(); + this.kinesisFirehose = firehoseToS3.kinesisFirehose; + this.s3Bucket = firehoseToS3.s3Bucket; // Setup the IAM Role for IoT Actions - const iotActionsRole = new iam.Role(this, 'IotActionsRole', { + this.iotActionsRole = new iam.Role(this, 'IotActionsRole', { assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'), }); @@ -95,52 +97,24 @@ export class IotToKinesisFirehoseToS3 extends Construct { actions: [ 'firehose:PutRecord' ], - resources: [this.firehose.attrArn] + resources: [this.kinesisFirehose.attrArn] }) ]}); // Attach policy to role - iotActionsPolicy.attachToRole(iotActionsRole); + iotActionsPolicy.attachToRole(this.iotActionsRole); const defaultIotTopicProps = defaults.DefaultCfnTopicRuleProps([{ firehose: { - deliveryStreamName: this.firehose.ref, - roleArn: iotActionsRole.roleArn + deliveryStreamName: this.kinesisFirehose.ref, + roleArn: this.iotActionsRole.roleArn } }]); const iotTopicProps = overrideProps(defaultIotTopicProps, props.iotTopicRuleProps, true); // Create the IoT topic rule - this.topic = new iot.CfnTopicRule(this, 'IotTopic', iotTopicProps); - } + this.iotTopicRule = new iot.CfnTopicRule(this, 'IotTopic', iotTopicProps); - /** - * @summary Returns an instance of kinesisfirehose.CfnDeliveryStream created by the construct. - * @returns {kinesisfirehose.CfnDeliveryStream} Instance of CfnDeliveryStream created by the construct - * @since 0.8.0 - * @access public - */ - public kinesisFirehose(): kinesisfirehose.CfnDeliveryStream { - return this.firehose as kinesisfirehose.CfnDeliveryStream; - } - - /** - * @summary Returns an instance of s3.Bucket created by the construct. - * @returns {s3.Bucket} Instance of s3.Bucket created by the construct - * @since 0.8.0 - * @access public - */ - public bucket(): s3.Bucket { - return this.s3Bucket; - } - - /** - * @summary Returns an instance of iot.CfnTopicRule created by the construct. - * @returns {iot.CfnTopicRule} Instance of CfnTopicRule created by the construct - * @since 0.8.0 - * @access public - */ - public iotTopicRule(): iot.CfnTopicRule { - return this.topic; + this.kinesisFirehoseRole = firehoseToS3.kinesisFirehoseRole; } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/package.json b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/package.json similarity index 53% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/package.json rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/package.json index 37b75e72f..675d7137a 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-iot-kinesisfirehose-s3", + "version": "1.46.0", "description": "CDK Constructs for AWS IoT to AWS Kinesis Firehose to AWS S3 integration.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3" }, "author": { "name": "Amazon Web Services", @@ -33,36 +33,36 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.iotkinesisfirehoses3", + "package": "software.amazon.awsconstructs.services.iotkinesisfirehoses3", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "iotkinesisfirehoses3" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.IotKinesisfirehoseS3", - "packageId": "Amazon.Konstruk.AWS.IotKinesisfirehoseS3", + "namespace": "Amazon.Constructs.AWS.IotKinesisfirehoseS3", + "packageId": "Amazon.Constructs.AWS.IotKinesisfirehoseS3", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-iot-kinesisfirehose-s3", - "module": "aws_solutions_konstruk.aws_iot_kinesisfirehose_s3" + "distName": "aws-solutions-constructs.aws-iot-kinesisfirehose-s3", + "module": "aws_solutions_constructs.aws_iot_kinesisfirehose_s3" } } }, "dependencies": { - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kinesisfirehose": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-iot": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-solutions-konstruk/aws-kinesisfirehose-s3": "~0.8.1", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kinesisfirehose": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-iot": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-kinesisfirehose-s3": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -72,13 +72,13 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kinesisfirehose": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-iot": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-solutions-konstruk/aws-kinesisfirehose-s3": "~0.8.1", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kinesisfirehose": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-iot": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-kinesisfirehose-s3": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/__snapshots__/test.iot-kinesisfirehose-s3.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/__snapshots__/test.iot-kinesisfirehose-s3.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/__snapshots__/test.iot-kinesisfirehose-s3.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/__snapshots__/test.iot-kinesisfirehose-s3.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/test.iot-kinesisfirehose-s3.test.ts b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/test.iot-kinesisfirehose-s3.test.ts similarity index 93% rename from source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/test.iot-kinesisfirehose-s3.test.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/test.iot-kinesisfirehose-s3.test.ts index a78b694fb..1c2380a79 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/test.iot-kinesisfirehose-s3.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-iot-kinesisfirehose-s3/test/test.iot-kinesisfirehose-s3.test.ts @@ -14,7 +14,6 @@ import { SynthUtils } from '@aws-cdk/assert'; import { IotToKinesisFirehoseToS3, IotToKinesisFirehoseToS3Props } from "../lib"; import * as cdk from "@aws-cdk/core"; -import * as iot from '@aws-cdk/aws-iot'; import '@aws-cdk/assert/jest'; function deploy(stack: cdk.Stack) { @@ -115,12 +114,14 @@ test('check firehose and s3 overrides', () => { } }}); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: IotToKinesisFirehoseToS3 = deploy(stack); - expect(construct.iotTopicRule()).toBeInstanceOf(iot.CfnTopicRule); - expect(construct.kinesisFirehose()).toBeDefined(); - expect(construct.bucket()).toBeDefined(); + expect(construct.iotTopicRule !== null); + expect(construct.kinesisFirehose !== null); + expect(construct.s3Bucket !== null); + expect(construct.iotActionsRole !== null); + expect(construct.kinesisFirehoseRole !== null); }); diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/README.md b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/README.md similarity index 63% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/README.md rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/README.md index 41357451e..2697ef9a4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-iot-lambda-dynamodb/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_iot_lambda_dynamodb`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-iot-lambda-dynamodb`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_iot_lambda_dynamodb`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-iot-lambda-dynamodb`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.iotlambdadynamodb`| -This AWS Solutions Konstruk implements an AWS IoT topic rule, an AWS Lambda function and Amazon DynamoDB table with the least privileged permissions. +This AWS Solutions Construct implements an AWS IoT topic rule, an AWS Lambda function and Amazon DynamoDB table with the least privileged permissions. Here is a minimal deployable pattern definition: ``` javascript -const { IotToLambdaToDynamoDBProps, IotToLambdaToDynamoDB } = require('@aws-solutions-konstruk/aws-iot-lambda-dynamodb'); +const { IotToLambdaToDynamoDBProps, IotToLambdaToDynamoDB } = require('@aws-solutions-constructs/aws-iot-lambda-dynamodb'); const props: IotToLambdaToDynamoDBProps = { deployLambda: true, @@ -77,9 +76,26 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|iotTopicRule()|[`iot.CfnTopicRule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRule.html)|Returns an instance of iot.CfnTopicRule created by the construct| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| -|dynamoTable()|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct| +|iotTopicRule|[`iot.CfnTopicRule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRule.html)|Returns an instance of iot.CfnTopicRule created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| +|dynamoTable|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon IoT Rule +* Configure least privilege access IAM role for Amazon IoT + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function + +### Amazon DynamoDB Table +* Set the billing mode for DynamoDB Table to On-Demand (Pay per request) +* Enable server-side encryption for DynamoDB Table using AWS managed KMS Key +* Creates a partition key called 'id' for DynamoDB Table +* Retain the Table when deleting the CloudFormation stack ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/architecture.png b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/lib/index.ts similarity index 68% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/lib/index.ts index 6bc12e73e..92f3fc70a 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/lib/index.ts @@ -14,8 +14,8 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as iot from '@aws-cdk/aws-iot'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; -import { IotToLambda } from '@aws-solutions-konstruk/aws-iot-lambda'; -import { LambdaToDynamoDB } from '@aws-solutions-konstruk/aws-lambda-dynamodb'; +import { IotToLambda } from '@aws-solutions-constructs/aws-iot-lambda'; +import { LambdaToDynamoDB } from '@aws-solutions-constructs/aws-lambda-dynamodb'; import { Construct } from '@aws-cdk/core'; /** @@ -58,9 +58,9 @@ export interface IotToLambdaToDynamoDBProps { } export class IotToLambdaToDynamoDB extends Construct { - private topic: iot.CfnTopicRule; - private fn: lambda.Function; - private table: dynamodb.Table; + public readonly iotTopicRule: iot.CfnTopicRule; + public readonly lambdaFunction: lambda.Function; + public readonly dynamoTable: dynamodb.Table; /** * @summary Constructs a new instance of the IotToLambdaToDynamoDB class. @@ -75,45 +75,15 @@ export class IotToLambdaToDynamoDB extends Construct { // Setup the IotToLambda const iotToLambda = new IotToLambda(this, 'IotToLambda', props); - this.topic = iotToLambda.iotTopicRule(); - this.fn = iotToLambda.lambdaFunction(); + this.iotTopicRule = iotToLambda.iotTopicRule; + this.lambdaFunction = iotToLambda.lambdaFunction; // Setup the LambdaToDynamoDB const lambdaToDynamoDB = new LambdaToDynamoDB(this, 'LambdaToDynamoDB', { deployLambda: false, - existingLambdaObj: this.fn, + existingLambdaObj: this.lambdaFunction, dynamoTableProps: props.dynamoTableProps }); - this.table = lambdaToDynamoDB.dynamoTable(); - } - - /** - * @summary Returns an instance of iot.CfnTopicRule created by the construct. - * @returns {iot.CfnTopicRule} Instance of CfnTopicRule created by the construct - * @since 0.8.0 - * @access public - */ - public iotTopicRule(): iot.CfnTopicRule { - return this.topic; - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of lambda.Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of dynamodb.Table created by the construct. - * @returns {dynamodb.Table} Instance of dynamodb.Table created by the construct - * @since 0.8.0 - * @access public - */ - public dynamoTable(): dynamodb.Table { - return this.table; + this.dynamoTable = lambdaToDynamoDB.dynamoTable; } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/package.json b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/package.json similarity index 54% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/package.json rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/package.json index 53a7e299f..be864dd3c 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-iot-lambda-dynamodb", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-iot-lambda-dynamodb", + "version": "1.46.0", "description": "CDK Constructs for AWS IoT to AWS Lambda to AWS DyanmoDB integration.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb" }, "author": { "name": "Amazon Web Services", @@ -34,36 +34,36 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.iotlambdadynamodb", + "package": "software.amazon.awsconstructs.services.iotlambdadynamodb", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "iotlambdadynamodb" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.IotLambdaDynamodb", - "packageId": "Amazon.Konstruk.AWS.IotLambdaDynamodb", + "namespace": "Amazon.Constructs.AWS.IotLambdaDynamodb", + "packageId": "Amazon.Constructs.AWS.IotLambdaDynamodb", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-iot-lambda-dynamodb", - "module": "aws_solutions_konstruk.aws_iot_lambda_dynamodb" + "distName": "aws-solutions-constructs.aws-iot-lambda-dynamodb", + "module": "aws_solutions_constructs.aws_iot_lambda_dynamodb" } } }, "dependencies": { - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-iot": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-solutions-konstruk/aws-iot-lambda": "~0.8.1", - "@aws-solutions-konstruk/aws-lambda-dynamodb": "~0.8.1", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-iot": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-iot-lambda": "~1.46.0", + "@aws-solutions-constructs/aws-lambda-dynamodb": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -73,13 +73,13 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-iot": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-solutions-konstruk/aws-iot-lambda": "~0.8.1", - "@aws-solutions-konstruk/aws-lambda-dynamodb": "~0.8.1", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-iot": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-iot-lambda": "~1.46.0", + "@aws-solutions-constructs/aws-lambda-dynamodb": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/__snapshots__/iot-lambda-dynamodb.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/__snapshots__/iot-lambda-dynamodb.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/__snapshots__/iot-lambda-dynamodb.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/__snapshots__/iot-lambda-dynamodb.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.expected.json b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.expected.json rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.ts b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/iot-lambda-dynamodb.test.ts b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/iot-lambda-dynamodb.test.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/iot-lambda-dynamodb.test.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/iot-lambda-dynamodb.test.ts index d2b4fe3f5..2ebf1473e 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/iot-lambda-dynamodb.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/iot-lambda-dynamodb.test.ts @@ -240,14 +240,14 @@ test('check lambda function policy ', () => { }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: IotToLambdaToDynamoDB = deployStack(stack); - expect(construct.lambdaFunction()).toBeDefined(); - expect(construct.dynamoTable()).toBeDefined(); - expect(construct.iotTopicRule()).toBeDefined(); + expect(construct.lambdaFunction !== null); + expect(construct.dynamoTable !== null); + expect(construct.iotTopicRule !== null); }); test('check exception for Missing existingObj from props for deploy = false', () => { diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda-dynamodb/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.gitignore b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.npmignore b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/README.md similarity index 69% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/README.md index 01b7b51e4..bb68f1712 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-iot-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_iot_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-iot-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_iot_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-iot-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.iotlambda`| -This AWS Solutions Konstruk implements an AWS IoT MQTT topic rule and an AWS Lambda function pattern. +This AWS Solutions Construct implements an AWS IoT MQTT topic rule and an AWS Lambda function pattern. Here is a minimal deployable pattern definition: ``` javascript -const { IotToLambdaProps, IotToLambda } = require('@aws-solutions-konstruk/aws-iot-lambda'); +const { IotToLambdaProps, IotToLambda } = require('@aws-solutions-constructs/aws-iot-lambda'); const props: IotToLambdaProps = { deployLambda: true, @@ -75,8 +74,19 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|iotTopicRule()|[`iot.CfnTopicRule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRule.html)|Returns an instance of iot.CfnTopicRule created by the construct| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| +|iotTopicRule|[`iot.CfnTopicRule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRule.html)|Returns an instance of iot.CfnTopicRule created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon IoT Rule +* Configure least privilege access IAM role for Amazon IoT + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/lib/index.ts similarity index 72% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/lib/index.ts index 3b809426e..cf3d61dc0 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/lib/index.ts @@ -15,8 +15,8 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as iot from '@aws-cdk/aws-iot'; import * as iam from '@aws-cdk/aws-iam'; import { Construct } from '@aws-cdk/core'; -import * as defaults from '@aws-solutions-konstruk/core'; -import { overrideProps } from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; /** * @summary The properties for the IotToLambda class. @@ -52,8 +52,8 @@ export interface IotToLambdaProps { } export class IotToLambda extends Construct { - private fn: lambda.Function; - private topic: iot.CfnTopicRule; + public readonly lambdaFunction: lambda.Function; + public readonly iotTopicRule: iot.CfnTopicRule; /** * @summary Constructs a new instance of the IotToLambda class. @@ -66,7 +66,7 @@ export class IotToLambda extends Construct { constructor(scope: Construct, id: string, props: IotToLambdaProps) { super(scope, id); - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps @@ -74,38 +74,17 @@ export class IotToLambda extends Construct { const defaultIotTopicProps = defaults.DefaultCfnTopicRuleProps([{ lambda: { - functionArn: this.fn.functionArn + functionArn: this.lambdaFunction.functionArn } }]); const iotTopicProps = overrideProps(defaultIotTopicProps, props.iotTopicRuleProps, true); // Create the IoT topic rule - this.topic = new iot.CfnTopicRule(this, 'IotTopic', iotTopicProps); + this.iotTopicRule = new iot.CfnTopicRule(this, 'IotTopic', iotTopicProps); - this.fn.addPermission("LambdaInvokePermission", { + this.lambdaFunction.addPermission("LambdaInvokePermission", { principal: new iam.ServicePrincipal('iot.amazonaws.com'), - sourceArn: this.topic.attrArn + sourceArn: this.iotTopicRule.attrArn }); } - - /** - * @summary Returns an instance of iot.CfnTopicRule created by the construct. - * @returns {iot.CfnTopicRule} Instance of CfnTopicRule created by the construct - * @since 0.8.0 - * @access public - */ - public iotTopicRule(): iot.CfnTopicRule { - return this.topic; - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of lambda.Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - } diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/package.json similarity index 60% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/package.json index 6a96d10ae..1a9a300cd 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-iot-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-iot-lambda", + "version": "1.46.0", "description": "CDK Constructs for AWS IoT to AWS Lambda integration", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-iot-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-iot-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,34 +34,34 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.iotlambda", + "package": "software.amazon.awsconstructs.services.iotlambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "iotlambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.IotLambda", - "packageId": "Amazon.Konstruk.AWS.IotLambda", + "namespace": "Amazon.Constructs.AWS.IotLambda", + "packageId": "Amazon.Constructs.AWS.IotLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-iot-lambda", - "module": "aws_solutions_konstruk.aws_iot_lambda" + "distName": "aws-solutions-constructs.aws-iot-lambda", + "module": "aws_solutions_constructs.aws_iot_lambda" } } }, "dependencies": { - "@aws-cdk/aws-iot": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-iot": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,11 +71,11 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-iot": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-cdk/aws-iam": "~1.40.0", + "@aws-cdk/aws-iot": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.ts b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-new-func.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-new-func.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.expected.json b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.expected.json rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts index c6f74df93..609b8f7ce 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts @@ -15,7 +15,7 @@ import { App, Stack } from "@aws-cdk/core"; import { IotToLambda, IotToLambdaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; // Setup const app = new App(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/iot-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/iot-lambda.test.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/iot-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/iot-lambda.test.ts index b133edfae..6c7d9b4a2 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/iot-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/iot-lambda.test.ts @@ -14,7 +14,6 @@ import { SynthUtils } from '@aws-cdk/assert'; import { IotToLambda, IotToLambdaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as iot from '@aws-cdk/aws-iot'; import * as cdk from "@aws-cdk/core"; import '@aws-cdk/assert/jest'; @@ -265,13 +264,13 @@ test('check iot lambda function role for deploy: false', () => { }); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: IotToLambda = deployNewFunc(stack); - expect(construct.iotTopicRule()).toBeInstanceOf(iot.CfnTopicRule); - expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); + expect(construct.iotTopicRule !== null); + expect(construct.lambdaFunction !== null); }); test('check exception for Missing existingObj from props for deploy = false', () => { diff --git a/source/patterns/@aws-solutions-konstruk/core/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-iot-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.gitignore b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.npmignore b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md similarity index 66% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md index c5ab3a024..cedbf9544 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,23 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-kinesisfirehose-s3-and-kinesisanalytics/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_kinesisfirehose_s3_and_kinesisanalytics`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_kinesisfirehose_s3_and_kinesisanalytics`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.kinesisfirehoses3kinesisanalytics`| -This AWS Solutions Konstruk implements an Amazon Kinesis Firehose delivery stream connected to: -1. An Amazon S3 bucket, and -1. An Amazon Kinesis Analytics application. +This AWS Solutions Construct implements an Amazon Kinesis Firehose delivery stream connected to an Amazon S3 bucket, and an Amazon Kinesis Analytics application. Here is a minimal deployable pattern definition: ``` javascript -const { KinesisFirehoseToAnalyticsAndS3 } = require('@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics'); +const { KinesisFirehoseToAnalyticsAndS3 } = require('@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics'); new KinesisFirehoseToAnalyticsAndS3(stack, 'FirehoseToS3AndAnalyticsPattern', { kinesisAnalyticsProps: { @@ -91,9 +88,27 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|kinesisAnalytics()|[`kinesisAnalytics.CfnApplication`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisanalytics.CfnApplication.html)|Returns an instance of the Kinesis Analytics application created by the pattern.| -|kinesisFirehose()|[`kinesisFirehose.CfnDeliveryStream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStream.html)|Returns an instance of the Kinesis Firehose delivery stream created by the pattern.| -|bucket()|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of the S3 bucket created by the pattern.| +|kinesisAnalytics|[`kinesisAnalytics.CfnApplication`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisanalytics.CfnApplication.html)|Returns an instance of the Kinesis Analytics application created by the pattern.| +|kinesisFirehose|[`kinesisFirehose.CfnDeliveryStream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStream.html)|Returns an instance of the Kinesis Firehose delivery stream created by the pattern.| +|s3Bucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of the S3 bucket created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon Kinesis Firehose +* Enable CloudWatch logging for Kinesis Firehose +* Configure least privilege access IAM role for Amazon Kinesis Firehose + +### Amazon S3 Bucket +* Configure Access logging for S3 Bucket +* Enable server-side encryption for S3 Bucket using AWS managed KMS Key +* Turn on the versioning for S3 Bucket +* Don't allow public access for S3 Bucket +* Retain the S3 Bucket when deleting the CloudFormation stack + +### Amazon Kinesis Data Analytics +* Configure least privilege access IAM role for Amazon Kinesis Analytics ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/architecture.png b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts similarity index 68% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts index f89cbd4c8..352a7836b 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts @@ -14,9 +14,9 @@ // Imports import * as kinesisFirehose from '@aws-cdk/aws-kinesisfirehose'; import * as kinesisAnalytics from '@aws-cdk/aws-kinesisanalytics'; -import { KinesisFirehoseToS3, KinesisFirehoseToS3Props } from '@aws-solutions-konstruk/aws-kinesisfirehose-s3'; +import { KinesisFirehoseToS3, KinesisFirehoseToS3Props } from '@aws-solutions-constructs/aws-kinesisfirehose-s3'; import * as s3 from '@aws-cdk/aws-s3'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; /** @@ -62,10 +62,9 @@ export interface KinesisFirehoseToAnalyticsAndS3Props { * @summary The KinesisFirehoseToAnalyticsAndS3 class. */ export class KinesisFirehoseToAnalyticsAndS3 extends Construct { - - // Declarations - private analytics: kinesisAnalytics.CfnApplication; - private kfs: KinesisFirehoseToS3; + public readonly kinesisAnalytics: kinesisAnalytics.CfnApplication; + public readonly kinesisFirehose: kinesisFirehose.CfnDeliveryStream; + public readonly s3Bucket: s3.Bucket; /** * @summary Constructs a new instance of the KinesisFirehoseToAnalyticsAndS3 class. @@ -87,42 +86,15 @@ export class KinesisFirehoseToAnalyticsAndS3 extends Construct { }; // Add the kinesisfirehose-s3 pattern - this.kfs = new KinesisFirehoseToS3(this, 'KinesisFirehoseToS3', kinesisFirehoseToS3Props); + const kfs = new KinesisFirehoseToS3(this, 'KinesisFirehoseToS3', kinesisFirehoseToS3Props); // Add the Kinesis Analytics application - this.analytics = defaults.buildKinesisAnalyticsApp(this, { - kinesisFirehose: this.kfs.kinesisFirehose(), + this.kinesisAnalytics = defaults.buildKinesisAnalyticsApp(this, { + kinesisFirehose: kfs.kinesisFirehose, kinesisAnalyticsProps: props.kinesisAnalyticsProps }); - } - - /** - * @summary Returns an instance of the kinesisAnalytics.CfnApplication created by the construct. - * @returns {kinesisAnalytics.CfnApplication} Instance of the CfnApplication created by the construct. - * @since 0.8.0 - * @access public - */ - public kinesisAnalytics(): kinesisAnalytics.CfnApplication { - return this.analytics; - } - - /** - * @summary Returns an instance of the kinesisFirehose.CfnDeliveryStream created by the construct. - * @returns {kinesisFirehose.CfnDeliveryStream} Instance of the CfnDeliveryStream created by the construct. - * @since 0.8.0 - * @access public - */ - public kinesisFirehose(): kinesisFirehose.CfnDeliveryStream { - return this.kfs.kinesisFirehose(); - } - /** - * @summary Returns an instance of the s3.Bucket created by the construct. - * @returns {s3.Bucket} Instance of the Bucket created by the construct. - * @since 0.8.0 - * @access public - */ - public bucket(): s3.Bucket { - return this.kfs.bucket(); + this.kinesisFirehose = kfs.kinesisFirehose; + this.s3Bucket = kfs.s3Bucket; } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json similarity index 52% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json index c5db98572..e2b4d1298 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics", + "version": "1.46.0", "description": "CDK constructs for defining an interaction between an Amazon Kinesis Data Firehose delivery stream and (1) an Amazon S3 bucket, and (2) an Amazon Kinesis Data Analytics application.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics" }, "author": { "name": "Amazon Web Services", @@ -33,37 +33,37 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.kinesisfirehoses3kinesisanalytics", + "package": "software.amazon.awsconstructs.services.kinesisfirehoses3kinesisanalytics", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "kinesisfirehoses3kinesisanalytics" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.KinesisFirehoseS3KinesisAnalytics", - "packageId": "Amazon.Konstruk.AWS.KinesisFirehoseS3KinesisAnalytics", + "namespace": "Amazon.Constructs.AWS.KinesisFirehoseS3KinesisAnalytics", + "packageId": "Amazon.Constructs.AWS.KinesisFirehoseS3KinesisAnalytics", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-kinesis-firehose-s3-kinesis-analytics", - "module": "aws_solutions_konstruk.aws_kinesis_firehose_s3_kinesis_analytics" + "distName": "aws-solutions-constructs.aws-kinesis-firehose-s3-kinesis-analytics", + "module": "aws_solutions_constructs.aws_kinesis_firehose_s3_kinesis_analytics" } } }, "dependencies": { - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kinesis": "~1.40.0", - "@aws-cdk/aws-kinesisanalytics": "~1.40.0", - "@aws-cdk/aws-kinesisfirehose": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-solutions-konstruk/aws-kinesisfirehose-s3": "~0.8.1", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kinesis": "~1.46.0", + "@aws-cdk/aws-kinesisanalytics": "~1.46.0", + "@aws-cdk/aws-kinesisfirehose": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-kinesisfirehose-s3": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -73,14 +73,14 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kinesis": "~1.40.0", - "@aws-cdk/aws-kinesisanalytics": "~1.40.0", - "@aws-cdk/aws-kinesisfirehose": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-solutions-konstruk/aws-kinesisfirehose-s3": "~0.8.1", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kinesis": "~1.46.0", + "@aws-cdk/aws-kinesisanalytics": "~1.46.0", + "@aws-cdk/aws-kinesisfirehose": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-kinesisfirehose-s3": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/__snapshots__/test.kinesisfirehose-analytics-s3.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/__snapshots__/test.kinesisfirehose-analytics-s3.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/__snapshots__/test.kinesisfirehose-analytics-s3.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/__snapshots__/test.kinesisfirehose-analytics-s3.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts index 3386d44d7..22733f158 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts @@ -61,7 +61,7 @@ test('Pattern deployment w/ default properties', () => { // -------------------------------------------------------------- // Test Case 2 - Test the getter methods // -------------------------------------------------------------- -test('Test getter methods', () => { +test('Test properties', () => { // Initial Setup const stack = new Stack(); const props: KinesisFirehoseToAnalyticsAndS3Props = { @@ -94,7 +94,7 @@ test('Test getter methods', () => { }; const app = new KinesisFirehoseToAnalyticsAndS3(stack, 'test-kinesis-firehose-kinesis-analytics', props); // Assertions - expect(app.kinesisAnalytics()).toBeDefined(); - expect(app.kinesisFirehose()).toBeDefined(); - expect(app.bucket()).toBeDefined(); + expect(app.kinesisAnalytics !== null); + expect(app.kinesisFirehose !== null); + expect(app.s3Bucket !== null); }); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.gitignore b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/README.md b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/README.md similarity index 59% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/README.md rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/README.md index 13a8c88aa..9885c2845 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-kinesisfirehose-s3/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_kinesisfirehose_s3`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-kinesisfirehose-s3`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_kinesisfirehose_s3`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-kinesisfirehose-s3`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.kinesisfirehoses3`| -This AWS Solutions Konstruk implements an Amazon Kinesis Data Firehose delivery stream connected to an Amazon S3 bucket. +This AWS Solutions Construct implements an Amazon Kinesis Data Firehose delivery stream connected to an Amazon S3 bucket. Here is a minimal deployable pattern definition: ``` javascript -const { KinesisFirehoseToS3 } = require('@aws-solutions-konstruk/aws-kinesisfirehose-s3'); +const { KinesisFirehoseToS3 } = require('@aws-solutions-constructs/aws-kinesisfirehose-s3'); new KinesisFirehoseToS3(stack, 'test-firehose-s3', {}); @@ -59,8 +58,24 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|kinesisFirehose()|[`kinesisfirehose.CfnDeliveryStream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStream.html)|Returns an instance of kinesisfirehose.CfnDeliveryStream created by the construct| -|bucket()|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct| +|kinesisFirehose|[`kinesisfirehose.CfnDeliveryStream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStream.html)|Returns an instance of kinesisfirehose.CfnDeliveryStream created by the construct| +|s3Bucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct| +|kinesisFirehoseRole|[`iam.Role`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html)|Returns an instance of the iam.Role created by the construct for Kinesis Data Firehose delivery stream| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon Kinesis Firehose +* Enable CloudWatch logging for Kinesis Firehose +* Configure least privilege access IAM role for Amazon Kinesis Firehose + +### Amazon S3 Bucket +* Configure Access logging for S3 Bucket +* Enable server-side encryption for S3 Bucket using AWS managed KMS Key +* Turn on the versioning for S3 Bucket +* Don't allow public access for S3 Bucket +* Retain the S3 Bucket when deleting the CloudFormation stack ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/architecture.png b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/lib/index.ts similarity index 82% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/lib/index.ts index c32d3af0e..a1e5c0a46 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/lib/index.ts @@ -14,9 +14,9 @@ import * as kinesisfirehose from '@aws-cdk/aws-kinesisfirehose'; import { Construct } from '@aws-cdk/core'; import * as s3 from '@aws-cdk/aws-s3'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import * as iam from '@aws-cdk/aws-iam'; -import { overrideProps } from '@aws-solutions-konstruk/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; import * as logs from '@aws-cdk/aws-logs'; import * as cdk from '@aws-cdk/core'; @@ -54,10 +54,9 @@ export interface KinesisFirehoseToS3Props { } export class KinesisFirehoseToS3 extends Construct { - - // Private variables - private firehose: kinesisfirehose.CfnDeliveryStream; - private s3Bucket: s3.Bucket; + public readonly kinesisFirehose: kinesisfirehose.CfnDeliveryStream; + public readonly kinesisFirehoseRole: iam.Role; + public readonly s3Bucket: s3.Bucket; /** * Constructs a new instance of the IotToLambda class. @@ -89,7 +88,7 @@ export class KinesisFirehoseToS3 extends Construct { const cwLogStream: logs.LogStream = cwLogGroup.addStream('firehose-log-stream'); // Setup the IAM Role for Kinesis Firehose - const firehoseRole = new iam.Role(this, 'KinesisFirehoseRole', { + this.kinesisFirehoseRole = new iam.Role(this, 'KinesisFirehoseRole', { assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), }); @@ -115,33 +114,19 @@ export class KinesisFirehoseToS3 extends Construct { ]}); // Attach policy to role - firehosePolicy.attachToRole(firehoseRole); + firehosePolicy.attachToRole(this.kinesisFirehoseRole); // Setup the default Kinesis Firehose props const defaultKinesisFirehoseProps: kinesisfirehose.CfnDeliveryStreamProps = - defaults.DefaultCfnDeliveryStreamProps(this.s3Bucket.bucketArn, firehoseRole.roleArn, + defaults.DefaultCfnDeliveryStreamProps(this.s3Bucket.bucketArn, this.kinesisFirehoseRole.roleArn, cwLogGroup.logGroupName, cwLogStream.logStreamName); // Override with the input props if (props.kinesisFirehoseProps) { const kinesisFirehoseProps = overrideProps(defaultKinesisFirehoseProps, props.kinesisFirehoseProps); - this.firehose = new kinesisfirehose.CfnDeliveryStream(this, 'KinesisFirehose', kinesisFirehoseProps); + this.kinesisFirehose = new kinesisfirehose.CfnDeliveryStream(this, 'KinesisFirehose', kinesisFirehoseProps); } else { - this.firehose = new kinesisfirehose.CfnDeliveryStream(this, 'KinesisFirehose', defaultKinesisFirehoseProps); + this.kinesisFirehose = new kinesisfirehose.CfnDeliveryStream(this, 'KinesisFirehose', defaultKinesisFirehoseProps); } } - - /** - * Returns an instance of kinesisfirehose.CfnDeliveryStream created by the construct - */ - public kinesisFirehose(): kinesisfirehose.CfnDeliveryStream { - return this.firehose; - } - - /** - * Returns an instance of s3.Bucket created by the construct - */ - public bucket(): s3.Bucket { - return this.s3Bucket; - } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/package.json b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/package.json similarity index 57% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/package.json rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/package.json index 0b3b0f1e4..5da63dbfc 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-kinesisfirehose-s3", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-kinesisfirehose-s3", + "version": "1.46.0", "description": "CDK constructs for defining an interaction between an Amazon Kinesis Data Firehose delivery stream and an Amazon S3 bucket.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3" }, "author": { "name": "Amazon Web Services", @@ -33,35 +33,35 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.kinesisfirehoses3", + "package": "software.amazon.awsconstructs.services.kinesisfirehoses3", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "kinesisfirehoses3" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.KinesisFirehoseS3", - "packageId": "Amazon.Konstruk.AWS.KinesisFirehoseS3", + "namespace": "Amazon.Constructs.AWS.KinesisFirehoseS3", + "packageId": "Amazon.Constructs.AWS.KinesisFirehoseS3", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-kinesis-firehose-s3", - "module": "aws_solutions_konstruk.aws_kinesis_firehose_s3" + "distName": "aws-solutions-constructs.aws-kinesis-firehose-s3", + "module": "aws_solutions_constructs.aws_kinesis_firehose_s3" } } }, "dependencies": { - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kinesisfirehose": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kinesisfirehose": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,12 +71,12 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kinesisfirehose": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kinesisfirehose": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts similarity index 92% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts index e62d545ac..71040c7ef 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts @@ -14,7 +14,6 @@ import { SynthUtils } from '@aws-cdk/assert'; import { KinesisFirehoseToS3, KinesisFirehoseToS3Props } from "../lib"; import * as cdk from '@aws-cdk/core'; -import * as kinesisfirehose from '@aws-cdk/aws-kinesisfirehose'; import '@aws-cdk/assert/jest'; function deploy(stack: cdk.Stack) { @@ -103,11 +102,12 @@ test('test kinesisFirehose override ', () => { }}); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: KinesisFirehoseToS3 = deploy(stack); - expect(construct.kinesisFirehose()).toBeInstanceOf(kinesisfirehose.CfnDeliveryStream); - expect(construct.bucket()).toBeDefined(); + expect(construct.kinesisFirehose !== null); + expect(construct.s3Bucket !== null); + expect(construct.kinesisFirehoseRole !== null); }); diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.npmignore b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/README.md similarity index 65% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/README.md index d70f9de0d..e333b695e 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-kinesisstreams-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_kinesisstreams_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-kinesisstreams-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_kinesisstreams_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-kinesisstreams-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.kinesisstreamslambda`| -This AWS Solutions Konstruk deploys a Kinesis Stream and Lambda function with the appropriate resources/properties for interaction and security. +This AWS Solutions Construct deploys a Kinesis Stream and Lambda function with the appropriate resources/properties for interaction and security. Here is a minimal deployable pattern definition: ``` javascript -const { KinesisStreamsToLambda } = require('@aws-solutions-konstruk/aws-kinesisstreams-lambda'); +const { KinesisStreamsToLambda } = require('@aws-solutions-constructs/aws-kinesisstreams-lambda'); new KinesisStreamsToLambda(stack, 'KinesisToLambdaPattern', { deployLambda: true, @@ -66,14 +65,26 @@ _Parameters_ |lambdaFunctionProps?|[`lambda.FunctionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.FunctionProps.html)|Optional user-provided props to override the default props for the Lambda function. This property is only required if `deployLambda` is set to true.| |kinesisStreamProps?|[`kinesis.StreamProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesis.StreamProps.html)|Optional user-provided props to override the default props for the Kinesis stream.| |eventSourceProps?|[`lambda.EventSourceMappingOptions`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.EventSourceMappingOptions.html)|Optional user-provided props to override the default props for the Lambda event source mapping.| -|encryptionKeyProps?|[`kms.KeyProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.KeyProps.html)|Optional user-provided props to override the default props for the KMS encryption key.| ## Pattern Properties | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|stream()|[`kinesis.Stream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesis.Stream.html)|Returns an instance of the Kinesis stream created by the pattern.| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| +|kinesisStream|[`kinesis.Stream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesis.Stream.html)|Returns an instance of the Kinesis stream created by the pattern.| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| +|kinesisStreamRole|[`iam.Role`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html)|Returns an instance of the iam.Role created by the construct for Kinesis stream.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon Kinesis Stream +* Configure least privilege access IAM role for Kinesis Stream +* Enable server-side encryption for Kinesis Stream using AWS Managed KMS Key + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/lib/index.ts similarity index 73% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/lib/index.ts index 199803c54..e34037336 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/lib/index.ts @@ -14,10 +14,9 @@ // Imports import * as lambda from '@aws-cdk/aws-lambda'; import * as kinesis from '@aws-cdk/aws-kinesis'; -import * as kms from '@aws-cdk/aws-kms'; import * as iam from '@aws-cdk/aws-iam'; -import * as defaults from '@aws-solutions-konstruk/core'; -import { overrideProps } from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; /** @@ -57,22 +56,15 @@ export interface KinesisStreamsToLambdaProps { * @default - Default props are used. */ readonly eventSourceProps?: lambda.EventSourceMappingOptions | any - /** - * Optional user-provided props to override the default props for the KMS encryption key. - * - * @default - Default props are used. - */ - readonly encryptionKeyProps?: kms.KeyProps | any } /** * @summary The KinesisStreamsToLambda class. */ export class KinesisStreamsToLambda extends Construct { - // Private variables - private kinesisStream: kinesis.Stream; - private fn: lambda.Function; - private encryptionKey: kms.Key; + public readonly kinesisStream: kinesis.Stream; + public readonly lambdaFunction: lambda.Function; + public readonly kinesisStreamRole: iam.Role; /** * @summary Constructs a new instance of the KinesisStreamsToLambda class. @@ -85,19 +77,13 @@ export class KinesisStreamsToLambda extends Construct { constructor(scope: Construct, id: string, props: KinesisStreamsToLambdaProps) { super(scope, id); - // Setup the encryption key - this.encryptionKey = defaults.buildEncryptionKey(this, { - encryptionKeyProps: props.encryptionKeyProps - }); - // Setup the Kinesis Stream this.kinesisStream = defaults.buildKinesisStream(this, { - encryptionKey: this.encryptionKey, kinesisStreamProps: props.kinesisStreamProps }); // Setup the Lambda function - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps @@ -107,11 +93,11 @@ export class KinesisStreamsToLambda extends Construct { const eventSourceProps = (props.eventSourceProps) ? overrideProps(defaults.DefaultKinesisEventSourceProps(this.kinesisStream.streamArn), props.eventSourceProps) : defaults.DefaultKinesisEventSourceProps(this.kinesisStream.streamArn); - this.fn.addEventSourceMapping('LambdaKinesisEventSourceMapping', eventSourceProps); + this.lambdaFunction.addEventSourceMapping('LambdaKinesisEventSourceMapping', eventSourceProps); // Add permissions for the Lambda function to access Kinesis const policy = new iam.Policy(this, 'LambdaFunctionPolicy'); - const role = this.fn.role as iam.Role; + this.kinesisStreamRole = this.lambdaFunction.role as iam.Role; policy.addStatements(new iam.PolicyStatement({ effect: iam.Effect.ALLOW, resources: [ this.kinesisStream.streamArn ], @@ -128,8 +114,8 @@ export class KinesisStreamsToLambda extends Construct { 'kinesis:ListStreams', ] })); - policy.attachToRole(role); - this.kinesisStream.grantRead(this.fn.grantPrincipal); + policy.attachToRole(this.kinesisStreamRole); + this.kinesisStream.grantRead(this.lambdaFunction.grantPrincipal); // Add appropriate cfn_nag metadata const cfnCustomPolicy = policy.node.defaultChild as iam.CfnPolicy; @@ -144,24 +130,4 @@ export class KinesisStreamsToLambda extends Construct { } }; } - - /** - * @summary Returns an instance of the kinesis.Stream created by the construct. - * @returns {kinesis.Stream} Instance of the Stream created by the construct. - * @since 0.8.0 - * @access public - */ - public stream(): kinesis.Stream { - return this.kinesisStream; - } - - /** - * @summary Returns an instance of the lambda.Function created by the construct. - * @returns {lambda.Function} Instance of the Function created by the construct. - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/package.json similarity index 58% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/package.json index 3fea2344a..64634f5a4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-kinesisstreams-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-kinesisstreams-lambda", + "version": "1.46.0", "description": "CDK constructs for defining an interaction between an Amazon Kinesis Data Stream and an AWS Lambda function.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,35 +34,35 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.kinesisstreamslambda", + "package": "software.amazon.awsconstructs.services.kinesisstreamslambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "kinesisstreamslambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.KinesisStreamsLambda", - "packageId": "Amazon.Konstruk.AWS.KinesisStreamsLambda", + "namespace": "Amazon.Constructs.AWS.KinesisStreamsLambda", + "packageId": "Amazon.Constructs.AWS.KinesisStreamsLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-kinesis-streams-lambda", - "module": "aws_solutions_konstruk.aws_kinesis_streams_lambda" + "distName": "aws-solutions-constructs.aws-kinesis-streams-lambda", + "module": "aws_solutions_constructs.aws_kinesis_streams_lambda" } } }, "dependencies": { - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kinesis": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kinesis": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -72,12 +72,12 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kinesis": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kinesis": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap similarity index 76% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap index 300bbac1a..6c176da95 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap @@ -17,83 +17,13 @@ Object { }, }, "Resources": Object { - "testkinesisstreamslambdaEncryptionKey6CFF01F7": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "EnableKeyRotation": true, - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - Object { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::GetAtt": Array [ - "testkinesisstreamslambdaLambdaFunctionServiceRoleD083672F", - "Arn", - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, "testkinesisstreamslambdaKinesisStream76FFCAB1": Object { "Properties": Object { "RetentionPeriodHours": 24, "ShardCount": 1, "StreamEncryption": Object { "EncryptionType": "KMS", - "KeyId": Object { - "Fn::GetAtt": Array [ - "testkinesisstreamslambdaEncryptionKey6CFF01F7", - "Arn", - ], - }, + "KeyId": "alias/aws/kinesis", }, }, "Type": "AWS::Kinesis::Stream", @@ -298,16 +228,6 @@ Object { ], }, }, - Object { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": Object { - "Fn::GetAtt": Array [ - "testkinesisstreamslambdaEncryptionKey6CFF01F7", - "Arn", - ], - }, - }, ], "Version": "2012-10-17", }, diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json similarity index 76% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json index 74499c45d..4bace5dc4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json @@ -1,71 +1,6 @@ { "Description": "Integration Test for aws-kinesisstreams-lambda", "Resources": { - "testkslambdaEncryptionKey4161DDEB": { - "Type": "AWS::KMS::Key", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" - ] - ] - } - }, - "Resource": "*" - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "testkslambdaLambdaFunctionServiceRole329F6464", - "Arn" - ] - } - }, - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "EnableKeyRotation": true - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, "testkslambdaKinesisStreamE607D575": { "Type": "AWS::Kinesis::Stream", "Properties": { @@ -73,12 +8,7 @@ "RetentionPeriodHours": 24, "StreamEncryption": { "EncryptionType": "KMS", - "KeyId": { - "Fn::GetAtt": [ - "testkslambdaEncryptionKey4161DDEB", - "Arn" - ] - } + "KeyId": "alias/aws/kinesis" } } }, @@ -153,16 +83,6 @@ "Arn" ] } - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "testkslambdaEncryptionKey4161DDEB", - "Arn" - ] - } } ], "Version": "2012-10-17" diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/integ.deployFunction.ts similarity index 98% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/integ.deployFunction.ts index d04854a6d..141928d5c 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/integ.deployFunction.ts @@ -24,7 +24,6 @@ stack.templateOptions.description = 'Integration Test for aws-kinesisstreams-lam // Definitions const props: KinesisStreamsToLambdaProps = { deployLambda: true, - encryptionKeyProps: {}, kinesisStreamProps: {}, eventSourceProps: { startingPosition: lambda.StartingPosition.TRIM_HORIZON, diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts similarity index 93% rename from source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts index 3496a455a..ba4ce4cc3 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts @@ -41,11 +41,10 @@ test('Pattern minimal deployment', () => { // -------------------------------------------------------------- // Test getter methods // -------------------------------------------------------------- -test('Test getter methods', () => { +test('Test properties', () => { // Initial Setup const stack = new Stack(); const props: KinesisStreamsToLambdaProps = { - encryptionKeyProps: {}, kinesisStreamProps: {}, deployLambda: true, lambdaFunctionProps: { @@ -60,7 +59,9 @@ test('Test getter methods', () => { }; const app = new KinesisStreamsToLambda(stack, 'test-kinesis-streams-lambda', props); // Assertion 1 - expect(app.lambdaFunction()).toBeDefined(); + expect(app.lambdaFunction !== null); // Assertion 2 - expect(app.stream()).toBeDefined(); + expect(app.kinesisStream !== null); + // Assertion 3 + expect(app.kinesisStreamRole !== null); }); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.gitignore b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.npmignore b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/README.md b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/README.md similarity index 63% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/README.md rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/README.md index 94c615d1c..977a4bfcc 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-lambda-dynamodb/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_lambda_dynamodb`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-lambda-dynamodb`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_lambda_dynamodb`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-lambda-dynamodb`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.lambdadynamodb`| -This AWS Solutions Konstruk implements the AWS Lambda function and Amazon DynamoDB table with the least privileged permissions. +This AWS Solutions Construct implements the AWS Lambda function and Amazon DynamoDB table with the least privileged permissions. Here is a minimal deployable pattern definition: ``` javascript -const { LambdaToDynamoDBProps, LambdaToDynamoDB } = require('@aws-solutions-konstruk/aws-lambda-dynamodb'); +const { LambdaToDynamoDBProps, LambdaToDynamoDB } = require('@aws-solutions-constructs/aws-lambda-dynamodb'); const props: LambdaToDynamoDBProps = { deployLambda: true, @@ -68,8 +67,22 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| -|dynamoTable()|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| +|dynamoTable|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Returns an instance of dynamodb.Table created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function + +### Amazon DynamoDB Table +* Set the billing mode for DynamoDB Table to On-Demand (Pay per request) +* Enable server-side encryption for DynamoDB Table using AWS managed KMS Key +* Creates a partition key called 'id' for DynamoDB Table +* Retain the Table when deleting the CloudFormation stack ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/architecture.png b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/lib/index.ts similarity index 71% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/lib/index.ts index 9d822f260..82dedfabc 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/lib/index.ts @@ -13,9 +13,9 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; -import { overrideProps } from '@aws-solutions-konstruk/core'; +import { overrideProps } from '@aws-solutions-constructs/core'; /** * @summary The properties for the LambdaToDynamoDB Construct @@ -51,8 +51,8 @@ export interface LambdaToDynamoDBProps { } export class LambdaToDynamoDB extends Construct { - private fn: lambda.Function; - private table: dynamodb.Table; + public readonly lambdaFunction: lambda.Function; + public readonly dynamoTable: dynamodb.Table; /** * @summary Constructs a new instance of the LambdaToDynamoDB class. @@ -65,7 +65,7 @@ export class LambdaToDynamoDB extends Construct { constructor(scope: Construct, id: string, props: LambdaToDynamoDBProps) { super(scope, id); - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps @@ -74,18 +74,18 @@ export class LambdaToDynamoDB extends Construct { // Set the default props for DynamoDB table if (props.dynamoTableProps) { const dynamoTableProps = overrideProps(defaults.DefaultTableProps, props.dynamoTableProps); - this.table = new dynamodb.Table(this, 'DynamoTable', dynamoTableProps); + this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', dynamoTableProps); } else { - this.table = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableProps); + this.dynamoTable = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableProps); } - this.fn.addEnvironment('DDB_TABLE_NAME', this.table.tableName); + this.lambdaFunction.addEnvironment('DDB_TABLE_NAME', this.dynamoTable.tableName); - this.table.grantReadWriteData(this.fn.grantPrincipal); + this.dynamoTable.grantReadWriteData(this.lambdaFunction.grantPrincipal); // Conditional metadata for cfn_nag if (props.dynamoTableProps?.billingMode === dynamodb.BillingMode.PROVISIONED) { - const cfnTable: dynamodb.CfnTable = this.table.node.findChild('Resource') as dynamodb.CfnTable; + const cfnTable: dynamodb.CfnTable = this.dynamoTable.node.findChild('Resource') as dynamodb.CfnTable; cfnTable.cfnOptions.metadata = { cfn_nag: { rules_to_suppress: [{ @@ -96,25 +96,4 @@ export class LambdaToDynamoDB extends Construct { }; } } - - /** - * @summary Returns an instance of dynamodb.Table created by the construct. - * @returns {dynamodb.Table} Instance of dynamodb.Table created by the construct - * @since 0.8.0 - * @access public - */ - public dynamoTable(): dynamodb.Table { - return this.table; - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of lambda.Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/package.json b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/package.json similarity index 61% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/package.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/package.json index 004f9a249..8b159eaf3 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-lambda-dynamodb", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-lambda-dynamodb", + "version": "1.46.0", "description": "CDK Constructs for AWS Lambda to AWS DynamoDB integration.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb" }, "author": { "name": "Amazon Web Services", @@ -34,33 +34,33 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.lambdadynamodb", + "package": "software.amazon.awsconstructs.services.lambdadynamodb", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "lambdadynamodb" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.LambdaDynamodb", - "packageId": "Amazon.Konstruk.AWS.LambdaDynamodb", + "namespace": "Amazon.Constructs.AWS.LambdaDynamodb", + "packageId": "Amazon.Constructs.AWS.LambdaDynamodb", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-lambda-dynamodb", - "module": "aws_solutions_konstruk.aws_lambda_dynamodb" + "distName": "aws-solutions-constructs.aws-lambda-dynamodb", + "module": "aws_solutions_constructs.aws_lambda_dynamodb" } } }, "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -70,10 +70,10 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.add-secondary-index.ts similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.add-secondary-index.ts index 127960e45..1805a1c20 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.add-secondary-index.ts @@ -37,6 +37,6 @@ const props: dynamodb.GlobalSecondaryIndexProps = { }, indexName: 'test_id2' }; -construct.dynamoTable().addGlobalSecondaryIndex(props); +construct.dynamoTable.addGlobalSecondaryIndex(props); app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.set-billing-mode.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.set-billing-mode.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-func.ts similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-func.ts index 203a9cef5..d254de838 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/integ.use-existing-func.ts @@ -15,7 +15,7 @@ import { App, Stack } from "@aws-cdk/core"; import { LambdaToDynamoDB } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; const app = new App(); const stack = new Stack(app, 'test-lambda-dynamodb-stack'); diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts index 0fb55c051..03c9d799a 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts @@ -252,13 +252,13 @@ test('check iot lambda function role for deploy: false', () => { ] }); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: LambdaToDynamoDB = deployNewFunc(stack); - expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); - expect(construct.dynamoTable()).toBeInstanceOf(dynamodb.Table); + expect(construct.lambdaFunction !== null); + expect(construct.dynamoTable !== null); }); test('check exception for Missing existingObj from props for deploy = false', () => { const stack = new cdk.Stack(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-lambda-dynamodb/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.npmignore b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/README.md b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/README.md similarity index 52% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/README.md rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/README.md index 5ec92d388..f1d8fa6c5 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-lambda-elasticsearch-kibana/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_lambda_elasticsearch_kibana`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_lambda_elasticsearch_kibana`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-lambda-elasticsearch-kibana`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.lambdaelasticsearchkibana`| -This AWS Solutions Konstruk implements the AWS Lambda function and Amazon Elasticsearch Service with the least privileged permissions. +This AWS Solutions Construct implements the AWS Lambda function and Amazon Elasticsearch Service with the least privileged permissions. Here is a minimal deployable pattern definition: ``` javascript -const { LambdaToElasticSearchAndKibana } = require('@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana'); +const { LambdaToElasticSearchAndKibana } = require('@aws-solutions-constructs/aws-lambda-elasticsearch-kibana'); const lambdaProps: lambda.FunctionProps = { code: lambda.Code.asset(`${__dirname}/lambda`), @@ -70,12 +69,31 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| -|userPool()|[`cognito.UserPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPool.html)|Returns an instance of cognito.UserPool created by the construct| -|userPoolClient()|[`cognito.UserPoolClient`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPoolClient.html)|Returns an instance of cognito.UserPoolClient created by the construct| -|identityPool()|[`cognito.CfnIdentityPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.CfnIdentityPool.html)|Returns an instance of cognito.CfnIdentityPool created by the construct| -|elasticsearchDomain()|[`elasticsearch.CfnDomain`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticsearch.CfnDomain.html)|Returns an instance of elasticsearch.CfnDomain created by the construct| -|cloudwatchAlarms()|[`cloudwatch.Alarm[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudwatch.Alarm.html)|Returns a list of cloudwatch.Alarm created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of lambda.Function created by the construct| +|userPool|[`cognito.UserPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPool.html)|Returns an instance of cognito.UserPool created by the construct| +|userPoolClient|[`cognito.UserPoolClient`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPoolClient.html)|Returns an instance of cognito.UserPoolClient created by the construct| +|identityPool|[`cognito.CfnIdentityPool`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.CfnIdentityPool.html)|Returns an instance of cognito.CfnIdentityPool created by the construct| +|elasticsearchDomain|[`elasticsearch.CfnDomain`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticsearch.CfnDomain.html)|Returns an instance of elasticsearch.CfnDomain created by the construct| +|cloudwatchAlarms|[`cloudwatch.Alarm[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudwatch.Alarm.html)|Returns a list of cloudwatch.Alarm created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function + +### Amazon Cognito +* Set password policy for User Pools +* Enforce the advanced security mode for User Pools + +### Amazon Elasticsearch Service +* Deploy best practices CloudWatch Alarms for the Elasticsearch Domain +* Secure the Kibana dashboard access with Cognito User Pools +* Enable server-side encryption for Elasticsearch Domain using AWS managed KMS Key +* Enable node-to-node encryption for Elasticsearch Domain +* Configure the cluster for the Amazon ES domain ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/architecture.png b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/lib/index.ts similarity index 52% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/lib/index.ts index 17ba52ae8..314d1a27d 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/lib/index.ts @@ -14,7 +14,7 @@ import * as elasticsearch from '@aws-cdk/aws-elasticsearch'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cognito from '@aws-cdk/aws-cognito'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; import { Role } from '@aws-cdk/aws-iam'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; @@ -59,12 +59,12 @@ export interface LambdaToElasticSearchAndKibanaProps { } export class LambdaToElasticSearchAndKibana extends Construct { - private userpool: cognito.UserPool; - private identitypool: cognito.CfnIdentityPool; - private userpoolclient: cognito.UserPoolClient; - private elasticsearch: elasticsearch.CfnDomain; - private fn: lambda.Function; - private cwAlarms: cloudwatch.Alarm[]; + public readonly userPool: cognito.UserPool; + public readonly identityPool: cognito.CfnIdentityPool; + public readonly userPoolClient: cognito.UserPoolClient; + public readonly elasticsearchDomain: elasticsearch.CfnDomain; + public readonly lambdaFunction: lambda.Function; + public readonly cloudwatchAlarms: cloudwatch.Alarm[]; /** * @summary Constructs a new instance of the CognitoToApiGatewayToLambda class. @@ -77,95 +77,35 @@ export class LambdaToElasticSearchAndKibana extends Construct { constructor(scope: Construct, id: string, props: LambdaToElasticSearchAndKibanaProps) { super(scope, id); - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps }); // Find the lambda service Role ARN - const lambdaFunctionRoleARN = this.fn.role?.roleArn; + const lambdaFunctionRoleARN = this.lambdaFunction.role?.roleArn; - this.userpool = defaults.buildUserPool(this); - this.userpoolclient = defaults.buildUserPoolClient(this, this.userpool); - this.identitypool = defaults.buildIdentityPool(this, this.userpool, this.userpoolclient); + this.userPool = defaults.buildUserPool(this); + this.userPoolClient = defaults.buildUserPoolClient(this, this.userPool); + this.identityPool = defaults.buildIdentityPool(this, this.userPool, this.userPoolClient); const cognitoAuthorizedRole: Role = defaults.setupCognitoForElasticSearch(this, props.domainName, { - userpool: this.userpool, - identitypool: this.identitypool, - userpoolclient: this.userpoolclient + userpool: this.userPool, + identitypool: this.identityPool, + userpoolclient: this.userPoolClient }); - this.elasticsearch = defaults.buildElasticSearch(this, props.domainName, { - userpool: this.userpool, - identitypool: this.identitypool, + this.elasticsearchDomain = defaults.buildElasticSearch(this, props.domainName, { + userpool: this.userPool, + identitypool: this.identityPool, cognitoAuthorizedRoleARN: cognitoAuthorizedRole.roleArn, serviceRoleARN: lambdaFunctionRoleARN}, props.esDomainProps); // Add ES Domain to lambda envrionment variable - this.fn.addEnvironment('DOMAIN_ENDPOINT', this.elasticsearch.attrDomainEndpoint); + this.lambdaFunction.addEnvironment('DOMAIN_ENDPOINT', this.elasticsearchDomain.attrDomainEndpoint); // Deploy best practices CW Alarms for ES - this.cwAlarms = defaults.buildElasticSearchCWAlarms(this); - } - - /** - * @summary Returns an instance of lambda.Function created by the construct. - * @returns {lambda.Function} Instance of Function created by the construct - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of cognito.UserPool created by the construct. - * @returns {cognito.UserPool} Instance of UserPool created by the construct - * @since 0.8.0 - * @access public - */ - public userPool(): cognito.UserPool { - return this.userpool; - } - - /** - * @summary Returns an instance of cognito.UserPoolClient created by the construct. - * @returns {cognito.UserPoolClient} Instance of UserPoolClient created by the construct - * @since 0.8.0 - * @access public - */ - public userPoolClient(): cognito.UserPoolClient { - return this.userpoolclient; - } - - /** - * @summary Returns an instance of cognito.CfnIdentityPool created by the construct. - * @returns {cognito.CfnIdentityPool} Instance of CfnIdentityPool created by the construct - * @since 0.8.0 - * @access public - */ - public identityPool(): cognito.CfnIdentityPool { - return this.identitypool; - } - - /** - * @summary Returns an instance of elasticsearch.CfnDomain created by the construct. - * @returns {elasticsearch.CfnDomain} Instance of CfnDomain created by the construct - * @since 0.8.0 - * @access public - */ - public elasticsearchDomain(): elasticsearch.CfnDomain { - return this.elasticsearch; - } - - /** - * @summary Returns a list of cloudwatch.Alarm created by the construct. - * @returns {cloudwatch.Alarm[]} List of cloudwatch.Alarm created by the construct - * @since 0.8.0 - * @access public - */ - public cloudwatchAlarms(): cloudwatch.Alarm[] { - return this.cwAlarms; + this.cloudwatchAlarms = defaults.buildElasticSearchCWAlarms(this); } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/package.json b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/package.json similarity index 54% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/package.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/package.json index 30f802b3a..9be3878a7 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-lambda-elasticsearch-kibana", + "version": "1.46.0", "description": "CDK Constructs for AWS Lambda to AWS Elasticsearch with Kibana integration", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana" }, "author": { "name": "Amazon Web Services", @@ -34,36 +34,36 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.lambdaelasticsearchkibana", + "package": "software.amazon.awsconstructs.services.lambdaelasticsearchkibana", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "lambdaelasticsearchkibana" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.LambdaElasticsearchKibana", - "packageId": "Amazon.Konstruk.AWS.LambdaElasticsearchKibana", + "namespace": "Amazon.Constructs.AWS.LambdaElasticsearchKibana", + "packageId": "Amazon.Constructs.AWS.LambdaElasticsearchKibana", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-lambda-elasticsearch-kibana", - "module": "aws_solutions_konstruk.aws_lambda_elasticsearch_kibana" + "distName": "aws-solutions-constructs.aws-lambda-elasticsearch-kibana", + "module": "aws_solutions_constructs.aws_lambda_elasticsearch_kibana" } } }, "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-elasticsearch": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-cloudwatch": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-elasticsearch": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -73,13 +73,13 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-elasticsearch": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-cloudwatch": "~1.40.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-elasticsearch": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap index bd735cd80..05def506b 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap @@ -230,6 +230,18 @@ Object { }, "testlambdaelasticsearchstackCognitoUserPool05D1387E": Object { "Properties": Object { + "AccountRecoverySetting": Object { + "RecoveryMechanisms": Array [ + Object { + "Name": "verified_phone_number", + "Priority": 1, + }, + Object { + "Name": "verified_email", + "Priority": 2, + }, + ], + }, "AdminCreateUserConfig": Object { "AllowAdminCreateUserOnly": true, }, @@ -259,6 +271,24 @@ Object { }, "testlambdaelasticsearchstackCognitoUserPoolClient6610371B": Object { "Properties": Object { + "AllowedOAuthFlows": Array [ + "implicit", + "code", + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": Array [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin", + ], + "CallbackURLs": Array [ + "https://example.com", + ], + "SupportedIdentityProviders": Array [ + "COGNITO", + ], "UserPoolId": Object { "Ref": "testlambdaelasticsearchstackCognitoUserPool05D1387E", }, diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json index 227a829bc..43b80bb98 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json @@ -176,6 +176,18 @@ "testlambdaelasticsearchkibanaCognitoUserPool9537802B": { "Type": "AWS::Cognito::UserPool", "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": true }, @@ -207,7 +219,25 @@ "Properties": { "UserPoolId": { "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" - } + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO" + ] } }, "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0": { @@ -233,7 +263,7 @@ "testlambdaelasticsearchkibanaUserPoolDomainB9BDF063": { "Type": "AWS::Cognito::UserPoolDomain", "Properties": { - "Domain": "test-domain1", + "Domain": "myconstructsdomain", "UserPoolId": { "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" } @@ -286,7 +316,7 @@ { "Ref": "AWS::AccountId" }, - ":domain/test-domain1/*" + ":domain/myconstructsdomain/*" ] ] } @@ -391,7 +421,7 @@ { "Ref": "AWS::AccountId" }, - ":domain/test-domain1" + ":domain/myconstructsdomain" ] ] } @@ -459,7 +489,7 @@ { "Ref": "AWS::AccountId" }, - ":domain/test-domain1/*" + ":domain/myconstructsdomain/*" ] ] } @@ -482,7 +512,7 @@ "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" } }, - "DomainName": "test-domain1", + "DomainName": "myconstructsdomain", "EBSOptions": { "EBSEnabled": true, "VolumeSize": 10 diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts index 77a79c83a..95ebacfb5 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts @@ -29,7 +29,7 @@ const lambdaProps: lambda.FunctionProps = { new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { lambdaFunctionProps: lambdaProps, deployLambda: true, - domainName: 'test-domain1' + domainName: 'myconstructsdomain' }); // Synth diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts similarity index 80% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts index 78212c177..c0d0556b7 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts @@ -16,8 +16,6 @@ import { LambdaToElasticSearchAndKibana, LambdaToElasticSearchAndKibanaProps } f import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from "@aws-cdk/core"; import '@aws-cdk/assert/jest'; -import { CfnDomain } from '@aws-cdk/aws-elasticsearch'; -import { CfnIdentityPool, UserPool, UserPoolClient } from '@aws-cdk/aws-cognito'; function deployNewFunc(stack: cdk.Stack) { const props: LambdaToElasticSearchAndKibanaProps = { @@ -57,17 +55,17 @@ test('check domain names', () => { }); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: LambdaToElasticSearchAndKibana = deployNewFunc(stack); - expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); - expect(construct.elasticsearchDomain()).toBeInstanceOf(CfnDomain); - expect(construct.identityPool()).toBeInstanceOf(CfnIdentityPool); - expect(construct.userPool()).toBeInstanceOf(UserPool); - expect(construct.userPoolClient()).toBeInstanceOf(UserPoolClient); - expect(construct.cloudwatchAlarms()).toHaveLength(9); + expect(construct.lambdaFunction !== null); + expect(construct.elasticsearchDomain !== null); + expect(construct.identityPool !== null); + expect(construct.userPool !== null); + expect(construct.userPoolClient !== null); + expect(construct.cloudwatchAlarms !== null); }); test('check exception for Missing existingObj from props for deploy = false', () => { diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.gitignore b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.npmignore b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/README.md b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/README.md similarity index 64% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/README.md rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/README.md index 219997e03..5cde61ea5 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-lambda-s3/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_lambda_s3`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-lambda-s3`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_lambda_s3`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-lambda-s3`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.lambdas3`| -This AWS Solutions Konstruk implements an AWS Lambda function connected to an Amazon S3 bucket. +This AWS Solutions Construct implements an AWS Lambda function connected to an Amazon S3 bucket. Here is a minimal deployable pattern definition: ``` javascript -const { LambdaToS3 } = require('@aws-solutions-konstruk/aws-lambda-s3'); +const { LambdaToS3 } = require('@aws-solutions-constructs/aws-lambda-s3'); new LambdaToS3(stack, 'LambdaToS3Pattern', { deployLambda: true, @@ -65,9 +64,25 @@ _Parameters_ |bucketProps?|[`s3.BucketProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.BucketProps.html)|Optional user provided props to override the default props for S3 Bucket| |bucketPermissions?|`string[]`|Optional bucket permissions to grant to the Lambda function. One or more of the following may be specified: `Delete`, `Put`, `Read`, `ReadWrite`, `Write`.| +## Pattern Properties -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| -|s3Bucket()|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of the S3 bucket created by the pattern.| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| +|s3Bucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of the S3 bucket created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function + +### Amazon S3 Bucket +* Configure Access logging for S3 Bucket +* Enable server-side encryption for S3 Bucket using AWS managed KMS Key +* Turn on the versioning for S3 Bucket +* Don't allow public access for S3 Bucket +* Retain the S3 Bucket when deleting the CloudFormation stack ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/architecture.png b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/lib/index.ts similarity index 76% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/lib/index.ts index 091d15244..17b00ed92 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/lib/index.ts @@ -14,7 +14,7 @@ // Imports import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; /** @@ -76,9 +76,8 @@ export interface LambdaToS3Props { * @summary The LambdaToS3 class. */ export class LambdaToS3 extends Construct { - // Private variables - private fn: lambda.Function; - private bucket: s3.Bucket; + public readonly lambdaFunction: lambda.Function; + public readonly s3Bucket: s3.Bucket; /** * @summary Constructs a new instance of the LambdaToS3 class. @@ -92,45 +91,45 @@ export class LambdaToS3 extends Construct { super(scope, id); // Setup the Lambda function - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps }); // Setup the S3 bucket - this.bucket = defaults.buildS3Bucket(this, { + this.s3Bucket = defaults.buildS3Bucket(this, { deployBucket: props.deployBucket, existingBucketObj: props.existingBucketObj, bucketProps: props.bucketProps }); // Configure environment variables - this.fn.addEnvironment('S3_BUCKET_NAME', this.bucket.bucketName); + this.lambdaFunction.addEnvironment('S3_BUCKET_NAME', this.s3Bucket.bucketName); // Add the requested or default bucket permissions if (props.hasOwnProperty('bucketPermissions') && props.bucketPermissions) { if (props.bucketPermissions.includes('Delete')) { - this.bucket.grantDelete(this.fn.grantPrincipal); + this.s3Bucket.grantDelete(this.lambdaFunction.grantPrincipal); } if (props.bucketPermissions.includes('Put')) { - this.bucket.grantPut(this.fn.grantPrincipal); + this.s3Bucket.grantPut(this.lambdaFunction.grantPrincipal); } if (props.bucketPermissions.includes('Read')) { - this.bucket.grantRead(this.fn.grantPrincipal); + this.s3Bucket.grantRead(this.lambdaFunction.grantPrincipal); } if (props.bucketPermissions.includes('ReadWrite')) { - this.bucket.grantReadWrite(this.fn.grantPrincipal); + this.s3Bucket.grantReadWrite(this.lambdaFunction.grantPrincipal); } if (props.bucketPermissions.includes('Write')) { - this.bucket.grantWrite(this.fn.grantPrincipal); + this.s3Bucket.grantWrite(this.lambdaFunction.grantPrincipal); } } else { - this.bucket.grantReadWrite(this.fn.grantPrincipal); + this.s3Bucket.grantReadWrite(this.lambdaFunction.grantPrincipal); } // Add appropriate metadata - const s3BucketResource = this.bucket.node.findChild('Resource') as s3.CfnBucket; + const s3BucketResource = this.s3Bucket.node.findChild('Resource') as s3.CfnBucket; s3BucketResource.cfnOptions.metadata = { cfn_nag: { rules_to_suppress: [{ @@ -140,24 +139,4 @@ export class LambdaToS3 extends Construct { } }; } - - /** - * @summary Returns an instance of the lambda.Function created by the construct. - * @returns {lambda.Function} Instance of the Function created by the construct. - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of the s3.Bucket created by the construct. - * @returns {s3.Bucket} Instance of the Bucket created by the construct. - * @since 0.8.0 - * @access public - */ - public s3Bucket(): s3.Bucket { - return this.bucket; - } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/package.json b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/package.json similarity index 62% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/package.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/package.json index 1e2e3d60c..277e06941 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-lambda-s3", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-lambda-s3", + "version": "1.46.0", "description": "CDK constructs for defining an interaction between an AWS Lambda function and an Amazon S3 bucket.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-lambda-s3" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-lambda-s3" }, "author": { "name": "Amazon Web Services", @@ -33,33 +33,33 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.lambdas3", + "package": "software.amazon.awsconstructs.services.lambdas3", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "lambdas3" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.LambdaS3", - "packageId": "Amazon.Konstruk.AWS.LambdaS3", + "namespace": "Amazon.Constructs.AWS.LambdaS3", + "packageId": "Amazon.Constructs.AWS.LambdaS3", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-lambda-s3", - "module": "aws_solutions_konstruk.aws_lambda_s3" + "distName": "aws-solutions-constructs.aws-lambda-s3", + "module": "aws_solutions_constructs.aws_lambda_s3" } } }, "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -69,10 +69,10 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.deployFunction.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.deployFunction.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.deployFunction.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.deployFunction.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.existingFunction.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.existingFunction.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.existingFunction.ts similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.existingFunction.ts index 738079769..2467e9150 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/integ.existingFunction.ts @@ -15,7 +15,7 @@ import { App, Stack } from "@aws-cdk/core"; import { LambdaToS3, LambdaToS3Props } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; // Setup const app = new App(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda-s3.test.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/lambda-s3.test.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda-s3.test.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/lambda-s3.test.ts index 324d3506a..cf2bffc42 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda-s3.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/lambda-s3.test.ts @@ -160,7 +160,7 @@ test('Test deployment w/ s3 multiple permissions', () => { // -------------------------------------------------------------- // Test the getter methods // -------------------------------------------------------------- -test('Test the getter methods', () => { +test('Test the properties', () => { // Stack const stack = new Stack(); // Helper declaration @@ -174,11 +174,11 @@ test('Test the getter methods', () => { bucketPermissions: ['Write'] }); // Assertion 1 - const func = pattern.lambdaFunction(); - expect(func).toBeDefined(); + const func = pattern.lambdaFunction; + expect(func !== null); // Assertion 2 - const bucket = pattern.s3Bucket(); - expect(bucket).toBeDefined(); + const bucket = pattern.s3Bucket; + expect(bucket !== null); }); // -------------------------------------------------------------- diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-lambda-s3/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.gitignore b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/README.md b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/README.md similarity index 67% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/README.md rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/README.md index bd54833d6..b40dec2f0 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-lambda-sns/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_lambda_sns`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-lambda-sns`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_lambda_sns`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-lambda-sns`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.lambdasns`| -This AWS Solutions Konstruk implements an AWS Lambda function connected to an Amazon SNS topic. +This AWS Solutions Construct implements an AWS Lambda function connected to an Amazon SNS topic. Here is a minimal deployable pattern definition: ``` javascript -const { LambdaToSns } = require('@aws-solutions-konstruk/aws-lambda-sns'); +const { LambdaToSns } = require('@aws-solutions-constructs/aws-lambda-sns'); new LambdaToSns(stack, 'LambdaToSnsPattern', { deployLambda: true, @@ -68,8 +67,20 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| -|snsTopic()|[`sns.Topic`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.Topic.html)|Returns an instance of the SNS topic created by the pattern.| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| +|snsTopic|[`sns.Topic`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.Topic.html)|Returns an instance of the SNS topic created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function + +### Amazon SNS Topic +* Configure least privilege access permissions for SNS Topic +* Enable server-side encryption forSNS Topic using Customer managed KMS Key ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/architecture.png b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/lib/index.ts similarity index 76% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/lib/index.ts index d7518d58d..d4abe8cc6 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/lib/index.ts @@ -15,7 +15,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as kms from '@aws-cdk/aws-kms'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; /** @@ -68,9 +68,8 @@ export interface LambdaToSnsProps { * @summary The LambdaToSns class. */ export class LambdaToSns extends Construct { - // Private variables - private fn: lambda.Function; - private topic: sns.Topic; + public readonly lambdaFunction: lambda.Function; + public readonly snsTopic: sns.Topic; /** * @summary Constructs a new instance of the LambdaToSns class. @@ -84,43 +83,23 @@ export class LambdaToSns extends Construct { super(scope, id); // Setup the Lambda function - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps }); // Setup the SNS topic - this.topic = defaults.buildTopic(this, { + this.snsTopic = defaults.buildTopic(this, { enableEncryption: props.enableEncryption, encryptionKey: props.encryptionKey }); // Configure environment variables - this.fn.addEnvironment('SNS_TOPIC_NAME', this.topic.topicName); - this.fn.addEnvironment('SNS_TOPIC_ARN', this.topic.topicArn); + this.lambdaFunction.addEnvironment('SNS_TOPIC_NAME', this.snsTopic.topicName); + this.lambdaFunction.addEnvironment('SNS_TOPIC_ARN', this.snsTopic.topicArn); // Add publishing permissions to the function - this.topic.grantPublish(this.fn.grantPrincipal); - } - - /** - * @summary Returns an instance of the lambda.Function created by the construct. - * @returns {lambda.Function} Instance of the Function created by the construct. - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of the sns.Topic created by the construct. - * @returns {sns.Topic} Instance of the Topic created by the construct. - * @since 0.8.0 - * @access public - */ - public snsTopic(): sns.Topic { - return this.topic; + this.snsTopic.grantPublish(this.lambdaFunction.grantPrincipal); } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/package.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/package.json similarity index 60% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/package.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/package.json index f68803794..ef8af8c5e 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-lambda-sns", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-lambda-sns", + "version": "1.46.0", "description": "CDK constructs for defining an interaction between an AWS Lambda function and an Amazon SNS topic.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-lambda-sns" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-lambda-sns" }, "author": { "name": "Amazon Web Services", @@ -33,34 +33,34 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.lambdasns", + "package": "software.amazon.awsconstructs.services.lambdasns", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "lambdasns" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.LambdaSns", - "packageId": "Amazon.Konstruk.AWS.LambdaSns", + "namespace": "Amazon.Constructs.AWS.LambdaSns", + "packageId": "Amazon.Constructs.AWS.LambdaSns", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-lambda-sns", - "module": "aws_solutions_konstruk.aws_lambda_sns" + "distName": "aws-solutions-constructs.aws-lambda-sns", + "module": "aws_solutions_constructs.aws_lambda_sns" } } }, "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-sns": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-sns": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -70,11 +70,11 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-sns": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-sns": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/__snapshots__/lambda-sns.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/__snapshots__/lambda-sns.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/__snapshots__/lambda-sns.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/__snapshots__/lambda-sns.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.deployFunction.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.deployFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.deployFunction.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.deployFunction.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.deployFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.deployFunction.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.existingFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.existingFunction.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.existingFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.existingFunction.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.existingFunction.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.existingFunction.ts similarity index 95% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.existingFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.existingFunction.ts index 575b481d7..37e5df975 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.existingFunction.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/integ.existingFunction.ts @@ -15,7 +15,7 @@ import { App, Stack } from "@aws-cdk/core"; import { LambdaToSns, LambdaToSnsProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; // Setup const app = new App(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/lambda-sns.test.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/lambda-sns.test.ts similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/lambda-sns.test.ts rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/lambda-sns.test.ts index e24fb0287..f472d7cab 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/lambda-sns.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/lambda-sns.test.ts @@ -130,7 +130,7 @@ test('Test deployment with imported encryption key', () => { // -------------------------------------------------------------- // Test the getter methods // -------------------------------------------------------------- -test('Test the getter methods', () => { +test('Test the properties', () => { // Stack const stack = new Stack(); // Helper declaration @@ -143,9 +143,9 @@ test('Test the getter methods', () => { } }); // Assertion 1 - const func = pattern.lambdaFunction(); - expect(func).toBeDefined(); + const func = pattern.lambdaFunction; + expect(func !== null); // Assertion 2 - const topic = pattern.snsTopic(); - expect(topic).toBeDefined(); + const topic = pattern.snsTopic; + expect(topic !== null); }); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-lambda-sns/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/README.md similarity index 69% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/README.md index 188a26ea2..917403394 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-s3-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_s3_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-s3-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_s3_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-s3-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.s3lambda`| -This AWS Solutions Konstruk implements an Amazon S3 bucket connected to an AWS Lambda function. +This AWS Solutions Construct implements an Amazon S3 bucket connected to an AWS Lambda function. Here is a minimal deployable pattern definition: ``` javascript -const { S3ToLambdaProps, S3ToLambda } = require('@aws-solutions-konstruk/aws-s3-lambda'); +const { S3ToLambdaProps, S3ToLambda } = require('@aws-solutions-constructs/aws-s3-lambda'); const stack = new Stack(app, 'test-s3-lambda-stack'); @@ -73,8 +72,23 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the lambda.Function created by the construct| -|s3Bucket()|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of the s3.Bucket created by the construct| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the lambda.Function created by the construct| +|s3Bucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of the s3.Bucket created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon S3 Bucket +* Configure Access logging for S3 Bucket +* Enable server-side encryption for S3 Bucket using AWS managed KMS Key +* Turn on the versioning for S3 Bucket +* Don't allow public access for S3 Bucket +* Retain the S3 Bucket when deleting the CloudFormation stack + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/lib/index.ts similarity index 84% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/lib/index.ts index 5931974ee..ce684bdf4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/lib/index.ts @@ -15,7 +15,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import { Construct, Stack } from '@aws-cdk/core'; import * as iam from '@aws-cdk/aws-iam'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { S3EventSourceProps, S3EventSource } from '@aws-cdk/aws-lambda-event-sources'; /** @@ -73,9 +73,8 @@ export interface S3ToLambdaProps { } export class S3ToLambda extends Construct { - // Private variables - private fn: lambda.Function; - private bucket: s3.Bucket; + public readonly lambdaFunction: lambda.Function; + public readonly s3Bucket: s3.Bucket; /** * @summary Constructs a new instance of the S3ToLambda class. * @param {cdk.App} scope - represents the scope for all the resources. @@ -87,20 +86,20 @@ export class S3ToLambda extends Construct { constructor(scope: Construct, id: string, props: S3ToLambdaProps) { super(scope, id); - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps }); - this.bucket = defaults.buildS3Bucket(this, { + this.s3Bucket = defaults.buildS3Bucket(this, { deployBucket: props.deployBucket, existingBucketObj: props.existingBucketObj, bucketProps: props.bucketProps }); // Create S3 trigger to invoke lambda function - this.fn.addEventSource(new S3EventSource(this.bucket, + this.lambdaFunction.addEventSource(new S3EventSource(this.s3Bucket, defaults.S3EventSourceProps(props.s3EventSourceProps))); this.addCfnNagSuppress(); @@ -108,7 +107,7 @@ export class S3ToLambda extends Construct { private addCfnNagSuppress() { // Extract the CfnBucket from the s3Bucket - const s3BucketResource = this.bucket.node.findChild('Resource') as s3.CfnBucket; + const s3BucketResource = this.s3Bucket.node.findChild('Resource') as s3.CfnBucket; s3BucketResource.cfnOptions.metadata = { cfn_nag: { @@ -149,24 +148,4 @@ export class S3ToLambda extends Construct { } }; } - - /** - * @summary Returns an instance of the lambda.Function created by the construct. - * @returns {lambda.Function} Instance of the Function created by the construct. - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of the s3.Bucket created by the construct. - * @returns {s3.Bucket} Instance of the Bucket created by the construct. - * @since 0.8.0 - * @access public - */ - public s3Bucket(): s3.Bucket { - return this.bucket; - } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/package.json similarity index 56% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/package.json index a0d85aad9..f609ad5c1 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-s3-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-s3-lambda", + "version": "1.46.0", "description": "CDK Constructs for AWS S3 to AWS Lambda integration", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-s3-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-s3-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,36 +34,36 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.s3lambda", + "package": "software.amazon.awsconstructs.services.s3lambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "s3lambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.S3Lambda", - "packageId": "Amazon.Konstruk.AWS.S3Lambda", + "namespace": "Amazon.Constructs.AWS.S3Lambda", + "packageId": "Amazon.Constructs.AWS.S3Lambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-s3-lambda", - "module": "aws_solutions_konstruk.aws_s3_lambda" + "distName": "aws-solutions-constructs.aws-s3-lambda", + "module": "aws_solutions_constructs.aws_s3_lambda" } } }, "dependencies": { - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/aws-s3-notifications": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/aws-s3-notifications": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -73,13 +73,13 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-s3-notifications": "~1.40.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-s3-notifications": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/__snapshots__/s3-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/__snapshots__/s3-lambda.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/__snapshots__/s3-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/__snapshots__/s3-lambda.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.ts b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.existing-s3-bucket.ts similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.ts rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.existing-s3-bucket.ts index d4cfa5665..8b906cdf8 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.ts +++ b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.existing-s3-bucket.ts @@ -16,7 +16,7 @@ import { App, Stack } from "@aws-cdk/core"; import { S3ToLambda, S3ToLambdaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; const app = new App(); // Empty arguments diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/s3-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/s3-lambda.test.ts similarity index 87% rename from source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/s3-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/s3-lambda.test.ts index 3533808a8..6967e69f8 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/s3-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-s3-lambda/test/s3-lambda.test.ts @@ -14,7 +14,6 @@ import { SynthUtils } from '@aws-cdk/assert'; import { S3ToLambda, S3ToLambdaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from "@aws-cdk/core"; import '@aws-cdk/assert/jest'; @@ -37,11 +36,11 @@ test('snapshot test S3ToLambda default params', () => { expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: S3ToLambda = deployNewFunc(stack); - expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); - expect(construct.s3Bucket()).toBeInstanceOf(s3.Bucket); + expect(construct.lambdaFunction !== null); + expect(construct.s3Bucket !== null); }); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.eslintignore rename to source/patterns/@aws-solutions-constructs/aws-s3-step-function/.eslintignore diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/.gitignore b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/.gitignore @@ -0,0 +1,16 @@ +lib/*.js +test/*.js +!test/lambda/* +*.js.map +*.d.ts +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-s3-step-function/.npmignore diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/README.md b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/README.md new file mode 100644 index 000000000..f8934f014 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/README.md @@ -0,0 +1,100 @@ +# aws-s3-step-function module + + +--- + +![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) + +> All classes are under active development and subject to non-backward compatible changes or removal in any +> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. +> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + +--- + + +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| +|:-------------|:-------------| +
+ +| **Language** | **Package** | +|:-------------|-----------------| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_s3_step_function`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-s3-step-function`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.s3stepfunction`| + +This AWS Solutions Construct implements an Amazon S3 bucket connected to an AWS Step Function. + +Here is a minimal deployable pattern definition: + +``` javascript +const { S3ToStepFunction, S3ToStepFunctionProps } = require('@aws-solutions-constructs/aws-s3-step-function'); + +const startState = new stepfunctions.Pass(stack, 'StartState'); + +const props: S3ToStepFunctionProps = { + stateMachineProps: { + definition: startState + } +}; + +new S3ToStepFunction(stack, 'test-s3-step-function-stack', props); +``` + +## Initializer + +``` text +new S3ToStepFunction(scope: Construct, id: string, props: S3ToStepFunctionProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`S3ToStepFunctionProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployBucket?|`boolean`|Whether to create a S3 Bucket or use an existing S3 Bucket| +|existingBucketObj?|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Existing instance of S3 Bucket object| +|bucketProps?|[`s3.BucketProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.BucketProps.html)|Optional user provided props to override the default props for S3 Bucket| +|stateMachineProps|[`sfn.StateMachineProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.StateMachineProps.html)|Optional user provided props to override the default props for sfn.StateMachine| +|eventRuleProps?|[`events.RuleProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-events.RuleProps.html)|Optional user provided eventRuleProps to override the defaults| +|deployCloudTrail?|`boolean`|Whether to deploy a Trail in AWS CloudTrail to log API events in Amazon S3| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|stateMachine|[`sfn.StateMachine`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-stepfunctions.StateMachine.html)|Returns an instance of sfn.StateMachine created by the construct| +|cloudwatchAlarms|[`cloudwatch.Alarm[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudwatch.Alarm.html)|Returns a list of cloudwatch.Alarm created by the construct| +|s3Bucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html)|Returns an instance of the s3.Bucket created by the construct| +|cloudtrail|[`cloudtrail.Trail`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudtrail.Trail.html)|Returns an instance of the cloudtrail.Trail created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon S3 Bucket +* Configure Access logging for S3 Bucket +* Enable server-side encryption for S3 Bucket using AWS managed KMS Key +* Turn on the versioning for S3 Bucket +* Don't allow public access for S3 Bucket +* Retain the S3 Bucket when deleting the CloudFormation stack + +### AWS CloudTrail +* Configure a Trail in AWS CloudTrail to log API events in Amazon S3 related to the Bucket created by the Construct + +### Amazon CloudWatch Events Rule +* Grant least privilege permissions to CloudWatch Events to trigger the Lambda Function + +### AWS Step Function +* Enable CloudWatch logging for API Gateway +* Deploy best practices CloudWatch Alarms for the Step Function + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/architecture.png b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..da37b98d66c1814220e3ccf84f94f3eafc25ae30 GIT binary patch literal 65426 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 F`Y-Zk-}(Rm literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/lib/index.ts new file mode 100644 index 000000000..4fc83e00f --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/lib/index.ts @@ -0,0 +1,160 @@ +/** + * 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. + */ + +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as defaults from '@aws-solutions-constructs/core'; +import { EventsRuleToStepFunction } from '@aws-solutions-constructs/aws-events-rule-step-function'; +import { Construct } from '@aws-cdk/core'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as cloudtrail from '@aws-cdk/aws-cloudtrail'; +import * as events from '@aws-cdk/aws-events'; + +/** + * @summary The properties for the S3ToStepFunction Construct + */ +export interface S3ToStepFunctionProps { + /** + * Whether to create a S3 Bucket or use an existing S3 Bucket. + * If set to false, you must provide S3 Bucket as `existingBucketObj` + * + * @default - true + */ + readonly deployBucket?: boolean, + /** + * Existing instance of S3 Bucket object. + * If `deployBucket` is set to false only then this property is required + * + * @default - None + */ + readonly existingBucketObj?: s3.Bucket, + /** + * Optional user provided props to override the default props. + * If `deploy` is set to true only then this property is required + * + * @default - Default props are used + */ + readonly bucketProps?: s3.BucketProps, + /** + * User provided StateMachineProps to override the defaults + * + * @default - None + */ + readonly stateMachineProps: sfn.StateMachineProps, + /** + * User provided eventRuleProps to override the defaults + * + * @default - None + */ + readonly eventRuleProps?: events.RuleProps; + /** + * Whether to deploy a Trail in AWS CloudTrail to log API events in Amazon S3 + * + * @default - true + */ + readonly deployCloudTrail?: boolean +} + +export class S3ToStepFunction extends Construct { + public readonly stateMachine: sfn.StateMachine; + public readonly s3Bucket: s3.Bucket; + public readonly cloudwatchAlarms: cloudwatch.Alarm[]; + public readonly cloudtrail: cloudtrail.Trail | undefined; + + /** + * @summary Constructs a new instance of the S3ToStepFunction class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {S3ToStepFunctionProps} props - user provided props for the construct + * @since 0.9.0 + * @access public + */ + constructor(scope: Construct, id: string, props: S3ToStepFunctionProps) { + super(scope, id); + + this.s3Bucket = defaults.buildS3Bucket(this, { + deployBucket: props.deployBucket, + existingBucketObj: props.existingBucketObj, + bucketProps: props.bucketProps + }); + + this.addCfnNagSuppress(this.s3Bucket); + + if (!props.hasOwnProperty('deployCloudTrail') || props.deployCloudTrail === true) { + const trailBucket = defaults.buildS3Bucket(this, { + deployBucket: true + }, 'CloudTrail'); + + this.addCfnNagSuppress(trailBucket); + + this.cloudtrail = new cloudtrail.Trail(this, 'S3EventsTrail', { + bucket: trailBucket + }); + + this.cloudtrail.addS3EventSelector([{ + bucket: this.s3Bucket + }], { + readWriteType: cloudtrail.ReadWriteType.ALL, + includeManagementEvents: false + }); + } + + let _eventRuleProps = {}; + if (props.eventRuleProps) { + _eventRuleProps = props.eventRuleProps; + } else { + // By default the CW Events Rule will filter any 's3:PutObject' events for the S3 Bucket + _eventRuleProps = { + eventPattern: { + source: ['aws.s3'], + detailType: ['AWS API Call via CloudTrail'], + detail: { + eventSource: [ + "s3.amazonaws.com" + ], + eventName: [ + "PutObject" + ], + requestParameters: { + bucketName: [ + this.s3Bucket.bucketName + ] + } + } + } + }; + } + + const eventsRuleToStepFunction = new EventsRuleToStepFunction(this, 'test-events-rule-step-function-stack', { + stateMachineProps: props.stateMachineProps, + eventRuleProps: _eventRuleProps + }); + + this.stateMachine = eventsRuleToStepFunction.stateMachine; + this.cloudwatchAlarms = eventsRuleToStepFunction.cloudwatchAlarms; + } + + private addCfnNagSuppress(bucket: s3.Bucket) { + // Extract the CfnBucket from the s3Bucket + const s3BucketResource = bucket.node.findChild('Resource') as s3.CfnBucket; + + s3BucketResource.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W51', + reason: `This S3 bucket Bucket does not need a bucket policy` + }] + } + }; + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/package.json b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/package.json new file mode 100644 index 000000000..abd6243c0 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/package.json @@ -0,0 +1,93 @@ +{ + "name": "@aws-solutions-constructs/aws-s3-step-function", + "version": "1.46.0", + "description": "CDK Constructs for AWS S3 to AWS Step Function integration", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-s3-step-function" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc -b .", + "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", + "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", + "test": "jest --coverage", + "clean": "tsc -b --clean", + "watch": "tsc -b -w", + "integ": "cdk-integ", + "integ-no-clean": "cdk-integ --no-clean", + "integ-assert": "cdk-integ-assert", + "jsii": "jsii", + "jsii-pacmak": "jsii-pacmak", + "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", + "snapshot-update": "npm run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.s3stepfunction", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "s3stepfunction" + } + }, + "dotnet": { + "namespace": "Amazon.Constructs.AWS.S3StepFunction", + "packageId": "Amazon.Constructs.AWS.S3StepFunction", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.aws-s3-step-function", + "module": "aws_solutions_constructs.aws_s3_step_function" + } + } + }, + "dependencies": { + "@aws-cdk/aws-stepfunctions": "~1.46.0", + "@aws-cdk/aws-stepfunctions-tasks": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-cdk/aws-cloudtrail": "~1.46.0", + "@aws-cdk/aws-events": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-events-rule-step-function": "~1.46.0", + "constructs": "^3.0.2" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.46.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-stepfunctions": "~1.46.0", + "@aws-cdk/aws-stepfunctions-tasks": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-cdk/aws-cloudtrail": "~1.46.0", + "@aws-cdk/aws-events": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-solutions-constructs/aws-events-rule-step-function": "~1.46.0", + "constructs": "^3.0.2" + } +} diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/__snapshots__/s3-step-function.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/__snapshots__/s3-step-function.test.js.snap new file mode 100644 index 000000000..8a6d56d65 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/__snapshots__/s3-step-function.test.js.snap @@ -0,0 +1,537 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test S3ToStepFunction default params 1`] = ` +Object { + "Resources": Object { + "tests3stepfunctionCloudTrailS3Bucket826F62C0": Object { + "DeletionPolicy": "Retain", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W51", + "reason": "This S3 bucket Bucket does not need a bucket policy", + }, + ], + }, + }, + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "LoggingConfiguration": Object { + "DestinationBucketName": Object { + "Ref": "tests3stepfunctionCloudTrailS3LoggingBucket4FA5C122", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "tests3stepfunctionCloudTrailS3BucketPolicyA41AE459": Object { + "Properties": Object { + "Bucket": Object { + "Ref": "tests3stepfunctionCloudTrailS3Bucket826F62C0", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:GetBucketAcl", + "Effect": "Allow", + "Principal": Object { + "Service": "cloudtrail.amazonaws.com", + }, + "Resource": Object { + "Fn::GetAtt": Array [ + "tests3stepfunctionCloudTrailS3Bucket826F62C0", + "Arn", + ], + }, + }, + Object { + "Action": "s3:PutObject", + "Condition": Object { + "StringEquals": Object { + "s3:x-amz-acl": "bucket-owner-full-control", + }, + }, + "Effect": "Allow", + "Principal": Object { + "Service": "cloudtrail.amazonaws.com", + }, + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "tests3stepfunctionCloudTrailS3Bucket826F62C0", + "Arn", + ], + }, + "/AWSLogs/", + Object { + "Ref": "AWS::AccountId", + }, + "/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + "tests3stepfunctionCloudTrailS3LoggingBucket4FA5C122": Object { + "DeletionPolicy": "Retain", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W35", + "reason": "This S3 bucket is used as the access logging bucket for another bucket", + }, + Object { + "id": "W51", + "reason": "This S3 bucket Bucket does not need a bucket policy", + }, + ], + }, + }, + "Properties": Object { + "AccessControl": "LogDeliveryWrite", + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "tests3stepfunctionS3Bucket57D6600C": Object { + "DeletionPolicy": "Retain", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W51", + "reason": "This S3 bucket Bucket does not need a bucket policy", + }, + ], + }, + }, + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "LoggingConfiguration": Object { + "DestinationBucketName": Object { + "Ref": "tests3stepfunctionS3LoggingBucketF58651AD", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "tests3stepfunctionS3EventsTrail4ECD93D3": Object { + "DependsOn": Array [ + "tests3stepfunctionCloudTrailS3BucketPolicyA41AE459", + ], + "Properties": Object { + "EnableLogFileValidation": true, + "EventSelectors": Array [ + Object { + "DataResources": Array [ + Object { + "Type": "AWS::S3::Object", + "Values": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "tests3stepfunctionS3Bucket57D6600C", + "Arn", + ], + }, + "/", + ], + ], + }, + ], + }, + ], + "IncludeManagementEvents": false, + "ReadWriteType": "All", + }, + ], + "IncludeGlobalServiceEvents": true, + "IsLogging": true, + "IsMultiRegionTrail": true, + "S3BucketName": Object { + "Ref": "tests3stepfunctionCloudTrailS3Bucket826F62C0", + }, + }, + "Type": "AWS::CloudTrail::Trail", + }, + "tests3stepfunctionS3LoggingBucketF58651AD": Object { + "DeletionPolicy": "Retain", + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W35", + "reason": "This S3 bucket is used as the access logging bucket for another bucket", + }, + Object { + "id": "W51", + "reason": "This S3 bucket Bucket does not need a bucket policy", + }, + ], + }, + }, + "Properties": Object { + "AccessControl": "LogDeliveryWrite", + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackEventsRule05BF80D3": Object { + "Properties": Object { + "EventPattern": Object { + "detail": Object { + "eventName": Array [ + "PutObject", + ], + "eventSource": Array [ + "s3.amazonaws.com", + ], + "requestParameters": Object { + "bucketName": Array [ + Object { + "Ref": "tests3stepfunctionS3Bucket57D6600C", + }, + ], + }, + }, + "detail-type": Array [ + "AWS API Call via CloudTrail", + ], + "source": Array [ + "aws.s3", + ], + }, + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Ref": "tests3stepfunctiontesteventsrulestepfunctionstackStateMachine5A6C0DFF", + }, + "Id": "Target0", + "RoleArn": Object { + "Fn::GetAtt": Array [ + "tests3stepfunctiontesteventsrulestepfunctionstackEventsRuleRoleF447A174", + "Arn", + ], + }, + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackEventsRuleRoleDefaultPolicy9B31B120": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": Object { + "Ref": "tests3stepfunctiontesteventsrulestepfunctionstackStateMachine5A6C0DFF", + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "tests3stepfunctiontesteventsrulestepfunctionstackEventsRuleRoleDefaultPolicy9B31B120", + "Roles": Array [ + Object { + "Ref": "tests3stepfunctiontesteventsrulestepfunctionstackEventsRuleRoleF447A174", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackEventsRuleRoleF447A174": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "events.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackExecutionAbortedAlarm2467E717": Object { + "Properties": Object { + "AlarmDescription": "Alarm for the number of executions that aborted exceeded the threshold of 1. ", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": Array [ + Object { + "Name": "StateMachineArn", + "Value": Object { + "Ref": "tests3stepfunctiontesteventsrulestepfunctionstackStateMachine5A6C0DFF", + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ExecutionsAborted", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackExecutionFailedAlarmABAAA96A": Object { + "Properties": Object { + "AlarmDescription": "Alarm for the number of executions that failed exceeded the threshold of 1. ", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": Array [ + Object { + "Name": "StateMachineArn", + "Value": Object { + "Ref": "tests3stepfunctiontesteventsrulestepfunctionstackStateMachine5A6C0DFF", + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ExecutionsFailed", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackExecutionThrottledAlarm1D666C22": Object { + "Properties": Object { + "AlarmDescription": "Alarm for the number of executions that throttled exceeded the threshold of 1. ", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": Array [ + Object { + "Name": "StateMachineArn", + "Value": Object { + "Ref": "tests3stepfunctiontesteventsrulestepfunctionstackStateMachine5A6C0DFF", + }, + }, + ], + "EvaluationPeriods": 1, + "MetricName": "ExecutionThrottled", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackStateMachine5A6C0DFF": Object { + "DependsOn": Array [ + "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineRoleDefaultPolicy4B8FDFDA", + "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineRole9C9F9AAC", + ], + "Properties": Object { + "DefinitionString": "{\\"StartAt\\":\\"StartState\\",\\"States\\":{\\"StartState\\":{\\"Type\\":\\"Pass\\",\\"End\\":true}}}", + "LoggingConfiguration": Object { + "Destinations": Array [ + Object { + "CloudWatchLogsLogGroup": Object { + "LogGroupArn": Object { + "Fn::GetAtt": Array [ + "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineLogGroupDA892B18", + "Arn", + ], + }, + }, + }, + ], + "Level": "ERROR", + }, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineRole9C9F9AAC", + "Arn", + ], + }, + }, + "Type": "AWS::StepFunctions::StateMachine", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineLogGroupDA892B18": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineRole9C9F9AAC": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": Object { + "Fn::Join": Array [ + "", + Array [ + "states.", + Object { + "Ref": "AWS::Region", + }, + ".amazonaws.com", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineRoleDefaultPolicy4B8FDFDA": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "The 'LogDelivery' actions do not support resource-level authorizations", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries", + ], + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": Array [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + "logs:DescribeLogGroups", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineRoleDefaultPolicy4B8FDFDA", + "Roles": Array [ + Object { + "Ref": "tests3stepfunctiontesteventsrulestepfunctionstackStateMachineRole9C9F9AAC", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/integ.s3-step-function-no-argument.expected.json b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/integ.s3-step-function-no-argument.expected.json new file mode 100644 index 000000000..29ef32f0f --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/integ.s3-step-function-no-argument.expected.json @@ -0,0 +1,533 @@ +{ + "Resources": { + "tests3stepfunctionstackS3LoggingBucket740A14C5": { + "Type": "AWS::S3::Bucket", + "Properties": { + "AccessControl": "LogDeliveryWrite", + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W35", + "reason": "This S3 bucket is used as the access logging bucket for another bucket" + }, + { + "id": "W51", + "reason": "This S3 bucket Bucket does not need a bucket policy" + } + ] + } + } + }, + "tests3stepfunctionstackS3Bucket8CC704E9": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "tests3stepfunctionstackS3LoggingBucket740A14C5" + } + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W51", + "reason": "This S3 bucket Bucket does not need a bucket policy" + } + ] + } + } + }, + "tests3stepfunctionstackCloudTrailS3LoggingBucketC8E8D35B": { + "Type": "AWS::S3::Bucket", + "Properties": { + "AccessControl": "LogDeliveryWrite", + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W35", + "reason": "This S3 bucket is used as the access logging bucket for another bucket" + }, + { + "id": "W51", + "reason": "This S3 bucket Bucket does not need a bucket policy" + } + ] + } + } + }, + "tests3stepfunctionstackCloudTrailS3Bucket9CD2A45F": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "tests3stepfunctionstackCloudTrailS3LoggingBucketC8E8D35B" + } + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W51", + "reason": "This S3 bucket Bucket does not need a bucket policy" + } + ] + } + } + }, + "tests3stepfunctionstackCloudTrailS3BucketPolicyE88DD0A5": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "tests3stepfunctionstackCloudTrailS3Bucket9CD2A45F" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:GetBucketAcl", + "Effect": "Allow", + "Principal": { + "Service": "cloudtrail.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "tests3stepfunctionstackCloudTrailS3Bucket9CD2A45F", + "Arn" + ] + } + }, + { + "Action": "s3:PutObject", + "Condition": { + "StringEquals": { + "s3:x-amz-acl": "bucket-owner-full-control" + } + }, + "Effect": "Allow", + "Principal": { + "Service": "cloudtrail.amazonaws.com" + }, + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "tests3stepfunctionstackCloudTrailS3Bucket9CD2A45F", + "Arn" + ] + }, + "/AWSLogs/", + { + "Ref": "AWS::AccountId" + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + } + } + }, + "tests3stepfunctionstackS3EventsTrailE9BE674D": { + "Type": "AWS::CloudTrail::Trail", + "Properties": { + "IsLogging": true, + "S3BucketName": { + "Ref": "tests3stepfunctionstackCloudTrailS3Bucket9CD2A45F" + }, + "EnableLogFileValidation": true, + "EventSelectors": [ + { + "DataResources": [ + { + "Type": "AWS::S3::Object", + "Values": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "tests3stepfunctionstackS3Bucket8CC704E9", + "Arn" + ] + }, + "/" + ] + ] + } + ] + } + ], + "IncludeManagementEvents": false, + "ReadWriteType": "All" + } + ], + "IncludeGlobalServiceEvents": true, + "IsMultiRegionTrail": true + }, + "DependsOn": [ + "tests3stepfunctionstackCloudTrailS3BucketPolicyE88DD0A5" + ] + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineLogGroupB72DF7A1": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineRoleC204E28A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineRoleDefaultPolicyCF5075D2": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + "logs:DescribeLogGroups" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineRoleDefaultPolicyCF5075D2", + "Roles": [ + { + "Ref": "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineRoleC204E28A" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "The 'LogDelivery' actions do not support resource-level authorizations" + } + ] + } + } + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachine03BA781C": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": "{\"StartAt\":\"StartState\",\"States\":{\"StartState\":{\"Type\":\"Pass\",\"End\":true}}}", + "RoleArn": { + "Fn::GetAtt": [ + "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineRoleC204E28A", + "Arn" + ] + }, + "LoggingConfiguration": { + "Destinations": [ + { + "CloudWatchLogsLogGroup": { + "LogGroupArn": { + "Fn::GetAtt": [ + "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineLogGroupB72DF7A1", + "Arn" + ] + } + } + } + ], + "Level": "ERROR" + } + }, + "DependsOn": [ + "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineRoleDefaultPolicyCF5075D2", + "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachineRoleC204E28A" + ] + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackEventsRuleRole7F5DCB98": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackEventsRuleRoleDefaultPolicy7B926713": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Effect": "Allow", + "Resource": { + "Ref": "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachine03BA781C" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "tests3stepfunctionstacktesteventsrulestepfunctionstackEventsRuleRoleDefaultPolicy7B926713", + "Roles": [ + { + "Ref": "tests3stepfunctionstacktesteventsrulestepfunctionstackEventsRuleRole7F5DCB98" + } + ] + } + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackEventsRule617230F2": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.s3" + ], + "detail-type": [ + "AWS API Call via CloudTrail" + ], + "detail": { + "eventSource": [ + "s3.amazonaws.com" + ], + "eventName": [ + "PutObject" + ], + "requestParameters": { + "bucketName": [ + { + "Ref": "tests3stepfunctionstackS3Bucket8CC704E9" + } + ] + } + } + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Ref": "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachine03BA781C" + }, + "Id": "Target0", + "RoleArn": { + "Fn::GetAtt": [ + "tests3stepfunctionstacktesteventsrulestepfunctionstackEventsRuleRole7F5DCB98", + "Arn" + ] + } + } + ] + } + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackExecutionFailedAlarmFB9B3517": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that failed exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachine03BA781C" + } + } + ], + "MetricName": "ExecutionsFailed", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1 + } + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackExecutionThrottledAlarmF000208D": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that throttled exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachine03BA781C" + } + } + ], + "MetricName": "ExecutionThrottled", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1 + } + }, + "tests3stepfunctionstacktesteventsrulestepfunctionstackExecutionAbortedAlarmE5C0507E": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Alarm for the number of executions that aborted exceeded the threshold of 1. ", + "Dimensions": [ + { + "Name": "StateMachineArn", + "Value": { + "Ref": "tests3stepfunctionstacktesteventsrulestepfunctionstackStateMachine03BA781C" + } + } + ], + "MetricName": "ExecutionsAborted", + "Namespace": "AWS/States", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/integ.s3-step-function-no-argument.ts b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/integ.s3-step-function-no-argument.ts new file mode 100644 index 000000000..b2f816329 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/integ.s3-step-function-no-argument.ts @@ -0,0 +1,31 @@ +/** + * 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 { S3ToStepFunction, S3ToStepFunctionProps } from "../lib"; +import * as stepfunctions from '@aws-cdk/aws-stepfunctions'; + +const app = new App(); +const stack = new Stack(app, 'test-s3-step-function-stack'); + +const startState = new stepfunctions.Pass(stack, 'StartState'); + +const props: S3ToStepFunctionProps = { + stateMachineProps: { + definition: startState + } +}; + +new S3ToStepFunction(stack, 'test-s3-step-function-stack', props); +app.synth(); diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/lambda/index.js new file mode 100644 index 000000000..4b3640c1e --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/lambda/index.js @@ -0,0 +1,10 @@ +console.log('Loading function'); + +exports.handler = async (event, context) => { + console.log('Received event:', JSON.stringify(event, null, 2)); +    return { +      statusCode: 200, +      headers: { 'Content-Type': 'text/plain' }, +      body: `Hello from Project Vesper! You've hit ${event.path}\n` +    }; +}; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/s3-step-function.test.ts b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/s3-step-function.test.ts new file mode 100644 index 000000000..48ee58186 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-s3-step-function/test/s3-step-function.test.ts @@ -0,0 +1,143 @@ +/** + * 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. + */ + +import { SynthUtils } from '@aws-cdk/assert'; +import { S3ToStepFunction, S3ToStepFunctionProps } from '../lib/index'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import { Bucket } from '@aws-cdk/aws-s3'; + +function deployNewStateMachine(stack: cdk.Stack) { + + const startState = new sfn.Pass(stack, 'StartState'); + + const props: S3ToStepFunctionProps = { + stateMachineProps: { + definition: startState + } + }; + + return new S3ToStepFunction(stack, 'test-s3-step-function', props); +} + +test('snapshot test S3ToStepFunction default params', () => { + const stack = new cdk.Stack(); + deployNewStateMachine(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check deployCloudTrail = false', () => { + const stack = new cdk.Stack(); + + const startState = new sfn.Pass(stack, 'StartState'); + + const props: S3ToStepFunctionProps = { + stateMachineProps: { + definition: startState + }, + deployCloudTrail: false + }; + + const construct = new S3ToStepFunction(stack, 'test-s3-step-function', props); + + expect(construct.cloudtrail === undefined); +}); + +test('override eventRuleProps', () => { + const stack = new cdk.Stack(); + + const mybucket = new Bucket(stack, 'mybucket'); + const startState = new sfn.Pass(stack, 'StartState'); + + const props: S3ToStepFunctionProps = { + stateMachineProps: { + definition: startState + }, + deployBucket: false, + existingBucketObj: mybucket, + eventRuleProps: { + eventPattern: { + source: ['aws.s3'], + detailType: ['AWS API Call via CloudTrail'], + detail: { + eventSource: [ + "s3.amazonaws.com" + ], + eventName: [ + "GetObject" + ], + requestParameters: { + bucketName: [ + mybucket.bucketName + ] + } + } + } + } + }; + + new S3ToStepFunction(stack, 'test-s3-step-function', props); + + expect(stack).toHaveResource('AWS::Events::Rule', { + EventPattern: { + "source": [ + "aws.s3" + ], + "detail-type": [ + "AWS API Call via CloudTrail" + ], + "detail": { + eventSource: [ + "s3.amazonaws.com" + ], + eventName: [ + "GetObject" + ], + requestParameters: { + bucketName: [ + { + Ref: "mybucket160F8132" + } + ] + } + } + }, + State: "ENABLED", + Targets: [ + { + Arn: { + Ref: "tests3stepfunctiontesteventsrulestepfunctionstackStateMachine5A6C0DFF" + }, + Id: "Target0", + RoleArn: { + "Fn::GetAtt": [ + "tests3stepfunctiontesteventsrulestepfunctionstackEventsRuleRoleF447A174", + "Arn" + ] + } + } + ] + }); +}); + +test('check properties', () => { + const stack = new cdk.Stack(); + + const construct: S3ToStepFunction = deployNewStateMachine(stack); + + expect(construct.cloudtrail !== null); + expect(construct.stateMachine !== null); + expect(construct.s3Bucket !== null); + expect(construct.cloudwatchAlarms !== null); +}); diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/.eslintignore @@ -0,0 +1,5 @@ +lib/*.js +test/*.js +*.d.ts +coverage +test/lambda/index.js \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-sns-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/.gitignore @@ -0,0 +1,16 @@ +lib/*.js +test/*.js +!test/lambda/* +*.js.map +*.d.ts +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/.npmignore b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/.npmignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/.npmignore rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/.npmignore diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/README.md similarity index 70% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/README.md index 5fc948a44..4ebb008e2 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-sns-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_sns_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-sns-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_sns_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-sns-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.snslambda`| -This AWS Solutions Konstruk implements an Amazon SNS connected to an AWS Lambda function. +This AWS Solutions Construct implements an Amazon SNS connected to an AWS Lambda function. Here is a minimal deployable pattern definition: ``` javascript -const { SnsToLambdaProps, SnsToLambda } = require('@aws-solutions-konstruk/aws-sns-lambda'); +const { SnsToLambdaProps, SnsToLambda } = require('@aws-solutions-constructs/aws-sns-lambda'); const stack = new Stack(app, 'test-sns-lambda'); @@ -73,8 +72,20 @@ _Parameters_ | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| -|snsTopic()|[`sns.Topic`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.Topic.html)|Returns an instance of the SNS topic created by the pattern.| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| +|snsTopic|[`sns.Topic`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.Topic.html)|Returns an instance of the SNS topic created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon SNS Topic +* Configure least privilege access permissions for SNS Topic +* Enable server-side encryption forSNS Topic using Customer managed KMS Key + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/lib/index.ts similarity index 78% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/lib/index.ts index 8d78984e5..1a7adc352 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/lib/index.ts @@ -14,7 +14,7 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as kms from '@aws-cdk/aws-kms'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; import { SnsEventSource } from '@aws-cdk/aws-lambda-event-sources'; @@ -68,9 +68,8 @@ export interface SnsToLambdaProps { * @summary The SnsToLambda class. */ export class SnsToLambda extends Construct { - // Private variables - private fn: lambda.Function; - private topic: sns.Topic; + public readonly lambdaFunction: lambda.Function; + public readonly snsTopic: sns.Topic; /** * @summary Constructs a new instance of the LambdaToSns class. @@ -84,39 +83,18 @@ export class SnsToLambda extends Construct { super(scope, id); // Setup the Lambda function - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps }); // Setup the SNS topic - this.topic = defaults.buildTopic(this, { + this.snsTopic = defaults.buildTopic(this, { enableEncryption: props.enableEncryption, encryptionKey: props.encryptionKey }); - this.fn.addEventSource(new SnsEventSource(this.topic)); - - } - - /** - * @summary Returns an instance of the lambda.Function created by the construct. - * @returns {lambda.Function} Instance of the Function created by the construct. - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of the sns.Topic created by the construct. - * @returns {sns.Topic} Instance of the Topic created by the construct. - * @since 0.8.0 - * @access public - */ - public snsTopic(): sns.Topic { - return this.topic; + this.lambdaFunction.addEventSource(new SnsEventSource(this.snsTopic)); } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/package.json similarity index 56% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/package.json index 2d319da18..91f377ca5 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-sns-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-sns-lambda", + "version": "1.46.0", "description": "CDK Constructs for AWS SNS to AWS Lambda integration", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-sns-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-sns-lambda" }, "author": { "name": "Amazon Web Services", @@ -34,36 +34,36 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.snslambda", + "package": "software.amazon.awsconstructs.services.snslambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "snslambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.SnsLambda", - "packageId": "Amazon.Konstruk.AWS.SnsLambda", + "namespace": "Amazon.Constructs.AWS.SnsLambda", + "packageId": "Amazon.Constructs.AWS.SnsLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-sns-lambda", - "module": "aws_solutions_konstruk.aws_sns_lambda" + "distName": "aws-solutions-constructs.aws-sns-lambda", + "module": "aws_solutions_constructs.aws_sns_lambda" } } }, "dependencies": { - "@aws-cdk/aws-sns": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-sns": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -73,13 +73,13 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-sns": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-cdk/aws-kms": "~1.40.0", + "@aws-cdk/aws-sns": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/integ.no-arguments.expected.json similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.expected.json rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/integ.no-arguments.expected.json diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/integ.no-arguments.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.ts rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/integ.no-arguments.ts diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/sns-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/sns-lambda.test.ts similarity index 87% rename from source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/sns-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/sns-lambda.test.ts index 3db6f010e..bd3265fae 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/sns-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-sns-lambda/test/sns-lambda.test.ts @@ -16,7 +16,6 @@ import { SnsToLambda, SnsToLambdaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from "@aws-cdk/core"; import '@aws-cdk/assert/jest'; -import { Topic } from '@aws-cdk/aws-sns'; function deployNewFunc(stack: cdk.Stack) { const props: SnsToLambdaProps = { @@ -37,11 +36,11 @@ test('snapshot test SnsToLambda default params', () => { expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); }); -test('check getter methods', () => { +test('check properties', () => { const stack = new cdk.Stack(); const construct: SnsToLambda = deployNewFunc(stack); - expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); - expect(construct.snsTopic()).toBeInstanceOf(Topic); + expect(construct.lambdaFunction !== null); + expect(construct.snsTopic !== null); }); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.eslintignore @@ -0,0 +1,5 @@ +lib/*.js +test/*.js +*.d.ts +coverage +test/lambda/index.js \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.gitignore b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.gitignore rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.gitignore diff --git a/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.npmignore b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/.npmignore @@ -0,0 +1,21 @@ +# Exclude typescript source and config +*.ts +tsconfig.json +coverage +.nyc_output +*.tgz +*.snk +*.tsbuildinfo + +# Include javascript files and typescript declarations +!*.js +!*.d.ts + +# Exclude jsii outdir +dist + +# Include .jsii +!.jsii + +# Include .jsii +!.jsii \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/README.md b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/README.md similarity index 64% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/README.md rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/README.md index 91330d7b9..505ba9453 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/README.md @@ -5,8 +5,6 @@ ![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -> **This is a _developer preview_ (public beta) module.** -> > All classes are under active development and subject to non-backward compatible changes or removal in any > future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. > This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. @@ -14,21 +12,22 @@ --- -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/aws-sqs-lambda/| +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| |:-------------|:-------------|
| **Language** | **Package** | |:-------------|-----------------| -|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png){: style="height:16px;width:16px"} Python|`aws_solutions_konstruk.aws_sns_lambda`| -|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png){: style="height:16px;width:16px"} Typescript|`@aws-solutions-konstruk/aws-sns-lambda`| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_sns_lambda`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-sns-lambda`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.sqslambda`| -This AWS Solutions Konstruk implements an Amazon SQS queue connected to an AWS Lambda function. +This AWS Solutions Construct implements an Amazon SQS queue connected to an AWS Lambda function. Here is a minimal deployable pattern definition: ``` javascript -const { SqsToLambda } = require('@aws-solutions-konstruk/aws-sqs-lambda'); +const { SqsToLambda } = require('@aws-solutions-constructs/aws-sqs-lambda'); new SqsToLambda(stack, 'SqsToLambdaPattern', { deployLambda: true, @@ -61,16 +60,27 @@ _Parameters_ |existingLambdaObj?|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|An optional, existing Lambda function.| |lambdaFunctionProps?|[`lambda.FunctionProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.FunctionProps.html)|Optional user-provided props to override the default props for the Lambda function.| |queueProps?|[`sqs.QueueProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.QueueProps.html)|Optional user-provided props to override the default props for the SQS queue.| -|encryptionKeyProps?|[`kms.KeyProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.KeyProps.html)|Optional user-provided props to override the default props for the KMS encryption key.| |deployDeadLetterQueue?|`boolean`|Whether to create a secondary queue to be used as a dead letter queue. Defaults to true.| -|maxReceiveCount?|`number`|The number of times a message can be unsuccesfully dequeued before being moved to the dead letter queue. Defaults to 15.| +|maxReceiveCount?|`number`|The number of times a message can be unsuccessfully dequeued before being moved to the dead letter queue. Defaults to 15.| ## Pattern Properties | **Name** | **Type** | **Description** | |:-------------|:----------------|-----------------| -|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| -|sqsQueue()|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html)|Returns an instance of the SQS queue created by the pattern.| +|lambdaFunction|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Returns an instance of the Lambda function created by the pattern.| +|sqsQueue|[`sqs.Queue`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sqs.Queue.html)|Returns an instance of the SQS queue created by the pattern.| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### Amazon SQS Queue +* Deploy SQS dead-letter queue for the source SQS Queue +* Enable server-side encryption for source SQS Queue using AWS Managed KMS Key + +### AWS Lambda Function +* Configure least privilege access IAM role for Lambda function +* Enable reusing connections with Keep-Alive for NodeJs Lambda function ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/architecture.png b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/architecture.png similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/architecture.png rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/architecture.png diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/lib/index.ts similarity index 70% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/lib/index.ts rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/lib/index.ts index 96342ac68..e5feec38a 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/lib/index.ts @@ -14,8 +14,7 @@ // Imports import * as sqs from '@aws-cdk/aws-sqs'; import * as lambda from '@aws-cdk/aws-lambda'; -import * as kms from '@aws-cdk/aws-kms'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources'; @@ -50,12 +49,6 @@ export interface SqsToLambdaProps { * @default - Default props are used */ readonly queueProps?: sqs.QueueProps | any - /** - * Optional user provided props to override the default props for the KMS. - * - * @default - Default props are used - */ - readonly encryptionKeyProps?: kms.KeyProps | any /** * Whether to deploy a secondary queue to be used as a dead letter queue. * @@ -63,7 +56,7 @@ export interface SqsToLambdaProps { */ readonly deployDeadLetterQueue?: boolean, /** - * The number of times a message can be unsuccesfully dequeued before being moved to the dead-letter queue. + * The number of times a message can be unsuccessfully dequeued before being moved to the dead-letter queue. * * @default - required field if deployDeadLetterQueue=true. */ @@ -74,10 +67,8 @@ export interface SqsToLambdaProps { * @summary The SqsToLambda class. */ export class SqsToLambda extends Construct { - // Private variables - private queue: sqs.Queue; - private fn: lambda.Function; - private encryptionKey: kms.Key; + public readonly sqsQueue: sqs.Queue; + public readonly lambdaFunction: lambda.Function; /** * @summary Constructs a new instance of the SqsToLambda class. @@ -90,11 +81,8 @@ export class SqsToLambda extends Construct { constructor(scope: Construct, id: string, props: SqsToLambdaProps) { super(scope, id); - // Setup the encryption key - this.encryptionKey = defaults.buildEncryptionKey(this, props.encryptionKeyProps); - // Setup the Lambda function - this.fn = defaults.buildLambdaFunction(this, { + this.lambdaFunction = defaults.buildLambdaFunction(this, { deployLambda: props.deployLambda, existingLambdaObj: props.existingLambdaObj, lambdaFunctionProps: props.lambdaFunctionProps @@ -104,7 +92,6 @@ export class SqsToLambda extends Construct { let dlqi: sqs.DeadLetterQueue | undefined; if (props.deployDeadLetterQueue || props.deployDeadLetterQueue === undefined) { const dlq: sqs.Queue = defaults.buildQueue(this, 'deadLetterQueue', { - encryptionKey: this.encryptionKey, queueProps: props.queueProps }); dlqi = defaults.buildDeadLetterQueue({ @@ -114,33 +101,12 @@ export class SqsToLambda extends Construct { } // Setup the queue - this.queue = defaults.buildQueue(this, 'queue', { - encryptionKey: this.encryptionKey, + this.sqsQueue = defaults.buildQueue(this, 'queue', { queueProps: props.queueProps, deadLetterQueue: dlqi }); // Setup the event source mapping - this.fn.addEventSource(new SqsEventSource(this.queue)); - } - - /** - * @summary Returns an instance of the lambda.Function created by the construct. - * @returns {lambda.Function} Instance of the Function created by the construct. - * @since 0.8.0 - * @access public - */ - public lambdaFunction(): lambda.Function { - return this.fn; - } - - /** - * @summary Returns an instance of the sqs.Queue created by the construct. - * @returns {sqs.Queue} Instance of the Queue created by the construct. - * @since 0.8.0 - * @access public - */ - public sqsQueue(): sqs.Queue { - return this.queue; + this.lambdaFunction.addEventSource(new SqsEventSource(this.sqsQueue)); } } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/package.json b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/package.json similarity index 57% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/package.json rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/package.json index c424739a3..db8398901 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-solutions-konstruk/aws-sqs-lambda", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-sqs-lambda", + "version": "1.46.0", "description": "CDK constructs for defining an interaction between an Amazon SQS queue and an AWS Lambda function.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-sqs-lambda" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-sqs-lambda" }, "author": { "name": "Amazon Web Services", @@ -24,6 +24,7 @@ "watch": "tsc -b -w", "integ": "cdk-integ", "integ-assert": "cdk-integ-assert", + "integ-no-clean": "cdk-integ --no-clean", "jsii": "jsii", "jsii-pacmak": "jsii-pacmak", "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", @@ -33,35 +34,35 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.sqslambda", + "package": "software.amazon.awsconstructs.services.sqslambda", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "sqslambda" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.SqsLambda", - "packageId": "Amazon.Konstruk.AWS.SqsLambda", + "namespace": "Amazon.Constructs.AWS.SqsLambda", + "packageId": "Amazon.Constructs.AWS.SqsLambda", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-solutions-konstruk.aws-sqs-lambda", - "module": "aws_solutions_konstruk.aws_sqs_lambda" + "distName": "aws-solutions-constructs.aws-sqs-lambda", + "module": "aws_solutions_constructs.aws_sqs_lambda" } } }, "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-sqs": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-sqs": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -71,12 +72,12 @@ ] }, "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-sqs": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-sqs": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "constructs": "^3.0.2" } } diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap similarity index 70% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap index 9d6935a2d..47e41e820 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap @@ -123,16 +123,6 @@ Object { ], }, }, - Object { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": Object { - "Fn::GetAtt": Array [ - "testapigatewaylambdaEncryptionKey06FC467F", - "Arn", - ], - }, - }, ], "Version": "2012-10-17", }, @@ -159,79 +149,9 @@ Object { }, "Type": "AWS::Lambda::EventSourceMapping", }, - "testapigatewaylambdaEncryptionKey06FC467F": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "EnableKeyRotation": true, - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - Object { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::GetAtt": Array [ - "ExistingLambdaFunctionServiceRole7CC6DE65", - "Arn", - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, "testapigatewaylambdaqueue1FFAE03C": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "testapigatewaylambdaEncryptionKey06FC467F", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", }, "Type": "AWS::SQS::Queue", }, @@ -256,71 +176,6 @@ Object { }, }, "Resources": Object { - "testsqslambdaEncryptionKey317A2F03": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "EnableKeyRotation": true, - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - Object { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::GetAtt": Array [ - "testsqslambdaLambdaFunctionServiceRoleF623B438", - "Arn", - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, "testsqslambdaLambdaFunction58720146": Object { "DependsOn": Array [ "testsqslambdaLambdaFunctionServiceRoleDefaultPolicy380B065C", @@ -411,16 +266,6 @@ Object { ], }, }, - Object { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": Object { - "Fn::GetAtt": Array [ - "testsqslambdaEncryptionKey317A2F03", - "Arn", - ], - }, - }, ], "Version": "2012-10-17", }, @@ -500,23 +345,13 @@ Object { }, "testsqslambdadeadLetterQueue85BDB0A3": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "testsqslambdaEncryptionKey317A2F03", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", }, "Type": "AWS::SQS::Queue", }, "testsqslambdaqueue601203B8": Object { "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "testsqslambdaEncryptionKey317A2F03", - "Arn", - ], - }, + "KmsMasterKeyId": "alias/aws/sqs", "RedrivePolicy": Object { "deadLetterTargetArn": Object { "Fn::GetAtt": Array [ diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.deployFunction.expected.json similarity index 72% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.deployFunction.expected.json index 790d01994..f4ef34d88 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.deployFunction.expected.json @@ -1,71 +1,6 @@ { "Description": "Integration Test for aws-sqs-lambda", "Resources": { - "testsqslambdaEncryptionKey317A2F03": { - "Type": "AWS::KMS::Key", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" - ] - ] - } - }, - "Resource": "*" - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "testsqslambdaLambdaFunctionServiceRoleF623B438", - "Arn" - ] - } - }, - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "EnableKeyRotation": true - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, "testsqslambdaLambdaFunctionServiceRoleF623B438": { "Type": "AWS::IAM::Role", "Properties": { @@ -137,16 +72,6 @@ "Arn" ] } - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "testsqslambdaEncryptionKey317A2F03", - "Arn" - ] - } } ], "Version": "2012-10-17" @@ -246,23 +171,13 @@ "testsqslambdadeadLetterQueue85BDB0A3": { "Type": "AWS::SQS::Queue", "Properties": { - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "testsqslambdaEncryptionKey317A2F03", - "Arn" - ] - } + "KmsMasterKeyId": "alias/aws/sqs" } }, "testsqslambdaqueue601203B8": { "Type": "AWS::SQS::Queue", "Properties": { - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "testsqslambdaEncryptionKey317A2F03", - "Arn" - ] - }, + "KmsMasterKeyId": "alias/aws/sqs", "RedrivePolicy": { "deadLetterTargetArn": { "Fn::GetAtt": [ diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.deployFunction.ts similarity index 96% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.deployFunction.ts index 6858f97c5..cacd982b4 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.ts +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.deployFunction.ts @@ -31,8 +31,7 @@ const props: SqsToLambdaProps = { }, queueProps: {}, deployDeadLetterQueue: true, - maxReceiveCount: 3, - encryptionKeyProps: {} + maxReceiveCount: 3 }; new SqsToLambda(stack, 'test-sqs-lambda', props); diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.expected.json b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.existingFunction.expected.json similarity index 72% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.expected.json rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.existingFunction.expected.json index 63114433d..0cfc28750 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.existingFunction.expected.json @@ -72,16 +72,6 @@ "Arn" ] } - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "testsqslambdaEncryptionKey317A2F03", - "Arn" - ] - } } ], "Version": "2012-10-17" @@ -178,91 +168,16 @@ } } }, - "testsqslambdaEncryptionKey317A2F03": { - "Type": "AWS::KMS::Key", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" - ] - ] - } - }, - "Resource": "*" - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "LambdaFunctionServiceRole0C4CDE0B", - "Arn" - ] - } - }, - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "EnableKeyRotation": true - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, "testsqslambdadeadLetterQueue85BDB0A3": { "Type": "AWS::SQS::Queue", "Properties": { - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "testsqslambdaEncryptionKey317A2F03", - "Arn" - ] - } + "KmsMasterKeyId": "alias/aws/sqs" } }, "testsqslambdaqueue601203B8": { "Type": "AWS::SQS::Queue", "Properties": { - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "testsqslambdaEncryptionKey317A2F03", - "Arn" - ] - }, + "KmsMasterKeyId": "alias/aws/sqs", "RedrivePolicy": { "deadLetterTargetArn": { "Fn::GetAtt": [ diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.ts b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.existingFunction.ts similarity index 92% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.ts rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.existingFunction.ts index 63337bc51..6bc7b9fbf 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.ts +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/integ.existingFunction.ts @@ -15,7 +15,7 @@ import { App, Stack } from "@aws-cdk/core"; import { SqsToLambda, SqsToLambdaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; // Setup const app = new App(); @@ -36,8 +36,7 @@ const props: SqsToLambdaProps = { existingLambdaObj: func, queueProps: {}, deployDeadLetterQueue: true, - maxReceiveCount: 3, - encryptionKeyProps: {} + maxReceiveCount: 3 }; new SqsToLambda(stack, 'test-sqs-lambda', props); diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/lambda/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/lambda/index.js rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/lambda/index.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/test.sqs-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/test.sqs-lambda.test.ts similarity index 93% rename from source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/test.sqs-lambda.test.ts rename to source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/test.sqs-lambda.test.ts index 171bc25b5..ddcb5fe64 100644 --- a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/test.sqs-lambda.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-sqs-lambda/test/test.sqs-lambda.test.ts @@ -58,15 +58,14 @@ test('Pattern deployment w/ new Lambda function and overridden props', () => { queueProps: { fifo: true }, - encryptionKeyProps: {}, deployDeadLetterQueue: false, maxReceiveCount: 0 }; const app = new SqsToLambda(stack, 'test-sqs-lambda', props); // Assertion 1 - expect(app.lambdaFunction()).toHaveProperty('environment.OVERRIDE', 'TRUE'); + expect(app.lambdaFunction).toHaveProperty('environment.OVERRIDE', 'TRUE'); // Assertion 2 - expect(app.sqsQueue()).toHaveProperty('fifo', true); + expect(app.sqsQueue).toHaveProperty('fifo', true); }); // -------------------------------------------------------------- @@ -84,7 +83,6 @@ test('Pattern deployment w/ Existing Lambda Function', () => { deployLambda: false, existingLambdaObj: fn, deployDeadLetterQueue: false, - encryptionKeyProps: {}, maxReceiveCount: 0, queueProps: {} }; @@ -107,15 +105,14 @@ test('Test getter methods', () => { code: lambda.Code.asset(`${__dirname}/lambda`) }, deployDeadLetterQueue: false, - encryptionKeyProps: {}, maxReceiveCount: 0, queueProps: {} }; const app = new SqsToLambda(stack, 'test-apigateway-lambda', props); // Assertion 1 - expect(app.lambdaFunction()).toBeDefined(); + expect(app.lambdaFunction !== null); // Assertion 2 - expect(app.sqsQueue()).toBeDefined(); + expect(app.sqsQueue !== null); }); // -------------------------------------------------------------- @@ -128,7 +125,6 @@ test('Test error handling for existing Lambda function', () => { deployLambda: false, existingLambdaObj: undefined, deployDeadLetterQueue: false, - encryptionKeyProps: {}, maxReceiveCount: 0, queueProps: {} }; @@ -148,7 +144,6 @@ test('Test error handling for new Lambda function w/o required properties', () = const props: SqsToLambdaProps = { deployLambda: true, deployDeadLetterQueue: false, - encryptionKeyProps: {}, maxReceiveCount: 0, queueProps: {} }; diff --git a/source/patterns/@aws-solutions-konstruk/core/.eslintignore b/source/patterns/@aws-solutions-constructs/core/.eslintignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/.eslintignore rename to source/patterns/@aws-solutions-constructs/core/.eslintignore diff --git a/source/patterns/@aws-solutions-konstruk/core/.gitignore b/source/patterns/@aws-solutions-constructs/core/.gitignore similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/.gitignore rename to source/patterns/@aws-solutions-constructs/core/.gitignore diff --git a/source/patterns/@aws-solutions-constructs/core/.npmignore b/source/patterns/@aws-solutions-constructs/core/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/.npmignore @@ -0,0 +1,21 @@ +# Exclude typescript source and config +*.ts +tsconfig.json +coverage +.nyc_output +*.tgz +*.snk +*.tsbuildinfo + +# Include javascript files and typescript declarations +!*.js +!*.d.ts + +# Exclude jsii outdir +dist + +# Include .jsii +!.jsii + +# Include .jsii +!.jsii \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/README.md b/source/patterns/@aws-solutions-constructs/core/README.md new file mode 100644 index 000000000..6ad6952bb --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/README.md @@ -0,0 +1,81 @@ +# core module + + +--- + +![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) + +> All classes are under active development and subject to non-backward compatible changes or removal in any +> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. +> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + +--- + + +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| +|:-------------|:-------------| +
+ +The core library includes the basic building blocks of the AWS Solutions Constructs Library. It defines the core classes that are used in the rest of the AWS Solutions Constructs Library. + +## Default Properties for AWS CDK Constructs + +Core library sets the default properties for the AWS CDK Constructs used by the AWS Solutions Constructs Library constructs. + +For example, the following is the snippet of default properties for S3 Bucket construct created by AWS Solutions Constructs. By default, it will turn on the server-side encryption, bucket versioning, block all public access and setup the S3 access logging. + +``` +{ + encryption: s3.BucketEncryption.S3_MANAGED, + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + removalPolicy: RemovalPolicy.RETAIN, + serverAccessLogsBucket: loggingBucket +} +``` + +## Override the default properties + +The default properties set by the Core library can be overridden by user provided properties. For example, the user can override the Amazon S3 Block Public Access property to meet specific requirements. + +``` + const stack = new cdk.Stack(); + + const props: CloudFrontToS3Props = { + deployBucket: true, + bucketProps: { + blockPublicAccess: { + blockPublicAcls: false, + blockPublicPolicy: true, + ignorePublicAcls: false, + restrictPublicBuckets: true + } + } + }; + + new CloudFrontToS3(stack, 'test-cloudfront-s3', props); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + PublicAccessBlockConfiguration: { + BlockPublicAcls: false, + BlockPublicPolicy: true, + IgnorePublicAcls: false, + RestrictPublicBuckets: true + }, + }); +``` + +## Property override warnings + +When a default property from the Core library is overridden by a user-provided property, Constructs will emit one or more warning messages to the console highlighting the change(s). These messages are intended to provide situational awareness to the user and prevent unintentional overrides that could create security risks. These messages will appear whenever deployment/build-related commands are executed, including `cdk deploy`, `cdk synth`, `npm test`, etc. + +Example message: +`AWS_CONSTRUCTS_WARNING: An override has been provided for the property: BillingMode. Default value: 'PAY_PER_REQUEST'. You provided: 'PROVISIONED'.` + +#### Toggling override warnings + +Override warning messages are enabled by default, but can be explicitly turned on/off using the `overrideWarningsEnabled` shell variable. + +- To explicitly turn off override warnings, run `export overrideWarningsEnabled=false`. +- To explicitly turn on override warnings, run `export overrideWarningsEnabled=true`. +- To revert to the default, run `unset overrideWarningsEnabled`. \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/index.ts b/source/patterns/@aws-solutions-constructs/core/index.ts similarity index 93% rename from source/patterns/@aws-solutions-konstruk/core/index.ts rename to source/patterns/@aws-solutions-constructs/core/index.ts index a8de6950e..743cf0a8b 100644 --- a/source/patterns/@aws-solutions-konstruk/core/index.ts +++ b/source/patterns/@aws-solutions-constructs/core/index.ts @@ -39,4 +39,6 @@ export * from './lib/events-rule-defaults'; export * from './lib/cognito-defaults'; export * from './lib/cognito-helper'; export * from './lib/elasticsearch-defaults'; -export * from './lib/elasticsearch-helper'; \ No newline at end of file +export * from './lib/elasticsearch-helper'; +export * from './lib/step-function-defaults'; +export * from './lib/step-function-helper'; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/apigateway-defaults.ts similarity index 97% rename from source/patterns/@aws-solutions-konstruk/core/lib/apigateway-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/apigateway-defaults.ts index c2a490e4b..3ae91021d 100644 --- a/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/apigateway-defaults.ts @@ -23,7 +23,9 @@ import { LogGroup } from '@aws-cdk/aws-logs'; */ function DefaultRestApiProps(_endpointType: api.EndpointType[], _logGroup: LogGroup): api.RestApiProps { return { - endpointTypes: _endpointType, + endpointConfiguration: { + types: _endpointType + }, cloudWatchRole: false, // Configure API Gateway Access logging deployOptions: { diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/apigateway-helper.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/apigateway-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/apigateway-helper.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/cloudfront-distribution-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/cloudfront-distribution-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/cloudfront-distribution-helper.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/cloudfront-distribution-helper.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cloudwatch-log-group-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/cloudwatch-log-group-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/cloudwatch-log-group-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/cloudwatch-log-group-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cognito-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/cognito-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/cognito-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/cognito-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cognito-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/cognito-helper.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/cognito-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/cognito-helper.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/dynamodb-table-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/dynamodb-table-defaults.ts similarity index 91% rename from source/patterns/@aws-solutions-konstruk/core/lib/dynamodb-table-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/dynamodb-table-defaults.ts index a3921a915..cb899b1a7 100644 --- a/source/patterns/@aws-solutions-konstruk/core/lib/dynamodb-table-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/dynamodb-table-defaults.ts @@ -15,7 +15,7 @@ import * as dynamodb from '@aws-cdk/aws-dynamodb'; const DefaultTableProps: dynamodb.TableProps = { billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, - serverSideEncryption: true, + encryption: dynamodb.TableEncryption.AWS_MANAGED, partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING @@ -24,7 +24,7 @@ const DefaultTableProps: dynamodb.TableProps = { const DefaultTableWithStreamProps: dynamodb.TableProps = { billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, - serverSideEncryption: true, + encryption: dynamodb.TableEncryption.AWS_MANAGED, partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-helper.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-helper.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/events-rule-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/events-rule-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/events-rule-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/events-rule-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/iot-topic-rule-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/iot-topic-rule-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/iot-topic-rule-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/iot-topic-rule-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/kinesis-analytics-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/kinesis-analytics-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/kinesis-analytics-helper.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/kinesis-analytics-helper.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-firehose-s3-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/kinesis-firehose-s3-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/kinesis-firehose-s3-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/kinesis-firehose-s3-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/kinesis-streams-defaults.ts similarity index 93% rename from source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/kinesis-streams-defaults.ts index ef20c82d8..11fe21d18 100644 --- a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/kinesis-streams-defaults.ts @@ -14,7 +14,7 @@ import * as kinesis from '@aws-cdk/aws-kinesis'; const DefaultStreamProps: kinesis.StreamProps = { - encryption: kinesis.StreamEncryption.KMS + encryption: kinesis.StreamEncryption.MANAGED }; export { DefaultStreamProps }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/kinesis-streams-helper.ts similarity index 80% rename from source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/kinesis-streams-helper.ts index dd69f3446..456207e73 100644 --- a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/kinesis-streams-helper.ts @@ -13,18 +13,11 @@ // Imports import * as kinesis from '@aws-cdk/aws-kinesis'; -import * as kms from '@aws-cdk/aws-kms'; import { DefaultStreamProps } from './kinesis-streams-defaults'; import * as cdk from '@aws-cdk/core'; import { overrideProps } from './utils'; export interface BuildKinesisStreamProps { - /** - * Optional external encryption key to use for stream encryption. - * - * @default - Default props are used. - */ - readonly encryptionKey?: kms.Key /** * Optional user provided props to override the default props for the Kinesis stream. * @@ -45,10 +38,7 @@ export function buildKinesisStream(scope: cdk.Construct, props?: BuildKinesisStr // If no property overrides, deploy using the default configuration kinesisStreamProps = DefaultStreamProps; } - // Set conditional stream encryption properties - if (!kinesisStreamProps.hasOwnProperty('encryptionKey') && props.hasOwnProperty('kinesisStreamProps')) { - kinesisStreamProps.encryptionKey = props.encryptionKey; - } + // Create the stream and return return new kinesis.Stream(scope, 'KinesisStream', kinesisStreamProps); } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kms-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/kms-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/kms-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/kms-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kms-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/kms-helper.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/kms-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/kms-helper.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/lambda-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/lambda-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/lambda-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/lambda-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/lambda-event-source-mapping-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/lambda-event-source-mapping-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/lambda-event-source-mapping-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/lambda-event-source-mapping-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/lambda-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/lambda-helper.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/lambda-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/lambda-helper.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/override-warning-service.ts b/source/patterns/@aws-solutions-constructs/core/lib/override-warning-service.ts similarity index 94% rename from source/patterns/@aws-solutions-konstruk/core/lib/override-warning-service.ts rename to source/patterns/@aws-solutions-constructs/core/lib/override-warning-service.ts index 973e89ecf..cb49b9238 100644 --- a/source/patterns/@aws-solutions-konstruk/core/lib/override-warning-service.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/override-warning-service.ts @@ -16,7 +16,7 @@ import * as log from 'npmlog'; /** * Emits a warning to the console when a prescriptive default value is overridden by the user. - * @param {objet} defaultProps the prescriptive defaults for the pattern. + * @param {object} defaultProps the prescriptive defaults for the pattern. * @param {object} userProps the properties provided by the user, to be compared against the defaultProps. */ export function flagOverriddenDefaults(defaultProps: object, userProps: object) { @@ -38,7 +38,7 @@ export function flagOverriddenDefaults(defaultProps: object, userProps: object) log.enableColor(); // Output const details = (valuesAreReadable) ? ` Default value: '${e.lhs}'. You provided: '${e.rhs}'.` : ''; - log.warn('AWS_KONSTRUK_WARNING: ', `An override has been provided for the property: ${path}.` + details); + log.warn('AWS_SOLUTIONS_CONSTRUCTS_WARNING: ', `An override has been provided for the property: ${path}.` + details); } } diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/s3-bucket-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/s3-bucket-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/s3-bucket-helper.ts similarity index 84% rename from source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/s3-bucket-helper.ts index 4a01a1df7..6f18950af 100644 --- a/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/s3-bucket-helper.ts @@ -40,7 +40,7 @@ export interface BuildS3BucketProps { readonly bucketProps?: s3.BucketProps } -export function buildS3Bucket(scope: cdk.Construct, props: BuildS3BucketProps): s3.Bucket { +export function buildS3Bucket(scope: cdk.Construct, props: BuildS3BucketProps, bucketId?: string): s3.Bucket { // Conditional s3 Bucket creation // If deployBucket == false if (props.hasOwnProperty('deployBucket') && props.deployBucket === false) { @@ -52,23 +52,25 @@ export function buildS3Bucket(scope: cdk.Construct, props: BuildS3BucketProps): // If deploy == true } else { if (props.bucketProps) { - return s3BucketWithLogging(scope, props.bucketProps); + return s3BucketWithLogging(scope, props.bucketProps, bucketId); } else { - return s3BucketWithLogging(scope, DefaultS3Props()); + return s3BucketWithLogging(scope, DefaultS3Props(), bucketId); } } } -function s3BucketWithLogging(scope: cdk.Construct, s3BucketProps?: s3.BucketProps): s3.Bucket { +function s3BucketWithLogging(scope: cdk.Construct, s3BucketProps?: s3.BucketProps, bucketId?: string): s3.Bucket { // Create the Application Bucket let bucketprops; + const _bucketId = bucketId ? bucketId + 'S3Bucket' : 'S3Bucket'; + const _loggingBucketId = bucketId ? bucketId + 'S3LoggingBucket' : 'S3LoggingBucket'; if (s3BucketProps?.serverAccessLogsBucket) { bucketprops = DefaultS3Props; } else { // Create the Logging Bucket - const loggingBucket: s3.Bucket = new s3.Bucket(scope, 'S3LoggingBucket', DefaultS3Props()); + const loggingBucket: s3.Bucket = new s3.Bucket(scope, _loggingBucketId, DefaultS3Props()); // Extract the CfnBucket from the loggingBucket const loggingBucketResource = loggingBucket.node.findChild('Resource') as s3.CfnBucket; @@ -92,7 +94,7 @@ function s3BucketWithLogging(scope: cdk.Construct, s3BucketProps?: s3.BucketProp if (s3BucketProps) { bucketprops = overrideProps(bucketprops, s3BucketProps); } - const s3Bucket: s3.Bucket = new s3.Bucket(scope, 'S3Bucket', bucketprops); + const s3Bucket: s3.Bucket = new s3.Bucket(scope, _bucketId, bucketprops); return s3Bucket; } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/sns-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/sns-defaults.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/sns-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/sns-defaults.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/sns-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/sns-helper.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/sns-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/sns-helper.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/sqs-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/sqs-defaults.ts similarity index 80% rename from source/patterns/@aws-solutions-konstruk/core/lib/sqs-defaults.ts rename to source/patterns/@aws-solutions-constructs/core/lib/sqs-defaults.ts index d092731d8..590c66470 100644 --- a/source/patterns/@aws-solutions-konstruk/core/lib/sqs-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/sqs-defaults.ts @@ -12,12 +12,10 @@ */ import * as sqs from '@aws-cdk/aws-sqs'; -import * as kms from '@aws-cdk/aws-kms'; -export function DefaultQueueProps(_encryptionMasterKey?: kms.Key) { +export function DefaultQueueProps() { const _DefaultQueueProps: sqs.QueueProps = { - encryption: sqs.QueueEncryption.KMS, - encryptionMasterKey: _encryptionMasterKey + encryption: sqs.QueueEncryption.KMS_MANAGED }; return _DefaultQueueProps; } diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/sqs-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/sqs-helper.ts similarity index 83% rename from source/patterns/@aws-solutions-konstruk/core/lib/sqs-helper.ts rename to source/patterns/@aws-solutions-constructs/core/lib/sqs-helper.ts index a2193e40e..bdf5da2bb 100644 --- a/source/patterns/@aws-solutions-konstruk/core/lib/sqs-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/sqs-helper.ts @@ -13,18 +13,11 @@ // Imports import * as sqs from '@aws-cdk/aws-sqs'; -import * as kms from '@aws-cdk/aws-kms'; import * as defaults from './sqs-defaults'; import * as cdk from '@aws-cdk/core'; import { overrideProps } from './utils'; export interface BuildQueueProps { - /** - * Optional external encryption key to use for stream encryption. - * - * @default - Default props are used. - */ - readonly encryptionKey?: kms.Key /** * Optional user provided props to override the default props for the primary queue. * @@ -46,10 +39,10 @@ export function buildQueue(scope: cdk.Construct, id: string, props?: BuildQueueP let queueProps; if (props.queueProps) { // If property overrides have been provided, incorporate them and deploy - queueProps = overrideProps(defaults.DefaultQueueProps(props.encryptionKey), props.queueProps); + queueProps = overrideProps(defaults.DefaultQueueProps(), props.queueProps); } else { // If no property overrides, deploy using the default configuration - queueProps = defaults.DefaultQueueProps(props.encryptionKey); + queueProps = defaults.DefaultQueueProps(); } // Determine whether a DLQ property should be added if (props.deadLetterQueue) { @@ -67,7 +60,7 @@ export interface BuildDeadLetterQueueProps { */ readonly deadLetterQueue: sqs.Queue /** - * The number of times a message can be unsuccesfully dequeued before being moved to the dead-letter queue. + * The number of times a message can be unsuccessfully dequeued before being moved to the dead-letter queue. * * @default - Default props are used */ diff --git a/source/patterns/@aws-solutions-constructs/core/lib/step-function-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/step-function-defaults.ts new file mode 100644 index 000000000..21160ba9b --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/lib/step-function-defaults.ts @@ -0,0 +1,27 @@ +/** + * 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. + */ + +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { LogGroup } from '@aws-cdk/aws-logs'; + +export function DefaultStateMachineProps(_logGroup: LogGroup): sfn.StateMachineProps | any { + + const stateMachineProps: sfn.StateMachineProps | any = { + logs: { + destination: _logGroup, + level: sfn.LogLevel.ERROR + } + }; + + return stateMachineProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/lib/step-function-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/step-function-helper.ts new file mode 100644 index 000000000..2b3ca5345 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/lib/step-function-helper.ts @@ -0,0 +1,119 @@ +/** + * 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. + */ + +// Imports +import { LogGroup } from '@aws-cdk/aws-logs'; +import * as cdk from '@aws-cdk/core'; +import * as smDefaults from './step-function-defaults'; +import { DefaultLogGroupProps } from './cloudwatch-log-group-defaults'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { overrideProps } from './utils'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +/** + * Builds and returns a StateMachine. + * @param scope - the construct to which the StateMachine should be attached to. + * @param stateMachineProps - user-specified properties to override the default properties. + */ +export function buildStateMachine(scope: cdk.Construct, stateMachineProps: sfn.StateMachineProps): sfn.StateMachine { + + let logGroup: LogGroup; + + // Configure Cloudwatch log group for Step function State Machine + if (!stateMachineProps.logs) { + logGroup = new LogGroup(scope, 'StateMachineLogGroup', DefaultLogGroupProps()); + } else { + logGroup = stateMachineProps.logs.destination as LogGroup; + } + + // Override the defaults with the user provided props + const _smProps = overrideProps(smDefaults.DefaultStateMachineProps(logGroup), stateMachineProps); + + // Override the Cloudwatch permissions to make it more fine grained + const _sm = new sfn.StateMachine(scope, 'StateMachine', _smProps); + const role = _sm.node.findChild('Role') as iam.Role; + const cfnDefaultPolicy = role.node.findChild('DefaultPolicy').node.defaultChild as iam.CfnPolicy; + + // Reduce the scope of actions for the existing DefaultPolicy + cfnDefaultPolicy.addPropertyOverride('PolicyDocument.Statement.0.Action', + [ + "logs:CreateLogDelivery", + 'logs:GetLogDelivery', + 'logs:UpdateLogDelivery', + 'logs:DeleteLogDelivery', + 'logs:ListLogDeliveries' + ]); + + // Override Cfn Nag warning W12: IAM policy should not allow * resource + cfnDefaultPolicy.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W12', + reason: `The 'LogDelivery' actions do not support resource-level authorizations` + }] + } + }; + + // Add a new policy with logging permissions for the given cloudwatch log group + _sm.addToRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:PutResourcePolicy', + 'logs:DescribeResourcePolicies', + 'logs:DescribeLogGroups' + ], + resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:*`] + })); + + return _sm; +} + +export function buildStepFunctionCWAlarms(scope: cdk.Construct, sm: sfn.StateMachine): cloudwatch.Alarm[] { + // Setup CW Alarms for State Machine + const alarms: cloudwatch.Alarm[] = new Array(); + + // Sum of number of executions that failed is >= 1 for 5 minutes, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'ExecutionFailedAlarm', { + metric: sm.metricFailed(), + threshold: 1, + evaluationPeriods: 1, + statistic: 'Sum', + period: cdk.Duration.seconds(300), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'Alarm for the number of executions that failed exceeded the threshold of 1. ' + })); + + // Sum of number of executions that failed maximum is >= 1 for 5 minute, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'ExecutionThrottledAlarm', { + metric: sm.metricThrottled(), + threshold: 1, + evaluationPeriods: 1, + statistic: 'Sum', + period: cdk.Duration.seconds(300), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'Alarm for the number of executions that throttled exceeded the threshold of 1. ' + })); + + // Number of executions that aborted maximum is >= 1 for 5 minute, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'ExecutionAbortedAlarm', { + metric: sm.metricAborted(), + threshold: 1, + evaluationPeriods: 1, + statistic: 'Maximum', + period: cdk.Duration.seconds(300), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'Alarm for the number of executions that aborted exceeded the threshold of 1. ' + })); + + return alarms; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/utils.ts b/source/patterns/@aws-solutions-constructs/core/lib/utils.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/lib/utils.ts rename to source/patterns/@aws-solutions-constructs/core/lib/utils.ts diff --git a/source/patterns/@aws-solutions-constructs/core/package.json b/source/patterns/@aws-solutions-constructs/core/package.json new file mode 100644 index 000000000..a0900039c --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/package.json @@ -0,0 +1,122 @@ +{ + "name": "@aws-solutions-constructs/core", + "version": "1.46.0", + "description": "Core CDK Construct for patterns library", + "main": "index.js", + "types": "index.ts", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/core" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc -b .", + "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", + "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", + "test": "jest --coverage", + "clean": "tsc -b --clean", + "watch": "tsc -b -w", + "integ": "cdk-integ", + "integ-assert": "cdk-integ-assert", + "jsii": "jsii", + "jsii-pacmak": "jsii-pacmak", + "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", + "snapshot-update": "npm test -- -u" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.core", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "core" + } + }, + "dotnet": { + "namespace": "Amazon.Constructs", + "packageId": "Amazon.Constructs", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.core", + "module": "aws_solutions_constructs.core" + } + } + }, + "dependencies": { + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/aws-iot": "~1.46.0", + "@aws-cdk/aws-kinesis": "~1.46.0", + "@aws-cdk/aws-kinesisanalytics": "~1.46.0", + "@aws-cdk/aws-kinesisfirehose": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/aws-sns": "~1.46.0", + "@aws-cdk/aws-sqs": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/aws-events": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-elasticsearch": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-cdk/aws-stepfunctions": "~1.46.0", + "@types/deep-diff": "^1.0.0", + "@types/npmlog": "^4.1.2", + "deep-diff": "^1.0.2", + "deepmerge": "^4.0.0", + "npmlog": "^4.1.2" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.46.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "bundledDependencies": [ + "deepmerge", + "npmlog", + "@types/npmlog", + "deep-diff", + "@types/deep-diff" + ], + "peerDependencies": { + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-cdk/aws-iot": "~1.46.0", + "@aws-cdk/aws-kinesis": "~1.46.0", + "@aws-cdk/aws-kinesisanalytics": "~1.46.0", + "@aws-cdk/aws-kinesisfirehose": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-lambda-event-sources": "~1.46.0", + "@aws-cdk/aws-logs": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/aws-sns": "~1.46.0", + "@aws-cdk/aws-sqs": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-kms": "~1.46.0", + "@aws-cdk/aws-events": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-elasticsearch": "~1.46.0", + "@aws-cdk/aws-cloudwatch": "~1.46.0", + "@aws-cdk/aws-stepfunctions": "~1.46.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/apigateway-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/apigateway-helper.test.js.snap similarity index 99% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/apigateway-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/apigateway-helper.test.js.snap index 7a58809aa..5c3ab8940 100644 --- a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/apigateway-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/apigateway-helper.test.js.snap @@ -1079,7 +1079,7 @@ Object { Object { "Ref": "LambdaRestApiDeploymentStageprodB1F3862A", }, - "/*/{proxy+}", + "/*/*", ], ], }, @@ -1116,7 +1116,7 @@ Object { Object { "Ref": "LambdaRestApi95870433", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap similarity index 99% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap index e2e39f30f..f828e33e3 100644 --- a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap @@ -562,7 +562,7 @@ Object { Object { "Ref": "RestApiDeploymentStageprod3855DE66", }, - "/*/{proxy+}", + "/*/*", ], ], }, @@ -599,7 +599,7 @@ Object { Object { "Ref": "RestApi0C43BF4B", }, - "/test-invoke-stage/*/{proxy+}", + "/test-invoke-stage/*/*", ], ], }, diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudwatch-log-group.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/cloudwatch-log-group.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudwatch-log-group.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/cloudwatch-log-group.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/congnito-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/congnito-helper.test.js.snap similarity index 83% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/congnito-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/congnito-helper.test.js.snap index 197bb90a9..9310bec1d 100644 --- a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/congnito-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/congnito-helper.test.js.snap @@ -5,6 +5,18 @@ Object { "Resources": Object { "CognitoUserPool53E37E69": Object { "Properties": Object { + "AccountRecoverySetting": Object { + "RecoveryMechanisms": Array [ + Object { + "Name": "verified_phone_number", + "Priority": 1, + }, + Object { + "Name": "verified_email", + "Priority": 2, + }, + ], + }, "AdminCreateUserConfig": Object { "AllowAdminCreateUserOnly": true, }, @@ -88,6 +100,18 @@ Object { "Resources": Object { "CognitoUserPool53E37E69": Object { "Properties": Object { + "AccountRecoverySetting": Object { + "RecoveryMechanisms": Array [ + Object { + "Name": "verified_phone_number", + "Priority": 1, + }, + Object { + "Name": "verified_email", + "Priority": 2, + }, + ], + }, "AdminCreateUserConfig": Object { "AllowAdminCreateUserOnly": true, }, @@ -117,6 +141,24 @@ Object { }, "CognitoUserPoolClient5AB59AE4": Object { "Properties": Object { + "AllowedOAuthFlows": Array [ + "implicit", + "code", + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": Array [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin", + ], + "CallbackURLs": Array [ + "https://example.com", + ], + "SupportedIdentityProviders": Array [ + "COGNITO", + ], "UserPoolId": Object { "Ref": "CognitoUserPool53E37E69", }, diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/dynamo-table.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/dynamo-table.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/dynamo-table.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/dynamo-table.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/elasticsearch-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/elasticsearch-helper.test.js.snap similarity index 93% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/elasticsearch-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/elasticsearch-helper.test.js.snap index 71d02185f..3d223289d 100644 --- a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/elasticsearch-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/elasticsearch-helper.test.js.snap @@ -190,6 +190,18 @@ Object { }, "CognitoUserPool53E37E69": Object { "Properties": Object { + "AccountRecoverySetting": Object { + "RecoveryMechanisms": Array [ + Object { + "Name": "verified_phone_number", + "Priority": 1, + }, + Object { + "Name": "verified_email", + "Priority": 2, + }, + ], + }, "AdminCreateUserConfig": Object { "AllowAdminCreateUserOnly": true, }, @@ -219,7 +231,25 @@ Object { }, "CognitoUserPoolClient5AB59AE4": Object { "Properties": Object { + "AllowedOAuthFlows": Array [ + "implicit", + "code", + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": Array [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin", + ], + "CallbackURLs": Array [ + "https://example.com", + ], "ClientName": "test", + "SupportedIdentityProviders": Array [ + "COGNITO", + ], "UserPoolId": Object { "Ref": "CognitoUserPool53E37E69", }, diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/events-rule.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/events-rule.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/events-rule.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/events-rule.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/iot-rule.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/iot-rule.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/iot-rule.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/iot-rule.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-analytics.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-analytics.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap new file mode 100644 index 000000000..0a475c44c --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test kinesisstream default params 1`] = ` +Object { + "Resources": Object { + "KinesisStream46752A3E": Object { + "Properties": Object { + "RetentionPeriodHours": 24, + "ShardCount": 1, + "StreamEncryption": Object { + "EncryptionType": "KMS", + "KeyId": "alias/aws/kinesis", + }, + }, + "Type": "AWS::Kinesis::Stream", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-streams-helper.test.js.snap similarity index 75% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-streams-helper.test.js.snap index 16e052c13..2d4e6783b 100644 --- a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kinesis-streams-helper.test.js.snap @@ -1,28 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`snapshot test kinesisstream default params 1`] = ` +exports[`Test deployment w/ custom properties 1`] = ` Object { "Resources": Object { - "KinesisStream46752A3E": Object { - "Properties": Object { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": Object { - "EncryptionType": "KMS", - "KeyId": Object { - "Fn::GetAtt": Array [ - "KinesisStreamKey72E22A02", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "KinesisStreamKey72E22A02": Object { + "EncryptionKey1B843E66": Object { "DeletionPolicy": "Retain", "Properties": Object { - "Description": "Created by KinesisStream", + "EnableKeyRotation": true, "KeyPolicy": Object { "Statement": Array [ Object { @@ -71,6 +55,41 @@ Object { "Type": "AWS::KMS::Key", "UpdateReplacePolicy": "Retain", }, + "KinesisStream46752A3E": Object { + "Properties": Object { + "Name": "myCustomKinesisStream", + "RetentionPeriodHours": 24, + "ShardCount": 1, + "StreamEncryption": Object { + "EncryptionType": "KMS", + "KeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + }, + "Type": "AWS::Kinesis::Stream", + }, + }, +} +`; + +exports[`Test minimal deployment with no properties 1`] = ` +Object { + "Resources": Object { + "KinesisStream46752A3E": Object { + "Properties": Object { + "RetentionPeriodHours": 24, + "ShardCount": 1, + "StreamEncryption": Object { + "EncryptionType": "KMS", + "KeyId": "alias/aws/kinesis", + }, + }, + "Type": "AWS::Kinesis::Stream", + }, }, } `; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kms-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kms-helper.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kms-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/kms-helper.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/lambda-func.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/lambda-func.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/lambda-func.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/lambda-func.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/s3-bucket-helper.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/s3-bucket-helper.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/s3-bucket.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/s3-bucket.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sns-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sns-helper.test.js.snap similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sns-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sns-helper.test.js.snap diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sqs-helper.test.js.snap similarity index 76% rename from source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-helper.test.js.snap rename to source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sqs-helper.test.js.snap index add414770..36e4ae13d 100644 --- a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-helper.test.js.snap +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/sqs-helper.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Test deployment w/ custom properties 1`] = ` +exports[`Test dead letter queue deployment/configuration 1`] = ` Object { "Resources": Object { "EncryptionKey1B843E66": Object { @@ -55,50 +55,43 @@ Object { "Type": "AWS::KMS::Key", "UpdateReplacePolicy": "Retain", }, - "KinesisStream46752A3E": Object { + "deadletterqueueD1EEB012": Object { "Properties": Object { - "Name": "myCustomKinesisStream", - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": Object { - "EncryptionType": "KMS", - "KeyId": Object { + "KmsMasterKeyId": "alias/aws/sqs", + }, + "Type": "AWS::SQS::Queue", + }, + "primaryqueue045A5712": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { "Fn::GetAtt": Array [ - "EncryptionKey1B843E66", + "deadletterqueueD1EEB012", "Arn", ], }, + "maxReceiveCount": 3, }, }, - "Type": "AWS::Kinesis::Stream", + "Type": "AWS::SQS::Queue", }, }, } `; -exports[`Test minimal deployment with no properties 1`] = ` +exports[`Test deployment w/ custom properties 1`] = ` Object { "Resources": Object { - "KinesisStream46752A3E": Object { - "Properties": Object { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": Object { - "EncryptionType": "KMS", - "KeyId": Object { - "Fn::GetAtt": Array [ - "KinesisStreamKey72E22A02", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "KinesisStreamKey72E22A02": Object { + "EncryptionKey1B843E66": Object { "DeletionPolicy": "Retain", "Properties": Object { - "Description": "Created by KinesisStream", + "EnableKeyRotation": true, "KeyPolicy": Object { "Statement": Array [ Object { @@ -147,6 +140,30 @@ Object { "Type": "AWS::KMS::Key", "UpdateReplacePolicy": "Retain", }, + "primaryqueue045A5712": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + "Type": "AWS::SQS::Queue", + }, + }, +} +`; + +exports[`Test minimal deployment with no properties 1`] = ` +Object { + "Resources": Object { + "primaryqueue045A5712": Object { + "Properties": Object { + "KmsMasterKeyId": "alias/aws/sqs", + }, + "Type": "AWS::SQS::Queue", + }, }, } `; diff --git a/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/step-function-helper.test.js.snap b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/step-function-helper.test.js.snap new file mode 100644 index 000000000..dd1b86f5c --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/test/__snapshots__/step-function-helper.test.js.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test minimal deployment with no properties 1`] = ` +Object { + "Resources": Object { + "StateMachine2E01A3A5": Object { + "DependsOn": Array [ + "StateMachineRoleDefaultPolicyDF1E6607", + "StateMachineRoleB840431D", + ], + "Properties": Object { + "DefinitionString": "{\\"StartAt\\":\\"StartState\\",\\"States\\":{\\"StartState\\":{\\"Type\\":\\"Pass\\",\\"End\\":true}}}", + "LoggingConfiguration": Object { + "Destinations": Array [ + Object { + "CloudWatchLogsLogGroup": Object { + "LogGroupArn": Object { + "Fn::GetAtt": Array [ + "StateMachineLogGroup15B91BCB", + "Arn", + ], + }, + }, + }, + ], + "Level": "ERROR", + }, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "StateMachineRoleB840431D", + "Arn", + ], + }, + }, + "Type": "AWS::StepFunctions::StateMachine", + }, + "StateMachineLogGroup15B91BCB": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "StateMachineRoleB840431D": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": Object { + "Fn::Join": Array [ + "", + Array [ + "states.", + Object { + "Ref": "AWS::Region", + }, + ".amazonaws.com", + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "StateMachineRoleDefaultPolicyDF1E6607": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "The 'LogDelivery' actions do not support resource-level authorizations", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries", + ], + "Effect": "Allow", + "Resource": "*", + }, + Object { + "Action": Array [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + "logs:DescribeLogGroups", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": Array [ + Object { + "Ref": "StateMachineRoleB840431D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/apigateway-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/apigateway-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/apigateway-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/apigateway-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-api-gateway-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/cloudfront-distribution-api-gateway-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-api-gateway-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/cloudfront-distribution-api-gateway-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-s3-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/cloudfront-distribution-s3-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-s3-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/cloudfront-distribution-s3-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/cloudwatch-log-group.test.ts b/source/patterns/@aws-solutions-constructs/core/test/cloudwatch-log-group.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/cloudwatch-log-group.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/cloudwatch-log-group.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/congnito-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/congnito-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/congnito-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/congnito-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/dynamo-table.test.ts b/source/patterns/@aws-solutions-constructs/core/test/dynamo-table.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/dynamo-table.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/dynamo-table.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/elasticsearch-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/elasticsearch-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/elasticsearch-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/elasticsearch-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/events-rule.test.ts b/source/patterns/@aws-solutions-constructs/core/test/events-rule.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/events-rule.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/events-rule.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/iot-rule.test.ts b/source/patterns/@aws-solutions-constructs/core/test/iot-rule.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/iot-rule.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/iot-rule.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/kinesis-analytics-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/kinesis-analytics-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics.test.ts b/source/patterns/@aws-solutions-constructs/core/test/kinesis-analytics.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/kinesis-analytics.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-firehose-s3-defaults.test.ts b/source/patterns/@aws-solutions-constructs/core/test/kinesis-firehose-s3-defaults.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/kinesis-firehose-s3-defaults.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/kinesis-firehose-s3-defaults.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-defaults.test.ts b/source/patterns/@aws-solutions-constructs/core/test/kinesis-streams-defaults.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-defaults.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/kinesis-streams-defaults.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/kinesis-streams-helper.test.ts similarity index 92% rename from source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/kinesis-streams-helper.test.ts index 94bfe492d..5405b43a8 100644 --- a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/kinesis-streams-helper.test.ts @@ -14,6 +14,7 @@ // Imports import { Stack } from "@aws-cdk/core"; import * as defaults from '../'; +import * as kinesis from '@aws-cdk/aws-kinesis'; import { SynthUtils, ResourcePart } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; @@ -48,9 +49,10 @@ test('Test deployment w/ custom properties', () => { const encKey = defaults.buildEncryptionKey(stack); // Helper declaration defaults.buildKinesisStream(stack, { - encryptionKey: encKey, kinesisStreamProps: { - streamName: 'myCustomKinesisStream' + streamName: 'myCustomKinesisStream', + encryption: kinesis.StreamEncryption.KMS, + encryptionKey: encKey } }); // Assertion 1 diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kms-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/kms-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/kms-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/kms-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/lambda-event-source.test.ts b/source/patterns/@aws-solutions-constructs/core/test/lambda-event-source.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/lambda-event-source.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/lambda-event-source.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/lambda-func.test.ts b/source/patterns/@aws-solutions-constructs/core/test/lambda-func.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/lambda-func.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/lambda-func.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/lambda-test/index.js b/source/patterns/@aws-solutions-constructs/core/test/lambda-test/index.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/lambda-test/index.js rename to source/patterns/@aws-solutions-constructs/core/test/lambda-test/index.js diff --git a/source/patterns/@aws-solutions-constructs/core/test/lambda/index.js b/source/patterns/@aws-solutions-constructs/core/test/lambda/index.js new file mode 100644 index 000000000..4b3640c1e --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/test/lambda/index.js @@ -0,0 +1,10 @@ +console.log('Loading function'); + +exports.handler = async (event, context) => { + console.log('Received event:', JSON.stringify(event, null, 2)); +    return { +      statusCode: 200, +      headers: { 'Content-Type': 'text/plain' }, +      body: `Hello from Project Vesper! You've hit ${event.path}\n` +    }; +}; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/override-warning-service.test.ts b/source/patterns/@aws-solutions-constructs/core/test/override-warning-service.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/override-warning-service.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/override-warning-service.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/s3-bucket-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/s3-bucket-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/s3-bucket-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/s3-bucket-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/s3-bucket.test.ts b/source/patterns/@aws-solutions-constructs/core/test/s3-bucket.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/s3-bucket.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/s3-bucket.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/sns-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/sns-helper.test.ts similarity index 100% rename from source/patterns/@aws-solutions-konstruk/core/test/sns-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/sns-helper.test.ts diff --git a/source/patterns/@aws-solutions-konstruk/core/test/sqs-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/sqs-helper.test.ts similarity index 88% rename from source/patterns/@aws-solutions-konstruk/core/test/sqs-helper.test.ts rename to source/patterns/@aws-solutions-constructs/core/test/sqs-helper.test.ts index eee43c39e..f53f596ff 100644 --- a/source/patterns/@aws-solutions-konstruk/core/test/sqs-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/sqs-helper.test.ts @@ -16,6 +16,7 @@ import { Stack } from "@aws-cdk/core"; import * as defaults from '../'; import { SynthUtils } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; +import * as sqs from '@aws-cdk/aws-sqs'; // -------------------------------------------------------------- // Test minimal deployment with no properties @@ -39,9 +40,10 @@ test('Test deployment w/ custom properties', () => { const encKey = defaults.buildEncryptionKey(stack); // Helper declaration defaults.buildQueue(stack, 'primary-queue', { - encryptionKey: encKey, queueProps: { - description: "custom-queue-props" + description: "custom-queue-props", + encryption: sqs.QueueEncryption.KMS, + encryptionMasterKey: encKey } }); // Assertion 1 @@ -63,9 +65,10 @@ test('Test dead letter queue deployment/configuration', () => { }); // Helper declaration defaults.buildQueue(stack, 'primary-queue', { - encryptionKey: encKey, queueProps: { - description: "not-the-dead-letter-queue-props" + description: "not-the-dead-letter-queue-props", + encryption: sqs.QueueEncryption.KMS, + encryptionMasterKey: encKey }, deadLetterQueue: dlqi }); diff --git a/source/patterns/@aws-solutions-constructs/core/test/step-function-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/step-function-helper.test.ts new file mode 100644 index 000000000..72f3e52da --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/core/test/step-function-helper.test.ts @@ -0,0 +1,165 @@ +/** + * 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. + */ + +// Imports +import { Stack } from "@aws-cdk/core"; +import * as defaults from '../'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { LogGroup } from "@aws-cdk/aws-logs"; + +// -------------------------------------------------------------- +// Test minimal deployment with no properties +// -------------------------------------------------------------- +test('Test minimal deployment with no properties', () => { + // Stack + const stack = new Stack(); + // Step function definition + const startState = new sfn.Pass(stack, 'StartState'); + // Build state machine + defaults.buildStateMachine(stack, { + definition: startState + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ custom properties +// -------------------------------------------------------------- +test('Test deployment w/ custom properties', () => { + // Stack + const stack = new Stack(); + // Step function definition + const startState = new sfn.Pass(stack, 'StartState'); + // Build state machine + defaults.buildStateMachine(stack, { + definition: startState, + stateMachineName: 'myStateMachine' + }); + // Assertion + expect(stack).toHaveResource("AWS::StepFunctions::StateMachine", { + StateMachineName: "myStateMachine" + }); +}); + +// -------------------------------------------------------------- +// Test deployment w/ logging enabled +// -------------------------------------------------------------- +test('Test deployment w/ logging enabled', () => { + // Stack + const stack = new Stack(); + // Step function definition + const startState = new sfn.Pass(stack, 'StartState'); + // Log group + const logGroup = new LogGroup(stack, 'myLogGroup', defaults.DefaultLogGroupProps()); + // Build state machine + defaults.buildStateMachine(stack, { + definition: startState, + logs: { + destination: logGroup, + level: sfn.LogLevel.FATAL + } + }); + // Assertion + expect(stack).toHaveResource("AWS::StepFunctions::StateMachine", { + LoggingConfiguration: { + Destinations: [{ + CloudWatchLogsLogGroup: { + LogGroupArn: { + "Fn::GetAtt": [ + "myLogGroup46524CAB", + "Arn" + ] + } + } + }], + Level: 'FATAL' + } + }); +}); + +// -------------------------------------------------------------- +// Check default Cloudwatch perissions +// -------------------------------------------------------------- +test('Test deployment w/ logging enabled', () => { + // Stack + const stack = new Stack(); + // Step function definition + const startState = new sfn.Pass(stack, 'StartState'); + // Build state machine + defaults.buildStateMachine(stack, { + definition: startState + }); + // Assertion + expect(stack).toHaveResource("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries" + ], + Effect: "Allow", + Resource: "*" + }, + { + Action: [ + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + "logs:DescribeLogGroups" + ], + Effect: "Allow", + Resource: { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + Ref: "AWS::Region" + }, + ":", + { + Ref: "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + Version: "2012-10-17" + } + }); +}); + +// -------------------------------------------------------------- +// Check CW Alarms +// -------------------------------------------------------------- +test('Count State Machine CW Alarms', () => { + // Stack + const stack = new Stack(); + // Step function definition + const startState = new sfn.Pass(stack, 'StartState'); + // Build state machine + const sm = defaults.buildStateMachine(stack, { + definition: startState + }); + const cwList = defaults.buildStepFunctionCWAlarms(stack, sm); + + expect(cwList.length).toEqual(3); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/eslintrc.yml b/source/patterns/@aws-solutions-constructs/eslintrc.yml similarity index 100% rename from source/patterns/@aws-solutions-konstruk/eslintrc.yml rename to source/patterns/@aws-solutions-constructs/eslintrc.yml diff --git a/source/patterns/@aws-solutions-konstruk/license-header.js b/source/patterns/@aws-solutions-constructs/license-header.js similarity index 100% rename from source/patterns/@aws-solutions-konstruk/license-header.js rename to source/patterns/@aws-solutions-constructs/license-header.js diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json deleted file mode 100644 index b7a1a5fa3..000000000 --- a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "name": "@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana", - "version": "0.8.1", - "description": "CDK Constructs for Amazon Dynamodb stream to AWS Lambda to AWS Elasticsearch with Kibana integration", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "repository": { - "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana" - }, - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com", - "organization": true - }, - "license": "Apache-2.0", - "scripts": { - "build": "tsc -b .", - "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", - "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", - "test": "jest --coverage", - "clean": "tsc -b --clean", - "watch": "tsc -b -w", - "integ": "cdk-integ", - "integ-no-clean": "cdk-integ --no-clean", - "integ-assert": "cdk-integ-assert", - "jsii": "jsii", - "jsii-pacmak": "jsii-pacmak", - "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", - "snapshot-update": "npm run jsii && npm test -- -u && npm run integ-assert" - }, - "jsii": { - "outdir": "dist", - "targets": { - "java": { - "package": "software.amazon.konstruk.services.dynamodbstreamlambdaelasticsearchkibana", - "maven": { - "groupId": "software.amazon.konstruk", - "artifactId": "dynamodbstreamlambdaelasticsearchkibana" - } - }, - "dotnet": { - "namespace": "Amazon.Konstruk.AWS.DynamodbStreamLambdaElasticsearchKibana", - "packageId": "Amazon.Konstruk.AWS.DynamodbStreamLambdaElasticsearchKibana", - "signAssembly": true, - "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" - }, - "python": { - "distName": "aws-solutions-konstruk.aws-dynamodb-stream-lambda-elasticsearch-kibana", - "module": "aws_solutions_konstruk.aws_dynamodb_stream_lambda_elasticsearch_kibana" - } - } - }, - "dependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-elasticsearch": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/aws-cloudwatch": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-solutions-konstruk/aws-dynamodb-stream-lambda": "~0.8.1", - "@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana": "~0.8.1", - "constructs": "^3.0.2" - }, - "devDependencies": { - "@aws-cdk/assert": "~1.40.0", - "@types/jest": "^24.0.23", - "@types/node": "^10.3.0" - }, - "jest": { - "moduleFileExtensions": [ - "js" - ] - }, - "peerDependencies": { - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-elasticsearch": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/aws-cloudwatch": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-solutions-konstruk/aws-dynamodb-stream-lambda": "~0.8.1", - "@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana": "~0.8.1", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "constructs": "^3.0.2" - } -} diff --git a/source/patterns/@aws-solutions-konstruk/core/README.md b/source/patterns/@aws-solutions-konstruk/core/README.md deleted file mode 100644 index 9ecafd285..000000000 --- a/source/patterns/@aws-solutions-konstruk/core/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# core module - - ---- - -![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) - -> **This is a _developer preview_ (public beta) module.** -> -> All classes are under active development and subject to non-backward compatible changes or removal in any -> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. -> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. - ---- - - -| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/api/core/| -|:-------------|:-------------| -
- -The core library includes the basic building blocks of the AWS Solutions Konstruk Library. It defines the core classes that are used in the rest of the AWS Solutions Konstruk Library. - -## Default Properties for AWS CDK Constructs - -Core library sets the default properties for the AWS CDK Constructs used by the AWS Solutions Konstruk Library constructs. - -For example, the following is the snippet of default properties for S3 Bucket construct created by AWS Solutions Konstruk construct. By default, it will turn on the server-side encryption, bucket versioning, block all public access and setup the S3 access logging. - -``` -{ - encryption: s3.BucketEncryption.S3_MANAGED, - versioned: true, - blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, - removalPolicy: RemovalPolicy.RETAIN, - serverAccessLogsBucket: loggingBucket -} -``` - -## Override the default properties - -The default properties set by the Core library can be overriden by user provided properties. For example, the user can override the Amazon S3 Block Public Access property to meet specific requirements. - -``` - const stack = new cdk.Stack(); - - const props: CloudFrontToS3Props = { - deployBucket: true, - bucketProps: { - blockPublicAccess: { - blockPublicAcls: false, - blockPublicPolicy: true, - ignorePublicAcls: false, - restrictPublicBuckets: true - } - } - }; - - new CloudFrontToS3(stack, 'test-cloudfront-s3', props); - - expect(stack).toHaveResource("AWS::S3::Bucket", { - PublicAccessBlockConfiguration: { - BlockPublicAcls: false, - BlockPublicPolicy: true, - IgnorePublicAcls: false, - RestrictPublicBuckets: true - }, - }); -``` - diff --git a/source/patterns/@aws-solutions-konstruk/core/package.json b/source/patterns/@aws-solutions-konstruk/core/package.json deleted file mode 100644 index 76287da2a..000000000 --- a/source/patterns/@aws-solutions-konstruk/core/package.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "name": "@aws-solutions-konstruk/core", - "version": "0.8.1", - "description": "Core CDK Construct for patterns library", - "main": "index.js", - "types": "index.ts", - "repository": { - "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", - "directory": "source/patterns/@aws-solutions-konstruk/core" - }, - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com", - "organization": true - }, - "license": "Apache-2.0", - "scripts": { - "build": "tsc -b .", - "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", - "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", - "test": "jest --coverage", - "clean": "tsc -b --clean", - "watch": "tsc -b -w", - "integ": "cdk-integ", - "integ-assert": "cdk-integ-assert", - "jsii": "jsii", - "jsii-pacmak": "jsii-pacmak", - "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", - "snapshot-update": "npm test -- -u" - }, - "jsii": { - "outdir": "dist", - "targets": { - "java": { - "package": "software.amazon.konstruk.services.core", - "maven": { - "groupId": "software.amazon.konstruk", - "artifactId": "core" - } - }, - "dotnet": { - "namespace": "Amazon.Konstruk", - "packageId": "Amazon.Konstruk", - "signAssembly": true, - "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" - }, - "python": { - "distName": "aws-solutions-konstruk.core", - "module": "aws_solutions_konstruk.core" - } - } - }, - "dependencies": { - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/aws-iot": "~1.40.0", - "@aws-cdk/aws-kinesis": "~1.40.0", - "@aws-cdk/aws-kinesisanalytics": "~1.40.0", - "@aws-cdk/aws-kinesisfirehose": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/aws-sns": "~1.40.0", - "@aws-cdk/aws-sqs": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/aws-events": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-elasticsearch": "~1.40.0", - "@aws-cdk/aws-cloudwatch": "~1.40.0", - "@types/deep-diff": "^1.0.0", - "@types/npmlog": "^4.1.2", - "deep-diff": "^1.0.2", - "deepmerge": "^4.0.0", - "npmlog": "^4.1.2" - }, - "devDependencies": { - "@aws-cdk/assert": "~1.40.0", - "@types/jest": "^24.0.23", - "@types/node": "^10.3.0" - }, - "jest": { - "moduleFileExtensions": [ - "js" - ] - }, - "bundledDependencies": [ - "deepmerge", - "npmlog", - "@types/npmlog", - "deep-diff", - "@types/deep-diff" - ], - "peerDependencies": { - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-cdk/aws-iot": "~1.40.0", - "@aws-cdk/aws-kinesis": "~1.40.0", - "@aws-cdk/aws-kinesisanalytics": "~1.40.0", - "@aws-cdk/aws-kinesisfirehose": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-lambda-event-sources": "~1.40.0", - "@aws-cdk/aws-logs": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/aws-sns": "~1.40.0", - "@aws-cdk/aws-sqs": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-kms": "~1.40.0", - "@aws-cdk/aws-events": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-elasticsearch": "~1.40.0", - "@aws-cdk/aws-cloudwatch": "~1.40.0" - } -} diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sqs-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sqs-helper.test.js.snap deleted file mode 100644 index 21679a006..000000000 --- a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sqs-helper.test.js.snap +++ /dev/null @@ -1,283 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Test dead letter queue deployment/configuration 1`] = ` -Object { - "Resources": Object { - "EncryptionKey1B843E66": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "EnableKeyRotation": true, - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "deadletterqueueD1EEB012": Object { - "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "deadletterqueueKey123D45B8", - "Arn", - ], - }, - }, - "Type": "AWS::SQS::Queue", - }, - "deadletterqueueKey123D45B8": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "Description": "Created by dead-letter-queue", - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "primaryqueue045A5712": Object { - "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "EncryptionKey1B843E66", - "Arn", - ], - }, - "RedrivePolicy": Object { - "deadLetterTargetArn": Object { - "Fn::GetAtt": Array [ - "deadletterqueueD1EEB012", - "Arn", - ], - }, - "maxReceiveCount": 3, - }, - }, - "Type": "AWS::SQS::Queue", - }, - }, -} -`; - -exports[`Test deployment w/ custom properties 1`] = ` -Object { - "Resources": Object { - "EncryptionKey1B843E66": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "EnableKeyRotation": true, - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "primaryqueue045A5712": Object { - "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "EncryptionKey1B843E66", - "Arn", - ], - }, - }, - "Type": "AWS::SQS::Queue", - }, - }, -} -`; - -exports[`Test minimal deployment with no properties 1`] = ` -Object { - "Resources": Object { - "primaryqueue045A5712": Object { - "Properties": Object { - "KmsMasterKeyId": Object { - "Fn::GetAtt": Array [ - "primaryqueueKeyD3CAD16D", - "Arn", - ], - }, - }, - "Type": "AWS::SQS::Queue", - }, - "primaryqueueKeyD3CAD16D": Object { - "DeletionPolicy": "Retain", - "Properties": Object { - "Description": "Created by primary-queue", - "KeyPolicy": Object { - "Statement": Array [ - Object { - "Action": Array [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": Object { - "AWS": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::", - Object { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/tools/cdk-integ-tools/package.json b/source/tools/cdk-integ-tools/package.json index 9ec2c34e8..7f7eb4205 100644 --- a/source/tools/cdk-integ-tools/package.json +++ b/source/tools/cdk-integ-tools/package.json @@ -31,9 +31,9 @@ "typescript": "~3.7.4" }, "dependencies": { - "@aws-cdk/cloudformation-diff": "1.40.0", - "@aws-cdk/cx-api": "1.40.0", - "aws-cdk": "1.40.0", + "@aws-cdk/cloudformation-diff": "~1.46.0", + "@aws-cdk/cx-api": "~1.46.0", + "aws-cdk": "~1.46.0", "fs-extra": "^8.1.0", "yargs": "^15.1.0" }, diff --git a/source/use_cases/aws-s3-static-website/architecture.png b/source/use_cases/aws-s3-static-website/architecture.png index 967bc554f08d66ed89aeb70cb9fa2faef6d5121e..54436bdf536bf640b1eddf0a16cb8e7da2bc43a1 100644 GIT binary patch literal 184640 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

5fLa_}|&hsvT>oX|mVbQbl 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⁢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 T-OO5+0SG)@{an^LB{Ts5zN)N| literal 172868 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

1jSY-UXe`70(Fg^SoD=^YiJ;U22U(y59~w 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Ð|h4*#1``yc7j$#h$n7$j9W$ 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 diff --git a/source/use_cases/aws-s3-static-website/lib/s3-static-site-stack.ts b/source/use_cases/aws-s3-static-website/lib/s3-static-site-stack.ts index a3df7e9fc..27c3fffc8 100644 --- a/source/use_cases/aws-s3-static-website/lib/s3-static-site-stack.ts +++ b/source/use_cases/aws-s3-static-website/lib/s3-static-site-stack.ts @@ -12,7 +12,7 @@ */ import { Construct, Stack, StackProps, Duration, CfnOutput } from '@aws-cdk/core'; -import { CloudFrontToS3 } from '@aws-solutions-konstruk/aws-cloudfront-s3'; +import { CloudFrontToS3 } from '@aws-solutions-constructs/aws-cloudfront-s3'; import * as lambda from '@aws-cdk/aws-lambda'; import { Provider } from '@aws-cdk/custom-resources'; import { CustomResource } from '@aws-cdk/aws-cloudformation'; @@ -25,10 +25,10 @@ export class S3StaticWebsiteStack extends Stack { const sourceBucket: string = 'wildrydes-us-east-1'; const sourcePrefix: string = 'WebApplication/1_StaticWebHosting/website/'; - const konstruk = new CloudFrontToS3(this, 'CloudFrontToS3', { + const construct = new CloudFrontToS3(this, 'CloudFrontToS3', { deployBucket: true }); - const targetBucket: string = konstruk.bucket().bucketName; + const targetBucket: string = construct.s3Bucket.bucketName; const lambdaFunc = new lambda.Function(this, 'copyObjHandler', { runtime: lambda.Runtime.PYTHON_3_8, @@ -71,7 +71,7 @@ export class S3StaticWebsiteStack extends Stack { }); new CfnOutput(this, 'websiteURL', { - value: 'https://' + konstruk.cloudFrontWebDistribution().domainName + value: 'https://' + construct.cloudFrontWebDistribution.domainName }); } } \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/package.json b/source/use_cases/aws-s3-static-website/package.json index da341c72f..dd28068c4 100644 --- a/source/use_cases/aws-s3-static-website/package.json +++ b/source/use_cases/aws-s3-static-website/package.json @@ -1,12 +1,12 @@ { - "name": "@aws-solutions-konstruk/aws-s3-static-website", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-s3-static-website", + "version": "1.46.0", "description": "Use case pattern for deploying a S3 static website.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", "directory": "source/use_cases/aws-s3-static-website" }, "author": { @@ -28,19 +28,19 @@ "build+lint+test": "npm run build && npm run lint && npm test && npm run integ-assert" }, "dependencies": { - "@aws-solutions-konstruk/aws-cloudfront-s3": "~0.8.1", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/custom-resources": "~1.40.0", - "@aws-cdk/aws-cloudformation": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-solutions-constructs/aws-cloudfront-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/custom-resources": "~1.46.0", + "@aws-cdk/aws-cloudformation": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "source-map-support": "^0.5.16" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, diff --git a/source/use_cases/aws-serverless-image-handler/README.md b/source/use_cases/aws-serverless-image-handler/README.md index b350f4013..fc092ca78 100644 --- a/source/use_cases/aws-serverless-image-handler/README.md +++ b/source/use_cases/aws-serverless-image-handler/README.md @@ -7,7 +7,7 @@ one or more Amazon S3 buckets within the deployment account. Here is a minimal deployable pattern definition: ``` -const { ServerlessImageHandler } = require('@aws-konstruk/aws-serverless-image-handler'); +const { ServerlessImageHandler } = require('@aws-solutions-constructs/aws-serverless-image-handler'); new ServerlessImageHandler(stack, 'ServerlessImageHandlerPattern', { deployLambda: true, diff --git a/source/use_cases/aws-serverless-image-handler/lib/index.ts b/source/use_cases/aws-serverless-image-handler/lib/index.ts index 9047fd8c5..441a91fef 100644 --- a/source/use_cases/aws-serverless-image-handler/lib/index.ts +++ b/source/use_cases/aws-serverless-image-handler/lib/index.ts @@ -12,15 +12,15 @@ */ // Imports -import * as defaults from '@aws-solutions-konstruk/core'; +import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; import * as cloudFront from '@aws-cdk/aws-cloudfront'; import * as apiGateway from '@aws-cdk/aws-apigateway'; import * as lambda from '@aws-cdk/aws-lambda'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; -import { CloudFrontToApiGatewayToLambda } from '@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda'; -import { LambdaToS3 } from '@aws-solutions-konstruk/aws-lambda-s3'; +import { CloudFrontToApiGatewayToLambda } from '@aws-solutions-constructs/aws-cloudfront-apigateway-lambda'; +import { LambdaToS3 } from '@aws-solutions-constructs/aws-lambda-s3'; /** * The properties for the ServerlessImageHandler class. @@ -151,7 +151,7 @@ export class ServerlessImageHandler extends Construct { deployLambda: true, lambdaFunctionProps: functionProps }); - const existingLambdaFn = this.cloudFrontApiGatewayLambda.lambdaFunction(); + const existingLambdaFn = this.cloudFrontApiGatewayLambda.lambdaFunction; // Build the LambdaToS3 pattern this.lambdaS3 = new LambdaToS3(this, 'ExistingLambdaS3', { @@ -196,9 +196,9 @@ export class ServerlessImageHandler extends Construct { // Add the SOURCE_BUCKETS environment variable to the Lambda function const bucketsArr = (props.sourceBuckets !== "") ? props.sourceBuckets.split(',') : []; - bucketsArr.push(this.lambdaS3.s3Bucket().bucketName); + bucketsArr.push(this.lambdaS3.s3Bucket.bucketName); const bucketsStr = bucketsArr.toString().replace(/\s+/g, ''); - this.cloudFrontApiGatewayLambda.lambdaFunction().addEnvironment("SOURCE_BUCKETS", bucketsStr); + this.cloudFrontApiGatewayLambda.lambdaFunction.addEnvironment("SOURCE_BUCKETS", bucketsStr); } /** @@ -208,7 +208,7 @@ export class ServerlessImageHandler extends Construct { * @access public */ public cloudFrontDistribution(): cloudFront.CloudFrontWebDistribution { - return this.cloudFrontApiGatewayLambda.cloudFrontWebDistribution(); + return this.cloudFrontApiGatewayLambda.cloudFrontWebDistribution; } /** @@ -218,7 +218,7 @@ export class ServerlessImageHandler extends Construct { * @access public */ public apiGateway(): apiGateway.RestApi { - return this.cloudFrontApiGatewayLambda.restApi(); + return this.cloudFrontApiGatewayLambda.apiGateway; } /** @@ -228,7 +228,7 @@ export class ServerlessImageHandler extends Construct { * @access public */ public lambdaFunction(): lambda.Function { - return this.cloudFrontApiGatewayLambda.lambdaFunction(); + return this.cloudFrontApiGatewayLambda.lambdaFunction; } /** @@ -238,6 +238,6 @@ export class ServerlessImageHandler extends Construct { * @access public */ public s3Bucket(): s3.Bucket { - return this.lambdaS3.s3Bucket(); + return this.lambdaS3.s3Bucket; } } \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/package.json b/source/use_cases/aws-serverless-image-handler/package.json index b63cb890d..de03059fc 100644 --- a/source/use_cases/aws-serverless-image-handler/package.json +++ b/source/use_cases/aws-serverless-image-handler/package.json @@ -1,13 +1,13 @@ { - "name": "@aws-konstruk/aws-serverless-image-handler", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-serverless-image-handler", + "version": "1.46.0", "description": "Use case pattern for deploying a serverless image handler API.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-konstruk.git", - "directory": "source/patterns/@aws-konstruk/aws-serverless-image-handler" + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-serverless-image-handler" }, "author": { "name": "Amazon Web Services", @@ -34,37 +34,37 @@ "outdir": "dist", "targets": { "java": { - "package": "software.amazon.konstruk.services.serverlessimagehandler", + "package": "software.amazon.awsconstructs.services.serverlessimagehandler", "maven": { - "groupId": "software.amazon.konstruk", + "groupId": "software.amazon.awsconstructs", "artifactId": "serverlessimagehandler" } }, "dotnet": { - "namespace": "Amazon.Konstruk.AWS.ServerlessImageHandler", - "packageId": "Amazon.Konstruk.AWS.ServerlessImageHandler", + "namespace": "Amazon.Constructs.AWS.ServerlessImageHandler", + "packageId": "Amazon.Constructs.AWS.ServerlessImageHandler", "signAssembly": true, "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" }, "python": { - "distName": "aws-konstruk.aws-serverless-image-handler", - "module": "aws_konstruk.aws_serverless_image_handler" + "distName": "aws-solutions-constructs.aws-serverless-image-handler", + "module": "aws_solutions_constructs.aws_serverless_image_handler" } } }, "dependencies": { - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/core": "~1.40.0", - "@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda": "~0.8.1", - "@aws-solutions-konstruk/aws-lambda-s3": "~0.8.1", - "@aws-solutions-konstruk/core": "~0.8.1" + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-solutions-constructs/aws-cloudfront-apigateway-lambda": "~1.46.0", + "@aws-solutions-constructs/aws-lambda-s3": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" }, @@ -74,14 +74,14 @@ ] }, "peerDependencies": { - "@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda": "~0.8.1", - "@aws-solutions-konstruk/aws-lambda-s3": "~0.8.1", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", - "@aws-cdk/aws-iam": "~1.40.0" + "@aws-solutions-constructs/aws-cloudfront-apigateway-lambda": "~1.46.0", + "@aws-solutions-constructs/aws-lambda-s3": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0" } } diff --git a/source/use_cases/aws-serverless-web-app/lib/s3-static-site-stack.ts b/source/use_cases/aws-serverless-web-app/lib/s3-static-site-stack.ts index 646f28893..1299caa2b 100644 --- a/source/use_cases/aws-serverless-web-app/lib/s3-static-site-stack.ts +++ b/source/use_cases/aws-serverless-web-app/lib/s3-static-site-stack.ts @@ -12,7 +12,7 @@ */ import { Construct, Stack, StackProps, Duration, CfnOutput } from '@aws-cdk/core'; -import { CloudFrontToS3 } from '@aws-solutions-konstruk/aws-cloudfront-s3'; +import { CloudFrontToS3 } from '@aws-solutions-constructs/aws-cloudfront-s3'; import * as lambda from '@aws-cdk/aws-lambda'; import { Provider } from '@aws-cdk/custom-resources'; import { CustomResource } from '@aws-cdk/aws-cloudformation'; @@ -25,10 +25,10 @@ export class S3StaticWebsiteStack extends Stack { const sourceBucket: string = 'wildrydes-us-east-1'; const sourcePrefix: string = 'WebApplication/1_StaticWebHosting/website/'; - const konstruk = new CloudFrontToS3(this, 'CloudFrontToS3', { + const construct = new CloudFrontToS3(this, 'CloudFrontToS3', { deployBucket: true }); - const targetBucket: string = konstruk.bucket().bucketName; + const targetBucket: string = construct.s3Bucket.bucketName; const lambdaFunc = new lambda.Function(this, 'staticContentHandler', { runtime: lambda.Runtime.PYTHON_3_8, @@ -71,7 +71,7 @@ export class S3StaticWebsiteStack extends Stack { }); new CfnOutput(this, 'websiteURL', { - value: 'https://' + konstruk.cloudFrontWebDistribution().domainName + value: 'https://' + construct.cloudFrontWebDistribution.domainName }); new CfnOutput(this, 'websiteBucket', { diff --git a/source/use_cases/aws-serverless-web-app/lib/serverless-backend-stack.ts b/source/use_cases/aws-serverless-web-app/lib/serverless-backend-stack.ts index 70b57f305..3afa348b5 100644 --- a/source/use_cases/aws-serverless-web-app/lib/serverless-backend-stack.ts +++ b/source/use_cases/aws-serverless-web-app/lib/serverless-backend-stack.ts @@ -11,8 +11,8 @@ * and limitations under the License. */ -import { CognitoToApiGatewayToLambda } from '@aws-solutions-konstruk/aws-cognito-apigateway-lambda'; -import { LambdaToDynamoDB } from '@aws-solutions-konstruk/aws-lambda-dynamodb'; +import { CognitoToApiGatewayToLambda } from '@aws-solutions-constructs/aws-cognito-apigateway-lambda'; +import { LambdaToDynamoDB } from '@aws-solutions-constructs/aws-lambda-dynamodb'; import { Construct, Stack, StackProps, Duration, Fn } from '@aws-cdk/core'; import * as lambda from '@aws-cdk/aws-lambda'; import { Provider } from '@aws-cdk/custom-resources'; @@ -27,7 +27,7 @@ export class ServerlessBackendStack extends Stack { const websiteBucketName: string = Fn.importValue('websiteBucket'); - const konstruk = new CognitoToApiGatewayToLambda(this, 'CognitoToApiGatewayToLambda', { + const construct = new CognitoToApiGatewayToLambda(this, 'CognitoToApiGatewayToLambda', { deployLambda: true, lambdaFunctionProps: { code: lambda.Code.asset(`${__dirname}/lambda/business-logic`), @@ -68,17 +68,17 @@ export class ServerlessBackendStack extends Stack { new CustomResource(this, 'CustomResource', { provider: customResourceProvider, properties: { - UserPool: konstruk.userPool().userPoolId, - Client: konstruk.userPoolClient().userPoolClientId, + UserPool: construct.userPool.userPoolId, + Client: construct.userPoolClient.userPoolClientId, Region: Stack.of(this).region, Bucket: websiteBucketName, - RestApi: konstruk.restApi().url + RestApi: construct.apiGateway.url } }); new LambdaToDynamoDB(this, 'LambdaToDynamoDB', { deployLambda: false, - existingLambdaObj: konstruk.lambdaFunction(), + existingLambdaObj: construct.lambdaFunction, dynamoTableProps: { tableName: 'Rides', partitionKey: { diff --git a/source/use_cases/aws-serverless-web-app/package.json b/source/use_cases/aws-serverless-web-app/package.json index da373f845..7b1a0c10e 100644 --- a/source/use_cases/aws-serverless-web-app/package.json +++ b/source/use_cases/aws-serverless-web-app/package.json @@ -1,12 +1,12 @@ { - "name": "@aws-solutions-konstruk/aws-serverless-web-app", - "version": "0.8.1", + "name": "@aws-solutions-constructs/aws-serverless-web-app", + "version": "1.46.0", "description": "Use case pattern for deploying a serverless web app.", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-solutions-konstruk.git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", "directory": "source/use_cases/aws-serverless-web-app" }, "author": { @@ -28,24 +28,24 @@ "build+lint+test": "npm run build && npm run lint && npm test && npm run integ-assert" }, "dependencies": { - "@aws-solutions-konstruk/aws-cloudfront-s3": "~0.8.1", - "@aws-solutions-konstruk/aws-cognito-apigateway-lambda": "~0.8.1", - "@aws-solutions-konstruk/aws-lambda-dynamodb": "~0.8.1", - "@aws-cdk/core": "~1.40.0", - "@aws-cdk/aws-lambda": "~1.40.0", - "@aws-cdk/aws-cloudfront": "~1.40.0", - "@aws-cdk/aws-s3": "~1.40.0", - "@aws-cdk/custom-resources": "~1.40.0", - "@aws-cdk/aws-cloudformation": "~1.40.0", - "@aws-cdk/aws-iam": "~1.40.0", - "@aws-cdk/aws-cognito": "~1.40.0", - "@aws-cdk/aws-apigateway": "~1.40.0", - "@aws-cdk/aws-dynamodb": "~1.40.0", - "@aws-solutions-konstruk/core": "~0.8.1", + "@aws-solutions-constructs/aws-cloudfront-s3": "~1.46.0", + "@aws-solutions-constructs/aws-cognito-apigateway-lambda": "~1.46.0", + "@aws-solutions-constructs/aws-lambda-dynamodb": "~1.46.0", + "@aws-cdk/core": "~1.46.0", + "@aws-cdk/aws-lambda": "~1.46.0", + "@aws-cdk/aws-cloudfront": "~1.46.0", + "@aws-cdk/aws-s3": "~1.46.0", + "@aws-cdk/custom-resources": "~1.46.0", + "@aws-cdk/aws-cloudformation": "~1.46.0", + "@aws-cdk/aws-iam": "~1.46.0", + "@aws-cdk/aws-cognito": "~1.46.0", + "@aws-cdk/aws-apigateway": "~1.46.0", + "@aws-cdk/aws-dynamodb": "~1.46.0", + "@aws-solutions-constructs/core": "~1.46.0", "source-map-support": "^0.5.16" }, "devDependencies": { - "@aws-cdk/assert": "~1.40.0", + "@aws-cdk/assert": "~1.46.0", "@types/jest": "^24.0.23", "@types/node": "^10.3.0" },